[
  {
    "path": ".agents/docs/adapter-development.md",
    "content": "# Adapter Development\n\nGuide for adding framework adapters to nuqs.\n\n## Overview\n\nAdapters wrap the app root and provide the minimal translation layer between nuqs and framework routing APIs.\n\n- **Next.js app router:** `nuqs/adapters/next/app`\n- **Next.js pages router:** `nuqs/adapters/next/pages`\n- **React SPA:** `nuqs/adapters/react`\n- **Remix:** `nuqs/adapters/remix`\n- **React Router v6:** `nuqs/adapters/react-router/v6`\n- **React Router v7:** `nuqs/adapters/react-router/v7`\n- **TanStack Router:** `nuqs/adapters/tanstack-router`\n- **Testing:** `nuqs/adapters/testing`\n\n## Adding a New Framework Adapter\n\n### Checklist\n\n1. **Mirror existing adapter API surface**\n   - Ensure the exported provider component matches established patterns\n   - Use consistent naming and parameter shapes\n\n2. **Implement minimal feature parity**\n   - Read/query: Parse current search params\n   - Push/replace history: Update URL without full reload\n   - Batching: Support merging multiple updates per tick\n\n3. **Create e2e bench**\n   - Add test application under `packages/e2e/<framework>`\n   - Cover both App Router and Pages Router if applicable\n\n4. **Documentation**\n   - Update README Adapters section\n   - Add docs content page explaining adapter setup\n\n5. **Test coverage**\n   - Unit tests for adapter integration\n   - E2E tests for framework-specific behaviors\n\n### Key Requirements\n\n- Adapter must support `shallow` option semantics (when applicable to framework)\n- Handle history `push` vs `replace` operations correctly\n- Ensure batch queue integrity (updates per key merged while preserving final state)\n- No memory leaks (listeners removed on unmount)\n\n### Server-Side Utilities\n\nWhen adding adapter support, consider server utilities:\n\n- **Loader:** `createLoader(parsers[, { urlKeys }])` for one-off parsing\n- **Cache:** `createSearchParamsCache(parsers)` for nested Server Components (Next.js app router)\n- **Serializer:** `createSerializer(parsers[, { urlKeys }])` for canonical URLs / links\n- Import server helpers from `'nuqs/server'` (avoids the `\"use client\"` directive)\n\nThese should work identically across adapters where applicable.\n\n## Options Semantics\n\nWhen implementing adapter support:\n\n- **`history`:** `'replace'` (default) or `'push'`\n- **`shallow`** (Next.js only): Default true (client-first). Set false to trigger RSC / SSR invalidation\n- **`throttleMs`:** ≥50ms (ignored if lower). Only URL & server notification are throttled, not in-memory state\n- **`startTransition`:** Pass from `React.useTransition` when using `shallow: false` for loading states\n\nOverride per-update via second argument to setter: `setValue(v, { history, shallow, throttleMs })`\n\n## Architectural Flow\n\n1. Hook reads initial value from current `window.location.search`\n2. Local React state mirrors parsed value\n3. Setter enqueues mutation intent (key → serialized value | delete)\n4. Batch flush (throttled) applies merged changes to History API (push/replace)\n5. Promise resolves with updated `URLSearchParams`\n6. If `shallow: false`: uses router APIs to trigger server-side rendering / data fetching\n\n## Extensibility\n\n- Prefer composition (wrapping adapter) over modifying core adapter\n- Keep adapter interfaces thin (translate framework navigation to common history operations)\n- Avoid duplicating logic across adapters (prefer shared utility)\n"
  },
  {
    "path": ".agents/docs/api-design.md",
    "content": "# API Design & Architecture\n\nGuide for maintaining API stability, designing extensions, and understanding the architecture.\n\n## Core Architecture\n\n### Flow\n\n1. **Initialization:** Hook reads initial value from current `window.location.search`\n2. **Local State:** React state mirrors parsed value\n3. **Mutation Enqueue:** Setter enqueues mutation intent (key → serialized value | delete)\n4. **Batch & Throttle:** Multiple `setState` calls in one tick are merged\n5. **Flush to URL:** Batch flush (throttled ≥50ms) applies merged changes to History API (push/replace)\n6. **Promise Resolution:** Returns when URL update flushes; cache per batch\n7. **Server Trigger:** If `shallow: false`, uses router APIs to trigger server-side rendering / data fetching\n\n### Batch Queue Integrity\n\n- **Key merging:** Updates for the same key within one tick are coalesced (final state wins)\n- **Final value preservation:** Last write for each key determines the output\n- **No reordering:** Order of keys in URL is stable\n\n## Design Principles\n\n### 1. URL as Single Source of Truth\n\n- URL shape defines state shape\n- Parsing must be deterministic\n- Serialization must be lossless\n\n### 2. Type Safety\n\n- Hooks return typed values matching parser output\n- Builder chaining preserves types\n- Type exports are part of public API\n\n### 3. Zero Dependencies & Bundle Size\n\n- No external dependencies\n- Avoid side effects in module top-level (affects tree shaking)\n- Keep parse/serialize fast and lightweight\n- Throwing in parsers has bundle cost (return `null` instead)\n\n### 4. Backward Compatibility\n\nBefore modifying core logic in `packages/nuqs`:\n\n1. **Assess API surface impact**\n   - Type exports\n   - Builder chaining\n   - Hook signatures\n\n2. **Maintain backward compatibility** unless intentional breaking change\n   - Breaking changes require justification\n   - Provide migration notes in PR body\n\n3. **Validate types** with `pnpm test --filter nuqs` (includes TS type tests)\n\n## Performance & Reliability\n\n### Batch Efficiency\n\n- Merging updates per key while preserving final state\n- Single URL write per flush cycle\n- No synchronous expensive operations inside parse/serialize\n\n### Memory Management\n\n- Ensure no memory leaks\n- Remove listeners on unmount\n- Clear event handler references\n\n### URL Determinism\n\n- Keep serialization deterministic\n- Use stable ordering for multiple keys\n- Consistent formatting\n\n## Extensibility Guidelines\n\nWhen extending nuqs:\n\n### Composition Over Modification\n\n- Prefer wrapping parsers over modifying core hook\n- Create adapters for new frameworks instead of baking support in core\n- Use builder pattern for optional behaviors\n\n### Code Organization\n\n- Add generic utilities under internal helpers module if reused ≥2 places\n- Keep adapter interfaces thin\n- Translate framework navigation to common history operations\n\n### Builder Pattern\n\n- Use `.withDefault()` for defaults\n- Use `.withOptions()` for behavior customization\n- Keep chaining lightweight\n\n## Safety Checklist for Core Changes\n\nDo not:\n\n- Introduce side effects in module top-level (tree shaking impact)\n- Use non-standard browser APIs without guards\n- Increase bundle size significantly (maintain zero dependencies)\n- Export internal implementation details\n- Break existing type signatures\n\nDo:\n\n- Test types with type-level tests (`.test-d.ts`)\n- Provide type exports alongside implementation\n- Document API changes in README\n- Validate with full test suite\n"
  },
  {
    "path": ".agents/docs/git-workflow.md",
    "content": "# Release & Git Workflow\n\nGuide for versioning, commits, pull requests, and release process.\n\n## Conventional Commits\n\nAll commits follow the Conventional Commits specification. This is enforced by review.\n\n### Format\n\n```\n<type>(<scope>): <subject>\n\n<body>\n\n<footer>\n```\n\n### Commit Types\n\n- **`feat:`** New feature\n- **`fix:`** Bug fix\n- **`perf:`** Performance improvement\n- **`docs:`** Documentation only\n- **`refactor:`** Code restructuring (no feature change)\n- **`test:`** Test addition or update\n- **`chore:`** Tooling, dependencies, config\n\n### Breaking Changes\n\nFor breaking changes:\n\n1. Use `feat!:` or `fix!:` prefix\n2. Add footer: `BREAKING CHANGE: <description>`\n\nExample:\n\n```\nfeat!: change parser return type\n\nBREAKING CHANGE: parseAsInteger now throws on invalid input instead of returning null\n```\n\n## Semantic Versioning\n\nVersion bumping is **automated by `semantic-release`**:\n\n- **Patch** (v1.2.3 → v1.2.4) — `fix:` commits\n- **Minor** (v1.2.0 → v1.3.0) — `feat:` commits\n- **Major** (v1.0.0 → v2.0.0) — `feat!:` or `fix!:` (breaking changes)\n\n**Do not manually bump versions** — The automation handles this.\n\n## Release Branch\n\n- **Release branch:** `next`\n- Commits to `next` trigger `semantic-release`\n- Publishing to NPM is automatic\n- Check [GitHub Releases](../../releases) for version history\n\n## Pull Request Standards\n\n### Before Opening a PR\n\n1. Ensure commit messages follow Conventional Commits\n2. Run local tests: `pnpm test`\n3. Verify types with type-level tests\n4. Update documentation if applicable\n5. Consider if breaking change justification is needed\n\n### PR Description\n\nInclude:\n\n- **Summary** — What is this PR doing and why?\n- **Type** — Is this a feature, fix, refactor, or doc update?\n- **Changes** — High-level list of modified areas\n- **Testing** — What test coverage was added?\n- **Breaking Changes** — If applicable, describe migration path\n\n### PR Title\n\nPR title should match the first commit message (Conventional Commit format):\n\n- `feat: add new parser type`\n- `fix: handle edge case in batching`\n- `docs: update adapter setup guide`\n\n### PR Checklist\n\nBefore marking ready for review:\n\n- [ ] `pnpm test` passes locally\n- [ ] Tests added/updated for new behavior\n- [ ] All new exports documented\n- [ ] Docs content updated if applicable\n- [ ] README updated if user-facing change\n- [ ] No unintended bundle size growth\n- [ ] Conventional Commit message\n- [ ] No unresolved TODOs introduced\n- [ ] No stray console logs (except controlled debug)\n\n## Documentation Updates\n\nUpdate documentation when:\n\n- **Public API surface changes** (new exports, hook signature)\n- **Parser behavior changes** (parsing rules, serialization)\n- **Adapter requirements change** (new options, breaking changes)\n\n### What to Update\n\n1. **README.md**\n   - Examples section\n   - API reference\n   - Adapter list\n\n2. **MDX docs** under `packages/docs/content`\n   - Mirror relevant README sections\n   - Add detailed examples\n   - Document configuration options\n\n### Documentation Best Practices\n\n- Keep examples concise\n- Link to existing demos instead of duplicating code\n- Maintain consistency with existing documentation style\n- Add examples that show common use cases\n- Update table of contents if adding new sections\n\n## Decision Log\n\nWhen making non-trivial architectural changes:\n\n1. Add a short note to AGENTS.md Decision Log section\n2. Format: `YYYY-MM-DD - <Title> - Rationale / Impact / Migration (if any)`\n3. Examples:\n   - `2025-01-15 - Add batching optimization - Reduces URL updates by 40% when multiple state changes occur in same tick. No migration needed.`\n   - `2025-01-20 - Parser requires eq method - Enables custom equality checks for complex types. Existing parsers automatically compatible.`\n\n## Automation & Tools\n\nThe team uses the following automation:\n\n- **Conventional Commits:** Enforced by commit linting\n- **Type checking:** Part of `pnpm test`\n- **semantic-release:** Automatic versioning and publishing from `next` branch\n- **PR checks:** Linting, testing, type checking run automatically\n\n**Agents may:**\n\n- Generate parser boilerplate\n- Update Adapters section in documentation\n- Append PR checklist results\n- Run local tests & lint before proposing changes\n\n**Agents must not:**\n\n- Auto-commit version bumps (handled by release automation)\n- Force push to main/master\n- Modify git configuration\n- Skip git hooks\n"
  },
  {
    "path": ".agents/docs/parser-implementation.md",
    "content": "# Parser Implementation\n\nGuide for creating custom parsers and understanding parser semantics.\n\n## Overview\n\nParsers are the bridge between URL strings and typed state. Each parser provides bidirectional conversion.\n\n## Creating a Custom Parser\n\n### Core Interface\n\nA parser has two methods:\n\n- **`parse(query: string): T | null`** — Deserialize from URL string\n- **`serialize(value: T): string`** — Serialize to URL string\n\nOptionally:\n\n- **`eq`** — Custom equality check (defaults to `===`)\n\n### Implementation Checklist\n\n1. **Implement `parse(query: string): T | null`**\n   - Return `null` for invalid input (not throwing; smaller bundle impact)\n   - Keep the function pure and fast\n\n2. **Implement `serialize(value: T): string`**\n   - Must be deterministic (stable output for same input)\n   - Pure function, no side effects\n\n3. **Wrap with `createParser`**\n   - Enables chaining with `.withDefault()` and `.withOptions()`\n   - Example: `export const parseAsInteger = createParser({ parse, serialize, eq })`\n\n4. **Validate bijectivity**\n   - Ensure `parse(serialize(v))` yields an equivalent value\n   - Use the `isParserBijective` helper to verify\n   - Round-trip tests are essential\n\n5. **Add unit tests**\n   - Valid inputs\n   - Invalid inputs\n   - Round-trip verification\n   - Edge cases specific to your type\n\n6. **Update documentation**\n   - README Parsing section\n   - MDX docs under `packages/docs/content`\n\n7. **Consider server import path support**\n   - Works identically when imported from `'nuqs/server'`\n   - Use standard library functions only (no DOM APIs)\n\n## Parser Design Principles\n\n### Serialization Rules\n\n- **Lossless:** Must preserve all information needed for valid round-trips\n- **Pure:** Same input always produces same output\n- **Deterministic:** Stable ordering when using multiple keys\n- **No side effects:** Keep async operations out of parse/serialize\n\n### Error Handling\n\n- **Invalid parse:** Return `null`, not throw\n  - Reduces bundle size impact\n  - Allows graceful degradation\n  - Simpler composition\n\n- **Validation is optional:** Parsers do not validate semantic constraints\n  - If you add validation helpers, keep them opt-in\n  - Avoid coupling to heavy schema libs\n  - Document integrations (e.g., Zod) externally\n\n### Performance Considerations\n\n- Keep parse/serialize as lightweight as possible\n- Avoid expensive operations in these functions\n- Remember they run synchronously on URL changes\n\n## Builder Methods\n\n### `.withDefault(value)`\n\nProvides an internal default value. The default is **not written to the URL**.\n\n```ts\nconst parser = parseAsInteger.withDefault(0)\n// URL: ?count=    (empty or absent)\n// State: 0        (from default)\n```\n\n### `.withOptions({ history, shallow, limitUrlUpdates, startTransition })`\n\nConfigure behavior options:\n\n- **`history`:** `'push'` or `'replace'` (default)\n- **`shallow`:** Trigger SSR/RSC invalidation (Next.js)\n- **`limitUrlUpdates`:** Optional function to debounce updates\n- **`startTransition`:** Pass from `useTransition` for loading states\n\n## Anti-Patterns\n\nAvoid:\n\n- **Throwing for invalid input** — Return `null` instead\n- **Lossy serialization** — Must preserve all information\n- **Impure functions** — Same input must produce same output\n- **Blocking async behavior** — No Promise-based parsing\n- **Non-deterministic ordering** — Matters for URL length and caching\n\n## Security & Validation\n\nParsers are primarily **type converters**, not validators. If you need validation:\n\n- Keep validation helpers opt-in\n- Document recommended external libraries (e.g., Zod, Standard Schema v1)\n- Prefer composition over coupling to schema libraries\n\n## Examples\n\nSee the parser test suite and README for concrete examples:\n\n- `parseAsInteger` — Basic number parsing\n- `parseAsString` — String identity\n- `parseAsArrayOf()` — Generic array parsing\n- Custom parsers in documentation\n"
  },
  {
    "path": ".agents/docs/quality-standards.md",
    "content": "# Quality Standards\n\nGuide for performance, security, reliability, and quality assurance.\n\n## Exit Conditions for Agent Tasks\n\nA task is **DONE** when all of the following are satisfied:\n\n- [ ] All checklist items satisfied\n- [ ] Tests pass locally (`pnpm test`)\n- [ ] Docs consistent with behavior\n- [ ] No unresolved TODOs introduced\n- [ ] No stray console logs (except controlled debug support)\n\n## Performance Guidelines\n\n### Bundle Size Constraints\n\n- **Maintain zero dependencies** — Core library has no external deps\n- **Avoid side effects in module top-level** — Affects tree shaking\n- **Keep parse/serialize fast** — Called synchronously on every URL change\n- **Avoid expensive operations** inside hooks (memoize if needed)\n\n### Measuring Performance\n\nFor performance improvements:\n\n1. Benchmark before/after\n2. Include measurement methodology in PR\n3. Document impact quantitatively\n4. Validate with full test suite\n\n### Batch Efficiency\n\n- Merging updates per key preserves final state\n- Single URL write per flush cycle (≥50ms throttle)\n- No blocking operations during flush\n- Listeners cleaned up on unmount (no leaks)\n\n## Reliability Guidelines\n\n### Memory Management\n\n- **Remove listeners on unmount** — Prevents memory leaks\n- **Clear event handler references** — Especially in cleanup phases\n- **No circular references** — Between components and parsers\n- **Test cleanup** — Verify unmounted components don't cause errors\n\n### URL Determinism\n\n- **Stable serialization** — Same input always produces same output\n- **Consistent ordering** — Multiple keys serialize in predictable order\n- **Deterministic parsing** — No randomness or side effects\n- **Lossless round-trip** — `parse(serialize(v)) ≈ v` for all valid values\n\n### Error Handling\n\nWhen something goes wrong:\n\n- **Parser invalid input:** Return `null`, not throw\n  - Smaller bundle impact\n  - Graceful degradation\n  - Easier composition\n\n- **Invalid state recovery:** Fall back to defaults gracefully\n- **Type safety:** Prevent invalid states at type level\n\n## Security Practices\n\n### Parser Validation\n\nParsers are primarily **type converters**, not validators:\n\n- Keep validation opt-in\n- Avoid coupling to heavy validation libraries\n- Document external integrations (Zod, Standard Schema v1)\n\n### No User Input Injection\n\n- Parse all URL params defensively\n- Validate types before using\n- Never interpolate user input into code/templates\n\n### Safe Browser API Usage\n\n- Guard non-standard browser APIs\n- Check for availability before use\n- Fallback to safe alternatives\n\n## Anti-Patterns to Avoid\n\n### In Parsers\n\n- ❌ **Throwing for invalid input** — Return `null` instead\n- ❌ **Lossy serialization** — Must preserve all information\n- ❌ **Impure functions** — Same input must produce same output\n- ❌ **Blocking async behavior** — No Promise-based parsing\n- ❌ **Non-deterministic output** — Breaks URL length and caching\n\n### In Hooks\n\n- ❌ **Side effects in render** — Use useEffect properly\n- ❌ **Synchronous expensive operations** — Defer to useCallback/useMemo\n- ❌ **Memory leaks on unmount** — Always clean up\n- ❌ **Infinite update loops** — Verify batch and throttle mechanics\n\n### In Adapters\n\n- ❌ **Duplicating logic across adapters** — Prefer shared utilities\n- ❌ **Tight coupling to framework internals** — Use public APIs\n- ❌ **Breaking API compatibility** — Keep surfaces aligned\n- ❌ **Missing batch/throttle support** — Essential for all adapters\n\n### In Core Library\n\n- ❌ **Side effects in module top-level** — Breaks tree shaking\n- ❌ **Non-standard browser APIs** — Without guards\n- ❌ **Significant bundle growth** — Monitor size in PRs\n- ❌ **Exporting internal implementation** — Only public interfaces\n\n## Code Quality Checklist\n\nFor any change:\n\n- [ ] Type-safe throughout\n- [ ] Tests added or updated\n- [ ] No console.log/debugger statements\n- [ ] No dead code\n- [ ] No duplicate logic\n- [ ] Comments explain \"why\", not \"what\"\n- [ ] Function names are clear\n- [ ] Error messages are helpful\n\n## Documentation Quality\n\nFor user-facing changes:\n\n- [ ] README updated with examples\n- [ ] API changes documented with types\n- [ ] Migration guide (if breaking change)\n- [ ] Examples runnable and up-to-date\n- [ ] No typos or grammatical errors\n- [ ] Consistent with existing docs style\n\n## Type Safety\n\n- [ ] All exports have explicit types\n- [ ] Generic constraints are clear\n- [ ] No `any` types unless justified\n- [ ] Type tests included (`.test-d.ts`)\n- [ ] Types match behavior\n- [ ] Return types are specific (not `unknown`)\n\n## Common Issues to Check\n\n### Import Paths\n\n- Verify relative imports resolve correctly\n- Check that exports work from both CJS and ESM\n- Ensure server utilities available from `'nuqs/server'`\n\n### Framework Adapters\n\n- Verify history API usage matches framework\n- Check batch/throttle behavior aligns with core\n- Test in actual framework (not just testing adapter)\n\n### Type Coverage\n\n- Run type tests: `pnpm test --filter nuqs`\n- Verify exported types in `api.test.ts`\n- Check builder chaining preserves types\n\n## Debugging Checklist\n\nEnable debug logs when investigating:\n\n```js\nlocalStorage.setItem('debug', 'nuqs')\n```\n\nUse prefixes:\n\n- `[nuqs]` — Single-key hook operations\n- `[nuq+]` — Multi-key hook operations\n\nCapture debug output for:\n\n- Issue reports\n- Performance analysis\n- State synchronization problems\n"
  },
  {
    "path": ".agents/docs/testing.md",
    "content": "# Testing Patterns\n\nGuide for testing strategy, test organization, and regression workflows.\n\n## Full Test Suite\n\nRun the complete test pipeline:\n\n```bash\npnpm test\n```\n\nThis takes **5-10 minutes** and includes:\n\n- Build (tsup)\n- Unit tests\n- Type-level tests\n- End-to-end tests\n\nDo not time out the full suite.\n\n## Test Categories\n\n### Unit Tests\n\n**Where:** `packages/nuqs/tests/*.test.ts`\n\nTest hooks with `NuqsTestingAdapter`:\n\n```ts\nimport { NuqsTestingAdapter } from 'nuqs/adapters/testing'\nimport { renderHook, act } from '@testing-library/react'\n```\n\nCoverage:\n\n- Parser logic (valid, invalid, round-trip)\n- Hook behavior (state updates, URL sync)\n- Batching and throttling\n- Builder methods (`.withDefault()`, `.withOptions()`)\n\n### Type-Level Tests\n\n**Where:** `packages/nuqs/tests/*.test-d.ts`\n\nAdd type tests when updating type definitions:\n\n```ts\nimport { expectType, expectAssignable } from 'tsd'\n```\n\nCoverage:\n\n- Hook return types\n- Parser generic constraints\n- Builder result types\n- Exported type shape\n\n### API Tests\n\n**Where:** `packages/nuqs/src/api.test.ts`\n\nCheck exported symbols when adding new exports:\n\n```ts\n// Verify API surface matches documentation\nimport * as api from 'nuqs'\n```\n\nCoverage:\n\n- All public exports present\n- No unintended exports\n\n### End-to-End Tests\n\n**Where:** `packages/e2e/*`\n\nTest framework-specific adapter behaviors:\n\nFramework targets:\n\n- Next.js app router\n- Next.js pages router\n- React SPA\n- Remix\n- TanStack Router\n- React Router v6/v7\n\nCoverage:\n\n- Initial page load with search params\n- URL updates and state synchronization\n- History push/replace\n- Adapter-specific features (shallow, SSR)\n- Frame/tab sync (where applicable)\n\n## Regression Workflow\n\nWhen fixing a bug:\n\n1. **Reproduce with failing test first** (preferred)\n   - Add test case demonstrating the bug\n   - Test should fail before fix\n   - Test passes after fix\n\n2. **Fix the issue**\n   - Minimal change to fix the specific problem\n   - Preserve all other behavior\n\n3. **Ensure types remain stable**\n   - Run type-level tests\n   - Check `api.test.ts`\n   - Validate with full `pnpm test`\n\n4. **Add scenario to e2e if framework-specific**\n   - If the bug is adapter-related, add e2e coverage\n   - Helps prevent regressions in that framework\n\n## Common Testing Patterns\n\n### Testing a Parser\n\n```ts\ndescribe('parseAsCustomType', () => {\n  it('parses valid input', () => {\n    expect(parseAsCustomType.parse('valid')).toEqual(expectedValue)\n  })\n\n  it('returns null for invalid input', () => {\n    expect(parseAsCustomType.parse('invalid')).toBeNull()\n  })\n\n  it('round-trips correctly', () => {\n    const value = {\n      /* ... */\n    }\n    expect(parseAsCustomType.parse(parseAsCustomType.serialize(value))).toEqual(\n      value\n    )\n  })\n})\n```\n\n### Testing Hook Behavior\n\n```ts\nit('updates state and URL together', () => {\n  const { result } = renderHook(() => useQueryState('key', parseAsInteger), {\n    wrapper: NuqsTestingAdapter\n  })\n\n  act(() => {\n    result.current[1](42)\n  })\n\n  expect(result.current[0]).toBe(42)\n  // Verify URL updated via NuqsTestingAdapter\n})\n```\n\n## Test Organization Best Practices\n\n- **One concept per test** — Single assertion focus\n- **Clear naming** — Describe the scenario, not just \"it works\"\n- **Isolate concerns** — Unit tests for logic, e2e for integration\n- **Use fixtures** — Reusable test data and setup\n- **Cleanup** — Unmount components, clear listeners\n- **Type safety** — Use TypeScript for test code too\n\n## Debugging Tests\n\nEnable debug logs:\n\n```ts\n// In test file\nbeforeEach(() => {\n  localStorage.setItem('debug', 'nuqs')\n})\n\nafterEach(() => {\n  localStorage.removeItem('debug')\n})\n```\n\nDebug output:\n\n- `[nuqs]` — Single-key operations\n- `[nuq+]` — Multi-key operations\n\n## CI/CD Integration\n\n- Tests run automatically on pull requests\n- Full suite must pass before merge\n- Type checking is part of test suite\n- No manual intervention needed for test validation\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [franky47]\nliberapay: francoisbest\ncustom: ['https://paypal.me/francoisbest?locale.x=fr_FR']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n---\n\n<!--\nPlease read and follow the issue template.\nIssues submitted without a reproduction and context\nwill take longer to resolve.\n-->\n\n## Context\n\nWhat's your version of `nuqs`?\n\n```\n-> Paste result from `cat package.json | grep -e nuqs` here\n```\n\nWhat framework are you using?\n\n<!-- Keep whichever is relevant (✅: used, ❌ not used) -->\n\n- ✅/❌ Next.js (app router)\n- ✅/❌ Next.js (pages router)\n- ✅/❌ React SPA (no router)\n- ✅/❌ Remix\n- ✅/❌ React Router\n- ✅/❌ Other (please specify)\n\nWhich version of your framework are you using?\n\n<!-- Note: Next.js information can obtained by running `next info` -->\n\n```\n-> Paste the relevant framework versions from your package.json here\n```\n\n## Description\n\n<!-- A clear and concise description of what the bug is, and what you expected to happen instead. -->\n\n## Reproduction\n\n<!-- Please provide a minimal reproduction in a CodeSandbox playground or dedicated repository, along with the steps to take to encounter the issue.\n\nExample: Steps to reproduce the behavior:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Feature idea\n    url: https://github.com/47ng/nuqs/discussions/new?category=ideas\n    about: Please share ideas for new features as discussion\n  - name: Question\n    url: https://github.com/47ng/nuqs/discussions/new?category=q-a\n    about: Please use the discussions to ask the community for help\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # - package-ecosystem: npm\n  #   directory: /\n  #   schedule:\n  #     interval: weekly\n  #     time: \"09:00\"\n  #     timezone: Europe/Paris\n  #   assignees:\n  #     - franky47\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: monthly\n    assignees:\n      - franky47\n"
  },
  {
    "path": ".github/workflows/analyse-nextjs-release.yml",
    "content": "name: \"Analyse Next.js release\"\nrun-name: \"Analyse Next.js ${{ inputs.version }}\"\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Next.js version to test against\"\n        required: true\n        type: string\n\nenv:\n  FORCE_COLOR: 3 # Diplay chalk colors\n  VERSION: ${{ inputs.version }}\n\njobs:\n  analyse-release:\n    runs-on: ubuntu-24.04-arm\n    name: Check for relevant Next.js core changes\n    steps:\n      - name: Ensure input follows SemVer\n        # Source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string\n        run: |\n          node -e \"\n            const version = process.env.VERSION;\n            const semverRegex = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n            if (!semverRegex.test(version)) {\n              console.error(\\`Error: Version '\\${version}' does not follow SemVer format\\`);\n              process.exit(1);\n            }\n            console.log(\\`Version '\\${version}' is valid SemVer\\`);\n          \"\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install\n      - name: Check for changes in app router\n        run: ./next-release-analyser.ts --version \"${{ env.VERSION }}\"\n        working-directory: packages/scripts\n        env:\n          MAILPACE_API_TOKEN: ${{ secrets.MAILPACE_API_TOKEN }}\n          EMAIL_ADDRESS_TO: ${{ secrets.EMAIL_ADDRESS_TO }}\n          EMAIL_ADDRESS_FROM: ${{ secrets.EMAIL_ADDRESS_FROM }}\n"
  },
  {
    "path": ".github/workflows/ci-cd.yml",
    "content": "name: CI/CD\n\non:\n  push:\n    branches:\n      - master\n      - beta\n      - next\n  pull_request:\n    types: [opened, reopened, synchronize]\n  workflow_dispatch:\n\nenv:\n  FORCE_COLOR: 3 # Diplay chalk colors\n\njobs:\n  lint:\n    name: Linting\n    runs-on: ubuntu-24.04-arm\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --ignore-scripts --frozen-lockfile --workspace-root\n      - name: Check monorepo with Sherif\n        run: pnpm run lint:sherif\n      - name: Check source code formatting\n        run: |\n          set +e # Allow Prettier to fail, but capture the error code\n          output=$(./node_modules/.bin/prettier --list-different ./packages/nuqs 2>&1)\n          exit_code=$?\n          set -e\n          if [ $exit_code -ne 0 ]; then\n            echo \"$output\" | while IFS= read -r file; do\n              echo \"::warning file=$file::Prettier detected formatting issues in $file\"\n            done\n            exit $exit_code\n          else\n            echo \"No formatting issues found\"\n          fi\n\n  publint:\n    name: Package Linting\n    runs-on: ubuntu-24.04-arm\n    needs: [ci-core]\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --ignore-scripts --frozen-lockfile --filter nuqs...\n      - name: Build package\n        run: pnpm build --filter nuqs\n      - name: Run publint on package.json\n        run: pnpm publint packages/nuqs\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: publint\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  ci-scripts:\n    name: CI (scripts)\n    runs-on: ubuntu-24.04-arm\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --ignore-scripts --frozen-lockfile --filter scripts\n      - name: Run tests\n        run: pnpm run test --filter scripts\n\n  ci-core:\n    name: CI (core)\n    runs-on: ubuntu-24.04-arm\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --ignore-scripts --frozen-lockfile --filter nuqs...\n      - name: Install Playwright Chromium\n        run: ./node_modules/.bin/playwright install chromium\n        working-directory: packages/nuqs\n      - name: Run tests\n        run: pnpm run test ${{ github.event_name == 'workflow_dispatch' && '--force' || '' }} --filter nuqs\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n\n  docs:\n    name: Docs Typecheck & Build\n    runs-on: ubuntu-24.04-arm\n    needs: [ci-core]\n    permissions:\n      contents: read\n      actions: read\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --filter docs...\n      - name: Type-check docs\n        run: pnpm run test --filter docs\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n      - name: Build docs\n        run: pnpm run build --filter docs\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: docs\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  e2e-next:\n    # Watch out! When changing the job name,\n    # update the required checks in GitHub\n    # branch protection settings for `next`.\n    name: E2E (next@${{ matrix.next-version }}${{ matrix.base-path && ' ⚾' || ''}}${{ matrix.react-compiler && ' ⚛️' || ''}}${{ matrix.cache-components && ' 💾' || ''}})\n    runs-on: ubuntu-24.04-arm\n    needs: [ci-core]\n    strategy:\n      fail-fast: false\n      matrix:\n        # Watch out! When changing the compat grid,\n        # update the required checks in GitHub\n        # branch protection settings for `next`.\n        base-path: [false]\n        react-compiler: [false]\n        cache-components: [false]\n        next-version:\n          # Only keep versions where there were relevant changes in the app router core,\n          # and the previous one to use as a baseline.\n          - \"14.2.0\"\n          # - '14.2.3' # before vercel/next.js#66755\n          - \"14.2.4\" # after vercel/next.js#66755\n          # - '14.2.7' # before vercel/next.js#69509\n          - \"14.2.8\" # after vercel/next.js#69509\n          - \"15.0.0\"\n          - \"15.1.0\"\n          - \"15.2.0\"\n          - latest\n        include:\n          - next-version: \"14.2.0\"\n            base-path: \"/base\"\n          - next-version: \"15.0.0\"\n            base-path: \"/base\"\n          - next-version: \"latest\"\n            base-path: \"/base\"\n          - next-version: \"latest\"\n            react-compiler: true\n          - next-version: \"latest\"\n            base-path: \"/base\"\n            react-compiler: true\n          - next-version: \"latest\"\n            cache-components: true\n            react-compiler: true\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile ${{ matrix.next-version != 'latest' && '--filter e2e-next...' || '' }}\n      - name: Install Next.js version ${{ matrix.next-version }}\n        if: ${{ matrix.next-version != 'local' }}\n        run: pnpm add --filter e2e-next next@${{ matrix.next-version }}\n      - name: Run cacheComponents codemod\n        if: ${{ matrix.cache-components }}\n        run: pnpm run cacheComponents:codemod\n        working-directory: packages/e2e/next\n      - name: Run integration tests\n        run: pnpm run test ${{ github.event_name == 'workflow_dispatch' && '--force' || '' }} --filter e2e-next\n        env:\n          BASE_PATH: ${{ matrix.base-path && matrix.base-path || '/' }}\n          REACT_COMPILER: ${{ matrix.react-compiler }}\n          CACHE_COMPONENTS: ${{ matrix.cache-components }}\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n      - name: Save Playwright report\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        if: failure()\n        with:\n          path: packages/e2e/next/.playwright/*\n          name: playwright-next-${{ matrix.next-version }}${{ matrix.base-path && '-basePath' || ''}}${{ matrix.react-compiler && '-react-compiler' || ''}}${{ matrix.cache-components && '-cache-components' || ''}}\n          include-hidden-files: true\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: next@${{ matrix.next-version }}${{ matrix.base-path && ' basePath' || ''}}${{ matrix.react-compiler && ' react-compiler' || ''}}${{ matrix.cache-components && '-cache-components' || ''}}\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  e2e-react:\n    name: E2E (react-fpn-${{ matrix.full-page-nav-on-shallow-false }})\n    runs-on: ubuntu-24.04-arm\n    needs: [ci-core]\n    strategy:\n      fail-fast: false\n      matrix:\n        full-page-nav-on-shallow-false: [false, true]\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --filter e2e-react...\n      - name: Run tests\n        run: pnpm run test ${{ github.event_name == 'workflow_dispatch' && '--force' || '' }} --filter e2e-react\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n          FULL_PAGE_NAV_ON_SHALLOW_FALSE: ${{ matrix.full-page-nav-on-shallow-false }}\n      - name: Save Playwright report\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        if: failure()\n        with:\n          path: packages/e2e/react/.playwright/*\n          name: playwright-react-fpn-${{ matrix.full-page-nav-on-shallow-false }}\n          include-hidden-files: true\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: react-fpn-${{ matrix.full-page-nav-on-shallow-false }}\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  e2e-react-router:\n    name: E2E (react-router ${{ matrix.react-router-version }})\n    runs-on: ubuntu-24.04-arm\n    needs: [ci-core]\n    strategy:\n      fail-fast: false\n      matrix:\n        react-router-version:\n          - \"v6\"\n          - \"v7\"\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --filter e2e-react-router-${{ matrix.react-router-version }}...\n      - name: Run tests\n        run: pnpm run test ${{ github.event_name == 'workflow_dispatch' && '--force' || '' }} --filter e2e-react-router-${{ matrix.react-router-version }}\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n      - name: Save Playwright report\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        if: failure()\n        with:\n          path: packages/e2e/react-router/${{ matrix.react-router-version }}/.playwright/*\n          name: playwright-react-router-${{ matrix.react-router-version }}\n          include-hidden-files: true\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: react-router-${{ matrix.react-router-version }}\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  e2e-remix:\n    name: E2E (remix)\n    runs-on: ubuntu-24.04-arm\n    needs: [ci-core]\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --filter e2e-remix...\n      - name: Run tests\n        run: pnpm run test ${{ github.event_name == 'workflow_dispatch' && '--force' || '' }} --filter e2e-remix\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n      - name: Save Playwright report\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        if: failure()\n        with:\n          path: packages/e2e/remix/.playwright/*\n          name: playwright-remix\n          include-hidden-files: true\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: remix\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  e2e-tanstack-router:\n    name: E2E (tanstack-router)\n    runs-on: ubuntu-22.04-arm\n    needs: [ci-core]\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --filter e2e-tanstack-router...\n      - name: Run tests\n        run: pnpm run test ${{ github.event_name == 'workflow_dispatch' && '--force' || '' }} --filter e2e-tanstack-router\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n      - name: Save Playwright report\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        if: failure()\n        with:\n          path: packages/e2e/tanstack-router/.playwright/*\n          name: playwright-tanstack-router\n          include-hidden-files: true\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: tanstack-router\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  ci-notify:\n    name: Notify on Slack\n    runs-on: ubuntu-24.04-arm\n    needs:\n      - docs\n      - lint\n      - publint\n      - ci-scripts\n      - ci-core\n      - e2e-next\n      - e2e-react\n      - e2e-react-router\n      - e2e-remix\n      - e2e-tanstack-router\n    steps:\n      - uses: 47ng/actions-slack-notify@main\n        with:\n          status: ${{ job.status }}\n          jobName: Continuous Integration\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  cd:\n    name: Deployment\n    runs-on: ubuntu-24.04-arm\n    permissions:\n      contents: write # to be able to publish a GitHub release\n      issues: write # to be able to comment on released issues\n      pull-requests: write # to be able to comment on released pull requests\n      id-token: write # to enable use of OIDC for NPM provenance / trusted publishing\n    needs:\n      - docs\n      - lint\n      - publint\n      - ci-scripts\n      - ci-core\n      - e2e-next\n      - e2e-react\n      - e2e-react-router\n      - e2e-remix\n      - e2e-tanstack-router\n    if: ${{ github.ref_name == 'master' || github.ref_name == 'beta' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          # No pnpm cache restore in CD: a poisoned cache could inject\n          # compromised packages into the published build. Fresh install\n          # from the registry ensures integrity (same rationale as\n          # skipping external Turbo cache below).\n      - name: Update npm # Ensure npm 11.5.1 or later is installed for OIDC trusted publishing\n        run: npm install -g npm@latest\n      - name: Install dependencies\n        run: pnpm install --ignore-scripts --frozen-lockfile --filter nuqs...\n      - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies\n        run: npm audit signatures\n      - name: Build package\n        run: pnpm build --filter nuqs\n      - name: Semantic Release\n        run: ../../node_modules/.bin/semantic-release\n        working-directory: packages/nuqs\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Read package version\n        id: package-version\n        run: |\n          VERSION=$(jq -r '.version' package.json)\n          echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n          echo \"Released version: $VERSION\"\n        working-directory: packages/nuqs\n      - name: Invalidate contributors ISR cache in the docs\n        if: ${{ github.event_name == 'push' && github.ref_name == 'master' }}\n        run: |\n          curl -s \"https://nuqs.dev/api/isr?tag=contributors&token=${{ secrets.ISR_TOKEN }}\"\n      - name: Install dependencies\n        if: ${{ github.event_name == 'push' && steps.package-version.outputs.version != '0.0.0-semantically-released' }}\n        run: pnpm install --ignore-scripts --frozen-lockfile --filter scripts\n      - name: Generate release notes\n        id: release-notes\n        if: ${{ github.event_name == 'push' && steps.package-version.outputs.version != '0.0.0-semantically-released' }}\n        run: |\n          NOTES=$(./release-notes-automation.ts)\n          echo \"$NOTES\" >> $GITHUB_STEP_SUMMARY\n          {\n            echo 'notes<<EOF'\n            echo \"$NOTES\"\n            echo 'EOF'\n          } >> $GITHUB_OUTPUT\n        working-directory: packages/scripts\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Update GitHub release notes\n        if: ${{ github.event_name == 'push' && steps.package-version.outputs.version != '0.0.0-semantically-released' }}\n        run: |\n          echo \"${{ steps.release-notes.outputs.notes }}\" | \\\n           gh release edit \"v${{ steps.package-version.outputs.version }}\" \\\n            --notes-file -\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/clear-shipping-next.yml",
    "content": "name: Clear \"Shipping Next\"\n\non:\n  workflow_dispatch:\n\nenv:\n  SHIPPING_NEXT_MILESTONE_ID: 2 # ID for \"Shipping Next\" milestone\n\npermissions:\n  pull-requests: write\n  issues: write\n\njobs:\n  clear_shipping_next:\n    name: Clear \"Shipping Next\" Milestone\n    runs-on: ubuntu-24.04-arm\n\n    steps:\n      - name: Get issues & PRs in \"Shipping Next\" milestone\n        id: get_issues\n        run: |\n          # Fetch issues with titles\n          response=$(gh api -X GET \\\n            repos/${{ github.repository }}/issues \\\n            -f milestone=${{ env.SHIPPING_NEXT_MILESTONE_ID }} \\\n            -f state=all \\\n            -f labels=released)\n\n          # Extract just the numbers for the output\n          issues=$(echo \"$response\" | jq -r '[.[].number] | join(\" \")')\n          echo \"issues=$issues\" >> $GITHUB_OUTPUT\n\n          # Add step summary with titles\n          if [ -n \"$issues\" ]; then\n            echo \"## Issues & PRs to clear from 'Shipping Next' milestone\" >> $GITHUB_STEP_SUMMARY\n            echo \"\" >> $GITHUB_STEP_SUMMARY\n            echo \"$response\" | jq -r '.[] | \"- [#\\(.number)](https://github.com/${{ github.repository }}/issues/\\(.number)) \\(.title)\"' >> $GITHUB_STEP_SUMMARY\n          else\n            echo \"No issues or PRs found in the 'Shipping Next' milestone with the 'released' label.\" >> $GITHUB_STEP_SUMMARY\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Clear milestone from issues & PRs\n        if: steps.get_issues.outputs.issues != ''\n        run: |\n          for issue_number in ${{ steps.get_issues.outputs.issues }}; do\n            gh api -X PATCH \\\n              repos/${{ github.repository }}/issues/$issue_number \\\n              -F milestone=null\n          done\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/milestone-automation.yml",
    "content": "name: Mark as \"Shipping Next\"\n\non:\n  pull_request_target:\n    types: [closed]\n    branches:\n      - next\n\nenv:\n  BACKLOG_MILESTONE_ID: 3 # ID for \"Backlog\" milestone\n  SHIPPING_NEXT_MILESTONE_ID: 2 # ID for \"Shipping Next\" milestone\n\npermissions:\n  pull-requests: write\n  issues: write\n\njobs:\n  update_milestones:\n    name: Update Milestones\n    runs-on: ubuntu-24.04-arm\n    if: github.event.pull_request.merged == true\n\n    steps:\n      - name: Check if PR was in Backlog\n        id: check_milestone\n        run: |\n          milestone_id=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} --jq '.milestone.number')\n          if [[ \"$milestone_id\" == \"${{ env.BACKLOG_MILESTONE_ID }}\" ]]; then\n            echo \"backlog_milestone=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"backlog_milestone=false\" >> $GITHUB_OUTPUT\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Update PR milestone to \"shipping next\"\n        if: steps.check_milestone.outputs.backlog_milestone == 'true'\n        run: |\n          gh api -X PATCH \\\n            repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }} \\\n            -f milestone=${{ env.SHIPPING_NEXT_MILESTONE_ID }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Update linked issues milestone to \"shipping next\"\n        if: steps.check_milestone.outputs.backlog_milestone == 'true'\n        run: |\n          set -euo pipefail # Exit on any error\n\n          # Validate PR number is numeric\n          if ! [[ \"${{ github.event.pull_request.number }}\" =~ ^[0-9]+$ ]]; then\n            echo \"Invalid PR number: ${{ github.event.pull_request.number }}\"\n            exit 1\n          fi\n\n          # Get linked issues using GraphQL API\n          query='query LinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {\n            repository(owner: $owner, name: $repo) {\n              pullRequest(number: $prNumber) {\n                closingIssuesReferences(first: 20) {\n                  nodes {\n                    number\n                    milestone {\n                      number\n                    }\n                  }\n                }\n              }\n            }\n          }'\n\n          # Extract owner and repo from repository\n          owner=$(echo \"${{ github.repository }}\" | cut -d'/' -f1)\n          repo=$(echo \"${{ github.repository }}\" | cut -d'/' -f2)\n\n          # Execute GraphQL query with error handling\n          echo \"Fetching linked issues for PR #${{ github.event.pull_request.number }}\"\n\n          if ! response=$(gh api graphql \\\n            -f query=\"$query\" \\\n            -f owner=\"$owner\" \\\n            -f repo=\"$repo\" \\\n            -F prNumber=\"${{ github.event.pull_request.number }}\" 2>&1); then\n            echo \"Failed to fetch linked issues: $response\"\n            exit 1\n          fi\n\n          # Extract issue numbers, keep only those with milestone \"Backlog\" (number 3)\n          linked_issues=$(echo \"$response\" | jq -r '\n            .data.repository.pullRequest.closingIssuesReferences.nodes[]\n            | select(.milestone != null and .milestone.number == ${{ env.BACKLOG_MILESTONE_ID }})\n            | .number\n          ')\n\n          # Check if there are any issues to update\n          if [[ -z \"$linked_issues\" ]]; then\n            echo \"No linked issues found or all issues already have the correct milestone\"\n            exit 0\n          fi\n\n          echo \"Found linked issues to update: $(echo \"$linked_issues\" | tr '\\n' ' ')\"\n\n          # Update milestone for each linked issue\n          failed_updates=0\n          updated_count=0\n\n          while IFS= read -r issue_number; do\n            [[ -z \"$issue_number\" ]] && continue\n\n            echo \"Updating milestone for issue #$issue_number\"\n            if gh api -X PATCH \\\n              \"repos/${{ github.repository }}/issues/$issue_number\" \\\n              -f milestone=${{ env.SHIPPING_NEXT_MILESTONE_ID }} \\\n              --silent; then\n              echo \"✓ Successfully updated milestone for issue #$issue_number\"\n              updated_count=$((updated_count + 1))\n            else\n              echo \"✗ Failed to update milestone for issue #$issue_number\"\n              failed_updates=$((failed_updates + 1))\n            fi\n          done <<< \"$linked_issues\"\n\n          echo \"Summary: Updated $updated_count issue(s), $failed_updates failure(s)\"\n\n          # Fail the step if any updates failed\n          if [[ $failed_updates -gt 0 ]]; then\n            echo \"Failed to update $failed_updates issue(s)\"\n            exit 1\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/pkg.pr.new.yml",
    "content": "name: PR Preview\non:\n  pull_request:\n    types: [opened, synchronize, labeled]\n    paths:\n      - 'packages/nuqs/**'\n\nconcurrency:\n  group: pkg-pr-new-${{ github.event.pull_request.number }}\n  cancel-in-progress: false\n\njobs:\n  deploy-preview:\n    name: Deploy to pkg.pr.new\n    if: |\n      contains(github.event.pull_request.author_association, 'MEMBER') ||\n      contains(github.event.pull_request.labels.*.name, 'deploy:preview')\n    runs-on: ubuntu-24.04-arm\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install\n      - name: Build package\n        run: pnpm build --filter nuqs\n      - name: Set package version\n        run: |\n          pnpm pkg set version=0.0.0-preview.${{ github.event.pull_request.head.sha }}\n          echo \"::notice title=Install (PR)::pnpm add https://pkg.pr.new/nuqs@${{ github.event.pull_request.number }}\"\n          echo \"::notice title=Install (SHA)::pnpm add https://pkg.pr.new/nuqs@${{ github.event.pull_request.head.sha }}\"\n          echo \"::notice title=Version::0.0.0-preview.${{ github.event.pull_request.head.sha }}\"\n        working-directory: packages/nuqs\n      - name: Publish to pkg.pr.new\n        run: pnpx pkg-pr-new publish --compact './packages/nuqs' --no-template --packageManager=pnpm --pnpm\n"
  },
  {
    "path": ".github/workflows/pr-base-enforcement.yml",
    "content": "name: Prevent PRs targetting master\n\non:\n  pull_request:\n    types: [opened, edited]\n    branches:\n      - master\n\njobs:\n  prevent-pr-targetting-master:\n    runs-on: ubuntu-24.04-arm\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - name: Post comment to update base branch\n        run: gh pr comment ${{ github.event.pull_request.number }} --body \"⚠️ Pull requests targetting the \\`master\\` branch are not allowed. Please update the base branch to \\`next\\` before reopening.\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Close PR\n        run: gh pr close ${{ github.event.pull_request.number }} --repo ${{ github.repository }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/pr-lint.yml",
    "content": "name: Lint PR Title\n\non:\n  pull_request:\n    types:\n      - opened\n      - edited\n      - synchronize\n\njobs:\n  lint-pr-title:\n    name: Lint PR Title\n    runs-on: ubuntu-24.04-arm\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          fetch-depth: 0\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm add -D @commitlint/load @commitlint/lint @commitlint/parse\n      - name: Lint PR title\n        env:\n          TITLE: ${{ github.event.pull_request.title }}\n        run: |\n          node -e \"\n            import loadConfig from '@commitlint/load';\n            import lint from '@commitlint/lint';\n            import pkgJson from './package.json' with { type: 'json' };\n            const config = await loadConfig(pkgJson.commitlint);\n            const result = await lint(process.env.TITLE, config.rules, config.parserPreset ? { parserOpts: config.parserPreset.parserOpts } : {});\n            process.env.GITHUB_STEP_SUMMARY += \\`## Linting Result\\n- Valid: \\${result.valid}\\n\\`;\n            if (result.valid === false) {\n              for (const { message } of result.errors) {\n                process.env.GITHUB_STEP_SUMMARY += \\`- Error: \\${message}\\n\\`;\n                console.error(message);\n              }\n              process.exit(1);\n            }\n          \"\n      - name: Get changed files\n        id: changed-files\n        run: echo \"files=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | tr '\\n' ' ')\" >> \"$GITHUB_OUTPUT\"\n      - name: Check version-bumping commits target core package\n        env:\n          TITLE: ${{ github.event.pull_request.title }}\n          CHANGED_FILES: ${{ steps.changed-files.outputs.files }}\n        run: |\n          node -e \"\n            import parse from '@commitlint/parse';\n            import { appendFile } from 'node:fs/promises';\n\n            // Commit types that trigger version bumps (semantic-release defaults)\n            const VERSION_BUMPING_TYPES = ['feat', 'fix', 'perf', 'revert'];\n            // Non-bumping types for reference in error messages\n            const NON_BUMPING_TYPES = ['build', 'chore', 'ci', 'clean', 'doc', 'ref', 'style', 'test'];\n\n            const parsed = await parse(process.env.TITLE);\n            const commitType = parsed.type;\n\n            if (!VERSION_BUMPING_TYPES.includes(commitType)) {\n              console.log(\\`Commit type '\\${commitType}' does not trigger a version bump. Skipping core package check.\\`);\n              process.exit(0);\n            }\n\n            console.log(\\`Commit type '\\${commitType}' triggers a version bump. Checking for changes in packages/nuqs...\\`);\n\n            const changedFiles = process.env.CHANGED_FILES.trim().split(' ').filter(Boolean);\n            const hasCoreChanges = changedFiles.some(file => file.startsWith('packages/nuqs/'));\n\n            if (!hasCoreChanges) {\n              const summary = \\`## ❌ Version Bump Check Failed\n\n            Your PR title uses the commit type **\\\\\\`\\${commitType}\\\\\\`** which triggers a version bump, but no changes were found in the core package (\\\\\\`packages/nuqs\\\\\\`).\n\n            ### What to do:\n\n            If this PR does not include changes to the core \\\\\\`nuqs\\\\\\` package, please use a non-bumping commit type instead:\n\n            | Type | Use for |\n            |------|---------|\n            | \\\\\\`doc\\\\\\` | Documentation updates |\n            | \\\\\\`chore\\\\\\` | Maintenance, CI/CD, dependencies |\n            | \\\\\\`test\\\\\\` | Test additions or modifications |\n            | \\\\\\`ci\\\\\\` | CI configuration changes |\n            | \\\\\\`build\\\\\\` | Build system changes |\n            | \\\\\\`style\\\\\\` | Code style/formatting |\n            | \\\\\\`ref\\\\\\` | Refactoring (non-core) |\n\n            ### Version-bumping types (require core package changes):\n            - \\\\\\`feat\\\\\\` → minor version bump\n            - \\\\\\`fix\\\\\\` → patch version bump\n            - \\\\\\`perf\\\\\\` → patch version bump\n            - \\\\\\`revert\\\\\\` → depends on reverted commit\n            \\`;\n\n              await appendFile(process.env.GITHUB_STEP_SUMMARY, summary);\n              console.error(\\`Error: PR title uses version-bumping type '\\${commitType}' but contains no changes in packages/nuqs\\`);\n              console.error(\\`Changed files: \\${changedFiles.join(', ')}\\`);\n              console.error(\\`\\nPlease use a non-bumping commit type: \\${NON_BUMPING_TYPES.join(', ')}\\`);\n              process.exit(1);\n            }\n\n            console.log('✓ Version-bumping commit type is valid: core package has changes.');\n            await appendFile(process.env.GITHUB_STEP_SUMMARY, \\`## ✅ Version Bump Check Passed\\n\\nCommit type \\\\\\`\\${commitType}\\\\\\` is valid because the PR includes changes to \\\\\\`packages/nuqs\\\\\\`.\\n\\`);\n          \"\n"
  },
  {
    "path": ".github/workflows/test-against-nextjs-release.yml",
    "content": "name: \"Test against Next.js release\"\nrun-name: \"Test against Next.js ${{ inputs.version }}\"\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Next.js version to test against\"\n        required: true\n        type: string\n\nenv:\n  FORCE_COLOR: 3 # Diplay chalk colors\n  VERSION: ${{ inputs.version }}\n\njobs:\n  test_against_nextjs_release:\n    name: CI (next@${{ inputs.version }}${{ (matrix.base-path || matrix.cache-components || matrix.react-compiler) && ' ' || ''}}${{ matrix.base-path && '⚾' || ''}}${{ matrix.react-compiler && '⚛️' || ''}}${{ matrix.cache-components && '💾' || ''}})\n    runs-on: ubuntu-24.04-arm\n    strategy:\n      fail-fast: false\n      matrix:\n        base-path: [false, \"/base\"]\n        react-compiler: [true, false]\n        cache-components: ${{ startsWith(inputs.version, '16.') && fromJson('[true, false]') || fromJson('[false]') }}\n    steps:\n      - name: Ensure input follows SemVer\n        # Source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string\n        run: |\n          node -e \"\n            const version = process.env.VERSION;\n            const semverRegex = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n            if (!semverRegex.test(version)) {\n              console.error(\\`Error: Version '\\${version}' does not follow SemVer format\\`);\n              process.exit(1);\n            }\n            console.log(\\`Version '\\${version}' is valid SemVer\\`);\n          \"\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install\n      - name: Install Next.js version ${{ env.VERSION }}\n        run: pnpm add --filter e2e-next --filter nuqs \"next@${{ env.VERSION }}\"\n      - name: Run cacheComponents codemod\n        if: ${{ matrix.cache-components }}\n        run: pnpm run cacheComponents:codemod\n        working-directory: packages/e2e/next\n      - name: Run integration tests\n        run: pnpm run test --filter e2e-next\n        env:\n          BASE_PATH: ${{ matrix.base-path && matrix.base-path || '/' }}\n          REACT_COMPILER: ${{ matrix.react-compiler }}\n          CACHE_COMPONENTS: ${{ matrix.cache-components }}\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          E2E_NO_CACHE_ON_RERUN: ${{ github.run_attempt }}\n      - name: Save Playwright report\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        if: failure()\n        with:\n          path: packages/e2e/next/.playwright/*\n          name: playwright-next-${{ env.VERSION }}${{ matrix.base-path && '-basePath' || ''}}${{ matrix.react-compiler && '-react-compiler' || ''}}${{ matrix.cache-components && '-cache-components' || ''}}\n          include-hidden-files: true\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: next@${{ env.VERSION }}${{ matrix.base-path && ' basePath' || ''}}${{ matrix.react-compiler && ' react-compiler' || ''}}${{ matrix.cache-components && '-cache-components' || ''}}\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  docs:\n    name: Docs (next@${{ inputs.version }})\n    if: ${{ startsWith(inputs.version, '16.') }}\n    runs-on: ubuntu-24.04-arm\n    permissions:\n      contents: read\n      actions: read\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238\n        with:\n          node-version-file: .node-version\n          cache: pnpm\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile --filter docs...\n      - name: Install Next.js version ${{ env.VERSION }}\n        run: pnpm add --filter docs --filter nuqs \"next@${{ env.VERSION }}\"\n      - name: Type-check docs\n        run: pnpm run test --filter docs\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n      - name: Build docs\n        run: pnpm run build --filter docs\n        env:\n          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - uses: 47ng/actions-slack-notify@main\n        name: Notify on Slack\n        if: failure()\n        with:\n          status: ${{ job.status }}\n          jobName: docs (next@${{ env.VERSION }})\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n\n  invalidate-isr-cache:\n    name: Invalidate ISR cache\n    runs-on: ubuntu-24.04-arm\n    needs:\n      - test_against_nextjs_release\n      - docs\n    if: ${{ always() }}\n    steps:\n      - name: Invalidate ISR cache for GitHub Actions status on landing page\n        run: curl -s \"https://nuqs.dev/api/isr?tag=github-actions-status&token=${{ secrets.ISR_TOKEN }}\"\n\n  notify:\n    name: Notify on Slack\n    runs-on: ubuntu-24.04-arm\n    needs:\n      - test_against_nextjs_release\n      - docs\n    if: ${{ needs.test_against_nextjs_release.result == 'success' && (needs.docs.result == 'success' || needs.docs.result == 'skipped') }}\n    steps:\n      - uses: 47ng/actions-slack-notify@main\n        with:\n          status: ${{ job.status }}\n          jobName: \"${{ env.VERSION }}\"\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\ndist/\ncoverage/\n.env\nyarn-error.log\n# Docker containers with persistance\n.volumes/\npackage-lock.json\n.next/\n.turbo/\n.vercel\n*.tsbuildinfo\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "pnpm commitlint --edit $1\n"
  },
  {
    "path": ".node-version",
    "content": "24.11.0\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.tabSize\": 2,\n  \"editor.insertSpaces\": true,\n  \"githubPullRequests.ignoredPullRequestBranches\": [\"next\", \"master\"],\n  \"githubPullRequests.queries\": [\n    {\n      \"label\": \"Backlog\",\n      \"query\": \"repo:47ng/nuqs is:pr is:open milestone:\\\"🪵 Backlog\\\"\"\n    },\n    {\n      \"label\": \"Docs\",\n      \"query\": \"repo:47ng/nuqs is:pr is:open label:documentation draft:false\"\n    },\n    {\n      \"label\": \"All reviewable\",\n      \"query\": \"repo:47ng/nuqs is:pr is:open draft:false\",\n      \"groupBy\": [\"milestone\"]\n    }\n  ],\n  \"githubIssues.queries\": [\n    {\n      \"label\": \"Backlog\",\n      \"query\": \"repo:47ng/nuqs is:issue is:open milestone:\\\"🪵 Backlog\\\"\"\n    },\n    {\n      \"label\": \"Bugs\",\n      \"query\": \"repo:47ng/nuqs is:issue is:open label:bug\"\n    },\n    {\n      \"label\": \"Open Issues\",\n      \"query\": \"repo:47ng/nuqs is:issue is:open -label:internal\",\n      \"groupBy\": [\"milestone\"]\n    },\n    {\n      \"label\": \"Internal\",\n      \"query\": \"repo:47ng/nuqs is:issue label:internal\"\n    }\n  ],\n  \"typescript.preferences.autoImportSpecifierExcludeRegexes\": [\n    \"^node:test$\" // We use Vitest\n  ]\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# AGENTS GUIDE\n\nOperational instructions for autonomous coding / AI agents contributing to the nuqs repository.\n\n**nuqs** is a library for type-safe URL query string ↔ React state synchronization with minimal bundle size and zero dependencies.\n\nRefer to: [README.md](README.md) & [CONTRIBUTING.md](CONTRIBUTING.md) for authoritative detail.\n\n---\n\n## Essential Context\n\n### Repository Structure (Monorepo)\n\n- **Library source:** `packages/nuqs`\n- **Documentation app** (Next.js + Fumadocs): `packages/docs`\n  - MDX content: `packages/docs/content`\n- **End-to-end test benches:** `packages/e2e`\n  - Framework targets: Next.js app/pages, React SPA, Remix, TanStack Router, React Router v6/v7\n- **Examples:** `packages/examples/*`\n\n### Core Concepts (nuqs)\n\n- **Goal:** Type-safe URL query string ↔ React state sync.\n- **Main Hooks:**\n  - `useQueryState(key, parserOrConfig)`\n  - `useQueryStates(configObject, options)`\n- **Parsers:** Provide `parse` & `serialize`; enhanced with `.withDefault()` & `.withOptions()`\n- **Batching & Throttling:** Multiple state updates in one tick are merged; URL updates throttled (≥50ms)\n- **Key Principles:**\n  1. URL = single source of truth\n  2. Serialization must be lossless & pure\n  3. Defaults are internal (not written to URL)\n  4. Invalid parse → return `null`\n\n### Configuration\n\n- **Package manager:** `pnpm`\n- **Build:** `pnpm build`\n- **Test suite:** `pnpm test` (5-10 minutes; includes build + unit + typing + e2e)\n- **Development:** `pnpm dev --filter <package-name>...` (triple dots start dependencies' dev script too)\n\n---\n\n## Development Guidelines\n\nFor detailed development guidelines organized by task, see:\n\n- **[Adapter Development](.agents/docs/adapter-development.md)** — Adding framework adapters\n- **[Parser Implementation](.agents/docs/parser-implementation.md)** — Creating custom parsers\n- **[API Design & Architecture](.agents/docs/api-design.md)** — Design principles, extensibility, type safety\n- **[Testing Patterns](.agents/docs/testing.md)** — Unit, type-level, and e2e testing strategies\n- **[Release & Git Workflow](.agents/docs/git-workflow.md)** — Conventional commits, semantic versioning, PR standards\n- **[Quality Standards](.agents/docs/quality-standards.md)** — Checklists, performance, security, anti-patterns\n\n---\n\n## Quick Reference: Common Tasks\n\n| Task                    | Guide                                                                             |\n| ----------------------- | --------------------------------------------------------------------------------- |\n| Fix a bug               | See [Testing Patterns](.agents/docs/testing.md) → Regression                      |\n| Add a new parser        | See [Parser Implementation](.agents/docs/parser-implementation.md)                |\n| Add a framework adapter | See [Adapter Development](.agents/docs/adapter-development.md)                    |\n| Improve performance     | See [API Design](.agents/docs/api-design.md) → Performance & Reliability          |\n| Update documentation    | See [Release & Git Workflow](.agents/docs/git-workflow.md) → Documentation        |\n| Prepare a pull request  | See [Release & Git Workflow](.agents/docs/git-workflow.md) → PR Quality Checklist |\n\n---\n\n## Debugging\n\nEnable debug logs in the browser console:\n\n```js\nlocalStorage.setItem('debug', 'nuqs')\n```\n\nIn server or Node environments (e.g. when using `nuqs/server`), set the `DEBUG` environment variable so it contains `nuqs`:\n\n```bash\nDEBUG=nuqs pnpm dev\n```\n\nLog lines are prefixed with `[nuq+]`\n\nEncourage debug logs in issue reports and include them in reproduction scripts.\n\n---\n\n## Exit Conditions for Agent Tasks\n\nA task is **DONE** when:\n\n- All checklist items satisfied\n- Tests pass locally (`pnpm test`)\n- Docs consistent with behavior\n- No unresolved TODOs introduced\n- No stray console logs (except controlled debug support)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\ncode-of-conduct@47ng.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution Guidelines\n\nFirst off, thanks for your help! 🙏\n\n## Getting started\n\n1. Fork and clone the repository\n2. Install dependencies with `pnpm install`\n3. Start the development environment with `pnpm dev`\n\n## Project structure\n\nThis monorepo contains:\n\n- The source code for the `nuqs` NPM package, in [`packages/nuqs`](./packages/nuqs).\n- A Next.js app under [`packages/docs`](./packages/docs) that serves the documentation and as a playground deployed at <https://nuqs.dev>\n- Test benches for [end-to-end tests](./packages/e2e) for each supported framework, driven by Playwright\n- Examples of integration with other tools.\n\nWhen running `next dev`, this will:\n\n- Build the library and watch for changes using [`tsup`](https://tsup.egoist.dev/)\n- Start the docs app, which will be available at <http://localhost:3000>.\n- Start the end-to-end test benches:\n  - http://localhost:3001 - [Next.js](./packages/e2e/next)\n  - http://localhost:3002 - [React SPA](./packages/e2e/react)\n  - http://localhost:3003 - [Remix](./packages/e2e/remix)\n  - http://localhost:3004 - [TanStack Router](./packages/e2e/tanstack-router)\n  - http://localhost:3005 - [React Router v5](./packages/e2e/react-router/v5)\n  - http://localhost:3006 - [React Router v6](./packages/e2e/react-router/v6)\n  - http://localhost:3007 - [React Router v7](./packages/e2e/react-router/v7)\n- Start the examples:\n  - http://localhost:4000 - [tRPC](./packages/examples/trpc)\n  - http://localhost:4001 - [Next.js - App router](./packages/examples/next-app)\n\n## Testing\n\nYou can run the complete integration test suite with `pnpm test` from the root of the repository.\n\nIt will build the library, run unit tests and typing tests against it, and then\nrun the end-to-end tests against the test bench apps (which uses the built library).\n\nWhen proposing changes or fixing a bug, adding tests (unit or in the\nappropriate e2e test environment) can help tremendously to validate and\nunderstand the changes.\n\n## Opening issues\n\nPlease follow the [issue template](.github/ISSUE_TEMPLATE/bug_report.md) when opening a new issue.\n\nA minimal reproduction example is very helpful to understand the issue and\ninspect it locally.\n\n## Proposing changes\n\nMake sure your changes:\n\n1. Pass the tests: `pnpm test`\n2. Pass linting checks: `pnpm lint`\n3. Have relevant documentation additions / updates (in the `packages/docs/content` and the README.md file).\n\nThis repository uses [`semantic-release`](https://semantic-release.gitbook.io/semantic-release/)\nto automatically publish new versions of the package to NPM.\nTo do this, the Git history follows the\n[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format.\n\nPull requests should target the `next` branch.\n\nIf your changes impact the `nuqs` package, you'll get a comment from [pkg.pr.new](https://pkg.pr.new)\nwith a preview deployment of the package you can install in your application.\n\nIf you are proposing a bug fix, pushing a failing test first (with a note in the\nPR description) is very helpful in showcasing the issue and validating the fix in\na follow-up commit (test-driven development).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 François Best <contact@francoisbest.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# nuqs\n\n[![NPM](https://img.shields.io/npm/v/nuqs?color=red)](https://www.npmjs.com/package/nuqs)\n[![MIT License](https://img.shields.io/github/license/47ng/nuqs.svg?color=blue)](https://github.com/47ng/nuqs/blob/next/LICENSE)\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/franky47?color=%23db61a2&label=Sponsors)](https://github.com/sponsors/franky47)\n[![CI/CD](https://github.com/47ng/nuqs/actions/workflows/ci-cd.yml/badge.svg?branch=next)](https://github.com/47ng/nuqs/actions/workflows/ci-cd.yml)\n[![Depfu](https://badges.depfu.com/badges/acad53fa2b09b1e435a19d6d18f29af4/count.svg)](https://depfu.com/github/47ng/nuqs?project_id=22104)\n\nType-safe search params state manager for React frameworks. Like `useState`, but stored in the URL query string.\n\n## Features\n\n- 🔀 **new:** Supports Next.js (`app` and `pages` routers), plain React (SPA), Remix, React Router, TanStack Router, and custom routers via [adapters](#adapters)\n- 🧘‍♀️ Simple: the URL is the source of truth\n- 🕰 Replace history or [append](#history) to use the Back button to navigate state updates\n- ⚡️ Built-in [parsers](#parsing) for common state types (integer, float, boolean, Date, and more). Create your own parsers for custom types & pretty URLs\n- ♊️ Related querystrings with [`useQueryStates`](#usequerystates)\n- 📡 [Shallow mode](#shallow) by default for URL query updates, opt-in to notify server components\n- 🗃 [Server cache](#accessing-searchparams-in-server-components) for type-safe searchParams access in nested server components\n- ⌛️ Support for [`useTransition`](#transitions) to get loading states on server updates\n\n## Documentation\n\nRead the complete documentation at [nuqs.dev](https://nuqs.dev).\n\n## Installation\n\n```shell\npnpm add nuqs\n```\n\n```shell\nyarn add nuqs\n```\n\n```shell\nnpm install nuqs\n```\n\n## Adapters\n\nYou will need to wrap your React component tree with an adapter for your framework. _(expand the appropriate section below)_\n\n<details><summary>▲ Next.js (app router)</summary>\n\n> Supported Next.js versions: `>=14.2.0`. For older versions, install `nuqs@^1` (which doesn't need this adapter code).\n\n```tsx\n// src/app/layout.tsx\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport { type ReactNode } from 'react'\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n  return (\n    <html>\n      <body>\n        <NuqsAdapter>{children}</NuqsAdapter>\n      </body>\n    </html>\n  )\n}\n```\n\n</details>\n\n<details><summary>▲ Next.js (pages router)</summary>\n\n> Supported Next.js versions: `>=14.2.0`. For older versions, install `nuqs@^1` (which doesn't need this adapter code).\n\n```tsx\n// src/pages/_app.tsx\nimport type { AppProps } from 'next/app'\nimport { NuqsAdapter } from 'nuqs/adapters/next/pages'\n\nexport default function MyApp({ Component, pageProps }: AppProps) {\n  return (\n    <NuqsAdapter>\n      <Component {...pageProps} />\n    </NuqsAdapter>\n  )\n}\n```\n\n</details>\n\n<details><summary>⚛️ Plain React (SPA)</summary>\n\nExample: via Vite or create-react-app.\n\n```tsx\nimport { NuqsAdapter } from 'nuqs/adapters/react'\n\ncreateRoot(document.getElementById('root')!).render(\n  <NuqsAdapter>\n    <App />\n  </NuqsAdapter>\n)\n```\n\n</details>\n\n<details><summary>💿 Remix</summary>\n\n> Supported Remix versions: `@remix-run/react@>=2`\n\n```tsx\n// app/root.tsx\nimport { NuqsAdapter } from 'nuqs/adapters/remix'\n\n// ...\n\nexport default function App() {\n  return (\n    <NuqsAdapter>\n      <Outlet />\n    </NuqsAdapter>\n  )\n}\n```\n\n</details>\n\n<details><summary><span style=\"width:16px;height:16px;background:#fff;border-radius:2px;\"><img width=\"16px\" height=\"16px\" src=\"https://reactrouter.com/_brand/React%20Router%20Brand%20Assets/React%20Router%20Logo/Light.svg\" /></span> React Router v6\n</summary>\n\n> Supported React Router versions: `react-router-dom@^6`\n\n```tsx\nimport { NuqsAdapter } from 'nuqs/adapters/react-router/v6'\nimport { createBrowserRouter, RouterProvider } from 'react-router-dom'\nimport App from './App'\n\nconst router = createBrowserRouter([\n  {\n    path: '/',\n    element: <App />\n  }\n])\n\nexport function ReactRouter() {\n  return (\n    <NuqsAdapter>\n      <RouterProvider router={router} />\n    </NuqsAdapter>\n  )\n}\n```\n\n</details>\n\n<details><summary><span style=\"width:16px;height:16px;background:#fff;border-radius:2px;\"><img width=\"16px\" height=\"16px\" src=\"https://reactrouter.com/_brand/React%20Router%20Brand%20Assets/React%20Router%20Logo/Light.svg\" /></span> React Router v7\n</summary>\n\n> Supported React Router versions: `react-router@^7`\n\n```tsx\n// app/root.tsx\nimport { NuqsAdapter } from 'nuqs/adapters/react-router/v7'\nimport { Outlet } from 'react-router'\n\n// ...\n\nexport default function App() {\n  return (\n    <NuqsAdapter>\n      <Outlet />\n    </NuqsAdapter>\n  )\n}\n```\n\n</details>\n\n<details><summary>🏝️ TanStack Router</summary>\n\n> Supported TanStack Router versions: `@tanstack/react-router@^1`\n> Note: TanStack Router support is experimental and does not yet cover TanStack Start.\n\n```tsx\n// src/routes/__root.tsx\nimport { NuqsAdapter } from 'nuqs/adapters/tanstack-router'\nimport { Outlet, createRootRoute } from '@tanstack/react-router'\n\nexport const Route = createRootRoute({\n  component: () => (\n    <>\n      <NuqsAdapter>\n        <Outlet />\n      </NuqsAdapter>\n    </>\n  )\n})\n```\n\n</details>\n\n## Usage\n\n```tsx\n'use client' // Only works in client components\n\nimport { useQueryState } from 'nuqs'\n\nexport default () => {\n  const [name, setName] = useQueryState('name')\n  return (\n    <>\n      <h1>Hello, {name || 'anonymous visitor'}!</h1>\n      <input value={name || ''} onChange={e => setName(e.target.value)} />\n      <button onClick={() => setName(null)}>Clear</button>\n    </>\n  )\n}\n```\n\n![](https://raw.githubusercontent.com/47ng/nuqs/next/useQueryState.gif)\n\n`useQueryState` takes one required argument: the key to use in the query string.\n\nLike `React.useState`, it returns an array with the value present in the query\nstring as a string (or `null` if none was found), and a state updater function.\n\nExample outputs for our hello world example:\n\n| URL          | name value | Notes                                                             |\n| ------------ | ---------- | ----------------------------------------------------------------- |\n| `/`          | `null`     | No `name` key in URL                                              |\n| `/?name=`    | `''`       | Empty string                                                      |\n| `/?name=foo` | `'foo'`    |\n| `/?name=2`   | `'2'`      | Always returns a string by default, see [Parsing](#parsing) below |\n\n## Parsing\n\nIf your state type is not a string, you must pass a parsing function in the\nsecond argument object.\n\nWe provide parsers for common and more advanced object types:\n\n```ts\nimport {\n  parseAsString,\n  parseAsInteger,\n  parseAsFloat,\n  parseAsBoolean,\n  parseAsTimestamp,\n  parseAsIsoDateTime,\n  parseAsArrayOf,\n  parseAsJson,\n  parseAsStringEnum,\n  parseAsStringLiteral,\n  parseAsNumberLiteral\n} from 'nuqs'\n\nuseQueryState('tag') // defaults to string\nuseQueryState('count', parseAsInteger)\nuseQueryState('brightness', parseAsFloat)\nuseQueryState('darkMode', parseAsBoolean)\nuseQueryState('after', parseAsTimestamp) // state is a Date\nuseQueryState('date', parseAsIsoDateTime) // state is a Date\nuseQueryState('array', parseAsArrayOf(parseAsInteger)) // state is number[]\nuseQueryState('json', parseAsJson<Point>()) // state is a Point\n\n// Enums (string-based only)\nenum Direction {\n  up = 'UP',\n  down = 'DOWN',\n  left = 'LEFT',\n  right = 'RIGHT'\n}\n\nconst [direction, setDirection] = useQueryState(\n  'direction',\n  parseAsStringEnum<Direction>(Object.values(Direction)) // pass a list of allowed values\n    .withDefault(Direction.up)\n)\n\n// Literals (string-based only)\nconst colors = ['red', 'green', 'blue'] as const\n\nconst [color, setColor] = useQueryState(\n  'color',\n  parseAsStringLiteral(colors) // pass a readonly list of allowed values\n    .withDefault('red')\n)\n\n// Literals (number-based only)\nconst diceSides = [1, 2, 3, 4, 5, 6] as const\n\nconst [side, setSide] = useQueryState(\n  'side',\n  parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values\n    .withDefault(4)\n)\n```\n\nYou may pass a custom set of `parse` and `serialize` functions:\n\n```tsx\nimport { useQueryState } from 'nuqs'\n\nexport default () => {\n  const [hex, setHex] = useQueryState('hex', {\n    // TypeScript will automatically infer it's a number\n    // based on what `parse` returns.\n    parse: (query: string) => parseInt(query, 16),\n    serialize: value => value.toString(16)\n  })\n}\n```\n\n## Default value\n\nWhen the query string is not present in the URL, the default behaviour is to\nreturn `null` as state.\n\nIt can make state updating and UI rendering tedious. Take this example of a simple counter stored in the URL:\n\n```tsx\nimport { useQueryState, parseAsInteger } from 'nuqs'\n\nexport default () => {\n  const [count, setCount] = useQueryState('count', parseAsInteger)\n  return (\n    <>\n      <pre>count: {count}</pre>\n      <button onClick={() => setCount(0)}>Reset</button>\n      {/* handling null values in setCount is annoying: */}\n      <button onClick={() => setCount(c => c ?? 0 + 1)}>+</button>\n      <button onClick={() => setCount(c => c ?? 0 - 1)}>-</button>\n      <button onClick={() => setCount(null)}>Clear</button>\n    </>\n  )\n}\n```\n\nYou can specify a default value to be returned in this case:\n\n```ts\nconst [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))\n\nconst increment = () => setCount(c => c + 1) // c will never be null\nconst decrement = () => setCount(c => c - 1) // c will never be null\nconst clearCount = () => setCount(null) // Remove query from the URL\n```\n\nNote: the default value is internal to React, it will **not** be written to the\nURL.\n\nSetting the state to `null` will remove the key in the query string and set the\nstate to the default value.\n\n## Options\n\n### History\n\nBy default, state updates are done by replacing the current history entry with\nthe updated query when state changes.\n\nYou can see this as a sort of `git squash`, where all state-changing\noperations are merged into a single history value.\n\nYou can also opt-in to push a new history item for each state change,\nper key, which will let you use the Back button to navigate state\nupdates:\n\n```ts\n// Default: replace current history with new state\nuseQueryState('foo', { history: 'replace' })\n\n// Append state changes to history:\nuseQueryState('foo', { history: 'push' })\n```\n\nAny other value for the `history` option will fallback to the default.\n\nYou can also override the history mode when calling the state updater function:\n\n```ts\nconst [query, setQuery] = useQueryState('q', { history: 'push' })\n\n// This overrides the hook declaration setting:\nsetQuery(null, { history: 'replace' })\n```\n\n### Shallow\n\n> Note: this feature only applies to Next.js\n\nBy default, query state updates are done in a _client-first_ manner: there are\nno network calls to the server.\n\nThis is equivalent to the `shallow` option of the Next.js pages router set to `true`,\nor going through the experimental [`windowHistorySupport`](https://github.com/vercel/next.js/discussions/48110)\nflag in the app router.\n\nTo opt-in to query updates notifying the server (to re-run `getServerSideProps`\nin the pages router and re-render Server Components on the app router),\nyou can set `shallow` to `false`:\n\n```ts\nconst [state, setState] = useQueryState('foo', { shallow: false })\n\n// You can also pass the option on calls to setState:\nsetState('bar', { shallow: false })\n```\n\n### Throttling URL updates\n\nBecause of browsers rate-limiting the History API, internal updates to the\nURL are queued and throttled to a default of 50ms, which seems to satisfy\nmost browsers even when sending high-frequency query updates, like binding\nto a text input or a slider.\n\nSafari's rate limits are much stricter and would require a throttle of around 340ms.\nIf you end up needing a longer time between updates, you can specify it in the\noptions:\n\n```ts\nuseQueryState('foo', {\n  // Send updates to the server maximum once every second\n  shallow: false,\n  throttleMs: 1000\n})\n\n// You can also pass the option on calls to setState:\nsetState('bar', { throttleMs: 1000 })\n```\n\n> Note: the state returned by the hook is always updated instantly, to keep UI responsive.\n> Only changes to the URL, and server requests when using `shallow: false`, are throttled.\n\nIf multiple hooks set different throttle values on the same event loop tick,\nthe highest value will be used. Also, values lower than 50ms will be ignored,\nto avoid rate-limiting issues. [Read more](https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs#batching--throttling).\n\n### Transitions\n\nWhen combined with `shallow: false`, you can use the `useTransition` hook to get\nloading states while the server is re-rendering server components with the\nupdated URL.\n\nPass in the `startTransition` function from `useTransition` to the options\nto enable this behaviour:\n\n```tsx\n'use client'\n\nimport React from 'react'\nimport { useQueryState, parseAsString } from 'nuqs'\n\nfunction ClientComponent({ data }) {\n  // 1. Provide your own useTransition hook:\n  const [isLoading, startTransition] = React.useTransition()\n  const [query, setQuery] = useQueryState(\n    'query',\n    // 2. Pass the `startTransition` as an option:\n    parseAsString.withOptions({\n      startTransition,\n      shallow: false // opt-in to notify the server (Next.js only)\n    })\n  )\n  // 3. `isLoading` will be true while the server is re-rendering\n  // and streaming RSC payloads, when the query is updated via `setQuery`.\n\n  // Indicate loading state\n  if (isLoading) return <div>Loading...</div>\n\n  // Normal rendering with data\n  return <div>{/*...*/}</div>\n}\n```\n\n## Configuring parsers, default value & options\n\nYou can use a builder pattern to facilitate specifying all of those things:\n\n```ts\nuseQueryState(\n  'counter',\n  parseAsInteger.withDefault(0).withOptions({\n    history: 'push',\n    shallow: false\n  })\n)\n```\n\nYou can get this pattern for your custom parsers too, and compose them\nwith others:\n\n```ts\nimport { createParser, parseAsHex } from 'nuqs'\n\n// Wrapping your parser/serializer in `createParser`\n// gives it access to the builder pattern & server-side\n// parsing capabilities:\nconst hexColorSchema = createParser({\n  parse(query) {\n    if (query.length !== 6) {\n      return null // always return null for invalid inputs\n    }\n    return {\n      // When composing other parsers, they may return null too.\n      r: parseAsHex.parse(query.slice(0, 2)) ?? 0x00,\n      g: parseAsHex.parse(query.slice(2, 4)) ?? 0x00,\n      b: parseAsHex.parse(query.slice(4)) ?? 0x00\n    }\n  },\n  serialize({ r, g, b }) {\n    return (\n      parseAsHex.serialize(r) +\n      parseAsHex.serialize(g) +\n      parseAsHex.serialize(b)\n    )\n  }\n})\n  // Eg: set common options directly\n  .withOptions({ history: 'push' })\n\n// Or on usage:\nuseQueryState(\n  'tribute',\n  hexColorSchema.withDefault({\n    r: 0x66,\n    g: 0x33,\n    b: 0x99\n  })\n)\n```\n\nNote: see this example running in the [hex-colors demo](<./packages/docs/src/app/playground/(demos)/hex-colors/page.tsx>).\n\n## Multiple Queries (batching)\n\nYou can call as many state update function as needed in a single event loop\ntick, and they will be applied to the URL asynchronously:\n\n```ts\nconst MultipleQueriesDemo = () => {\n  const [lat, setLat] = useQueryState('lat', parseAsFloat)\n  const [lng, setLng] = useQueryState('lng', parseAsFloat)\n  const randomCoordinates = React.useCallback(() => {\n    setLat(Math.random() * 180 - 90)\n    setLng(Math.random() * 360 - 180)\n  }, [])\n}\n```\n\nIf you wish to know when the URL has been updated, and what it contains, you can\nawait the Promise returned by the state updater function, which gives you the\nupdated URLSearchParameters object:\n\n```ts\nconst randomCoordinates = React.useCallback(() => {\n  setLat(42)\n  return setLng(12)\n}, [])\n\nrandomCoordinates().then((search: URLSearchParams) => {\n  search.get('lat') // 42\n  search.get('lng') // 12, has been queued and batch-updated\n})\n```\n\n<details>\n<summary><em>Implementation details (Promise caching)</em></summary>\n\nThe returned Promise is cached until the next flush to the URL occurs,\nso all calls to a setState (of any hook) in the same event loop tick will\nreturn the same Promise reference.\n\nDue to throttling of calls to the Web History API, the Promise may be cached\nfor several ticks. Batched updates will be merged and flushed once to the URL.\nThis means not every setState will reflect to the URL, if another one comes\noverriding it before flush occurs.\n\nThe returned React state will reflect all set values instantly,\nto keep UI responsive.\n\n---\n\n</details>\n\n## `useQueryStates`\n\nFor query keys that should always move together, you can use `useQueryStates`\nwith an object containing each key's type:\n\n```ts\nimport { useQueryStates, parseAsFloat } from 'nuqs'\n\nconst [coordinates, setCoordinates] = useQueryStates(\n  {\n    lat: parseAsFloat.withDefault(45.18),\n    lng: parseAsFloat.withDefault(5.72)\n  },\n  {\n    history: 'push'\n  }\n)\n\nconst { lat, lng } = coordinates\n\n// Set all (or a subset of) the keys in one go:\nconst search = await setCoordinates({\n  lat: Math.random() * 180 - 90,\n  lng: Math.random() * 360 - 180\n})\n```\n\n## Loaders\n\nTo parse search params as a one-off operation, you can use a **loader function**:\n\n```tsx\nimport { createLoader } from 'nuqs' // or 'nuqs/server'\n\nconst searchParams = {\n  q: parseAsString,\n  page: parseAsInteger.withDefault(1)\n}\n\nconst loadSearchParams = createLoader(searchParams)\n\nconst { q, page } = loadSearchParams('?q=hello&page=2')\n```\n\nIt accepts various types of inputs (strings, URL, URLSearchParams, Request, Promises, etc.). [Read more](https://nuqs.dev/docs/server-side#loaders)\n\nSee the [server-side parsing demo](<./packages/docs/src/app/playground/(demos)/pagination>)\nfor a live example showing how to reuse parser configurations between\nclient and server code.\n\n## Accessing searchParams in Server Components\n\nIf you wish to access the searchParams in a deeply nested Server Component\n(ie: not in the Page component), you can use `createSearchParamsCache`\nto do so in a type-safe manner.\n\n> Note: parsers **don't validate** your data. If you expect positive integers\n> or JSON-encoded objects of a particular shape, you'll need to feed the result\n> of the parser to a schema validation library, like [Zod](https://zod.dev).\n\n```tsx\n// searchParams.ts\nimport {\n  createSearchParamsCache,\n  parseAsInteger,\n  parseAsString\n} from 'nuqs/server'\n// Note: import from 'nuqs/server' to avoid the \"use client\" directive\n\nexport const searchParamsCache = createSearchParamsCache({\n  // List your search param keys and associated parsers here:\n  q: parseAsString.withDefault(''),\n  maxResults: parseAsInteger.withDefault(10)\n})\n\n// page.tsx\nimport { searchParamsCache } from './searchParams'\n\nexport default function Page({\n  searchParams\n}: {\n  searchParams: Record<string, string | string[] | undefined>\n}) {\n  // ⚠️ Don't forget to call `parse` here.\n  // You can access type-safe values from the returned object:\n  const { q: query } = searchParamsCache.parse(searchParams)\n  return (\n    <div>\n      <h1>Search Results for {query}</h1>\n      <Results />\n    </div>\n  )\n}\n\nfunction Results() {\n  // Access type-safe search params in children server components:\n  const maxResults = searchParamsCache.get('maxResults')\n  return <span>Showing up to {maxResults} results</span>\n}\n```\n\nThe cache will only be valid for the current page render\n(see React's [`cache`](https://react.dev/reference/react/cache) function).\n\nNote: the cache only works for **server components**, but you may share your\nparser declaration with `useQueryStates` for type-safety in client components:\n\n```tsx\n// searchParams.ts\nimport { parseAsFloat, createSearchParamsCache } from 'nuqs/server'\n\nexport const coordinatesParsers = {\n  lat: parseAsFloat.withDefault(45.18),\n  lng: parseAsFloat.withDefault(5.72)\n}\nexport const coordinatesCache = createSearchParamsCache(coordinatesParsers)\n\n// page.tsx\nimport { coordinatesCache } from './searchParams'\nimport { Server } from './server'\nimport { Client } from './client'\n\nexport default async function Page({ searchParams }) {\n  await coordinatesCache.parse(searchParams)\n  return (\n    <>\n      <Server />\n      <Suspense>\n        <Client />\n      </Suspense>\n    </>\n  )\n}\n\n// server.tsx\nimport { coordinatesCache } from './searchParams'\n\nexport function Server() {\n  const { lat, lng } = coordinatesCache.all()\n  // or access keys individually:\n  const lat = coordinatesCache.get('lat')\n  const lng = coordinatesCache.get('lng')\n  return (\n    <span>\n      Latitude: {lat} - Longitude: {lng}\n    </span>\n  )\n}\n\n// client.tsx\n// prettier-ignore\n;'use client'\n\nimport { useQueryStates } from 'nuqs'\nimport { coordinatesParsers } from './searchParams'\n\nexport function Client() {\n  const [{ lat, lng }, setCoordinates] = useQueryStates(coordinatesParsers)\n  // ...\n}\n```\n\n## Serializer helper\n\nTo populate `<Link>` components with state values, you can use the `createSerializer`\nhelper.\n\nPass it an object describing your search params, and it will give you a function\nto call with values, that generates a query string serialized as the hooks would do.\n\nExample:\n\n```ts\nimport {\n  createSerializer,\n  parseAsInteger,\n  parseAsIsoDateTime,\n  parseAsString,\n  parseAsStringLiteral\n} from 'nuqs/server'\n\nconst searchParams = {\n  search: parseAsString,\n  limit: parseAsInteger,\n  from: parseAsIsoDateTime,\n  to: parseAsIsoDateTime,\n  sortBy: parseAsStringLiteral(['asc', 'desc'])\n}\n\n// Create a serializer function by passing the description of the search params to accept\nconst serialize = createSerializer(searchParams)\n\n// Then later, pass it some values (a subset) and render them to a query string\nserialize({\n  search: 'foo bar',\n  limit: 10,\n  from: new Date('2024-01-01'),\n  // here, we omit `to`, which won't be added\n  sortBy: null // null values are also not rendered\n})\n// ?search=foo+bar&limit=10&from=2024-01-01T00:00:00.000Z\n```\n\n### Base parameter\n\nThe returned `serialize` function can take a base parameter over which to\nappend/amend the search params:\n\n```ts\nserialize('/path?baz=qux', { foo: 'bar' }) // /path?baz=qux&foo=bar\n\nconst search = new URLSearchParams('?baz=qux')\nserialize(search, { foo: 'bar' }) // ?baz=qux&foo=bar\n\nconst url = new URL('https://example.com/path?baz=qux')\nserialize(url, { foo: 'bar' }) // https://example.com/path?baz=qux&foo=bar\n\n// Passing null removes existing values\nserialize('?remove=me', { foo: 'bar', remove: null }) // ?foo=bar\n```\n\n## Parser type inference\n\nTo access the underlying type returned by a parser, you can use the\n`inferParserType` type helper:\n\n```ts\nimport { parseAsInteger, type inferParserType } from 'nuqs' // or 'nuqs/server'\n\nconst intNullable = parseAsInteger\nconst intNonNull = parseAsInteger.withDefault(0)\n\ninferParserType<typeof intNullable> // number | null\ninferParserType<typeof intNonNull> // number\n```\n\nFor an object describing parsers (that you'd pass to `createSearchParamsCache`\nor to `useQueryStates`, `inferParserType` will\nreturn the type of the object with the parsers replaced by their inferred types:\n\n```ts\nimport { parseAsBoolean, parseAsInteger, type inferParserType } from 'nuqs' // or 'nuqs/server'\n\nconst parsers = {\n  a: parseAsInteger,\n  b: parseAsBoolean.withDefault(false)\n}\n\ninferParserType<typeof parsers>\n// { a: number | null, b: boolean }\n```\n\n## Testing\n\nSince nuqs v2, you can use a testing adapter to unit-test components using\n`useQueryState` and `useQueryStates` in isolation, without needing to mock\nyour framework or router.\n\nHere's an example using Testing Library and Vitest:\n\n```tsx\nimport { render, screen } from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'\nimport { describe, expect, it, vi } from 'vitest'\nimport { CounterButton } from './counter-button'\n\nit('should increment the count when clicked', async () => {\n  const user = userEvent.setup()\n  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()\n  render(<CounterButton />, {\n    // Setup the test by passing initial search params / querystring,\n    // and give it a function to call on URL updates\n    wrapper: ({ children }) => (\n      <NuqsTestingAdapter searchParams=\"?count=42\" onUrlUpdate={onUrlUpdate}>\n        {children}\n      </NuqsTestingAdapter>\n    )\n  })\n  // Initial state assertions: there's a clickable button displaying the count\n  const button = screen.getByRole('button')\n  expect(button).toHaveTextContent('count is 42')\n  // Act\n  await user.click(button)\n  // Assert changes in the state and in the (mocked) URL\n  expect(button).toHaveTextContent('count is 43')\n  expect(onUrlUpdate).toHaveBeenCalledOnce()\n  expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=43')\n  expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('43')\n  expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push')\n})\n```\n\nSee [#259](https://github.com/47ng/nuqs/issues/259) for more testing-related discussions.\n\n## Debugging\n\nYou can enable debug logs in the browser by setting the `debug` item in localStorage\nto `nuqs`, and reload the page.\n\n```js\n// In your devtools:\nlocalStorage.setItem('debug', 'nuqs')\n```\n\n> Note: unlike the `debug` package, this will not work with wildcards, but\n> you can combine it: `localStorage.setItem('debug', '*,nuqs')`\n\nLog lines will be prefixed with `[nuqs]` for `useQueryState` and `[nuq+]` for\n`useQueryStates`, along with other internal debug logs.\n\nUser timings markers are also recorded, for advanced performance analysis using\nyour browser's devtools.\n\nProviding debug logs when opening an [issue](https://github.com/47ng/nuqs/issues)\nis always appreciated. 🙏\n\n### SEO\n\nIf your page uses query strings for local-only state, you should add a\ncanonical URL to your page, to tell SEO crawlers to ignore the query string\nand index the page without it.\n\nIn the app router, this is done via the metadata object:\n\n```ts\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  alternates: {\n    canonical: '/url/path/without/querystring'\n  }\n}\n```\n\nIf however the query string is defining what content the page is displaying\n(eg: YouTube's watch URLs, like `https://www.youtube.com/watch?v=dQw4w9WgXcQ`),\nyour canonical URL should contain relevant query strings, and you can still\nuse your parsers to read it, and to serialize the canonical URL:\n\n```ts\n// page.tsx\nimport type { Metadata, ResolvingMetadata } from 'next'\nimport { notFound } from 'next/navigation'\nimport {\n  createParser,\n  parseAsString,\n  createLoader,\n  createSerializer,\n  type SearchParams,\n  type UrlKeys\n} from 'nuqs/server'\n\nconst youTubeVideoIdRegex = /^[^\"&?\\/\\s]{11}$/i\nconst youTubeSearchParams = {\n  videoId: createParser({\n    parse(query) {\n      if (!youTubeVideoIdRegex.test(query)) {\n        return null\n      }\n      return query\n    },\n    serialize(videoId) {\n      return videoId\n    }\n  })\n}\nconst youTubeUrlKeys: UrlKeys<typeof youTubeSearchParams> = {\n  videoId: 'v'\n}\nconst loadYouTubeSearchParams = createLoader(youTubeSearchParams, {\n  urlKeys: youTubeUrlKeys\n})\nconst serializeYouTubeSearchParams = createSerializer(youTubeSearchParams, {\n  urlKeys: youTubeUrlKeys\n})\n\n// --\n\ntype Props = {\n  searchParams: Promise<SearchParams>\n}\n\nexport async function generateMetadata({\n  searchParams\n}: Props): Promise<Metadata> {\n  const { videoId } = await loadYouTubeSearchParams(searchParams)\n  if (!videoId) {\n    notFound()\n  }\n  return {\n    alternates: {\n      canonical: serializeYouTubeSearchParams('/watch', { videoId })\n      // /watch?v=dQw4w9WgXcQ\n    }\n  }\n}\n```\n\n### Lossy serialization\n\nIf your serializer loses precision or doesn't accurately represent\nthe underlying state value, you will lose this precision when\nreloading the page or restoring state from the URL (eg: on navigation).\n\nExample:\n\n```ts\nconst geoCoordParser = {\n  parse: parseFloat,\n  serialize: v => v.toFixed(4) // Loses precision\n}\n\nconst [lat, setLat] = useQueryState('lat', geoCoordParser)\n```\n\nHere, setting a latitude of 1.23456789 will render a URL query string\nof `lat=1.2345`, while the internal `lat` state will be correctly\nset to 1.23456789.\n\nUpon reloading the page, the state will be incorrectly set to 1.2345.\n\n## License\n\n[MIT](https://github.com/47ng/nuqs/blob/next/LICENSE)\n\nMade with ❤️ by [François Best](https://francoisbest.com)\n\nUsing this package at work ? [Sponsor me](https://github.com/sponsors/franky47)\nto help with support and maintenance.\n\n<div>\nnuqs is part of the &nbsp;<a href=\"https://vercel.com/oss\"><img alt=\"Vercel OSS Program\" src=\"https://vercel.com/oss/program-badge.svg\" /></a>&nbsp;<small><a href=\"https://vercel.com/blog/spring25-oss-program\">(spring 2025 cohort)</a></small>\n</div>\n\n<br/>\n\n![Project analytics and stats](https://repobeats.axiom.co/api/embed/3ee740e4729dce3992bfa8c74645cfebad8ba034.svg 'Repobeats analytics image')\n"
  },
  {
    "path": "errors/NUQS-101.md",
    "content": "# This package is ESM only.\n\nSince version 2.0.0, `nuqs` is now an [ESM-only package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).\n\n## Probable cause\n\nYou may have encountered this error when trying to import `nuqs` in a CommonJS\nenvironment, like Jest or ESLint.\n\n## Possible solutions\n\nIf you cannot update your project to use ESM (which would be the most future-proof\nsolution), please refer to the following guides:\n\n### Jest\n\nSee the [testing adapter documentation](https://nuqs.dev/docs/testing#jest-and-esm)\nfor its configuration with Jest.\n\n### ESLint\n\nSee issue [#691](https://github.com/47ng/nuqs/issues/691) for more details.\n\n### Something else?\n\nIf you are encountering this error in a different context, please\n[open an issue](https://github.com/47ng/nuqs/issues/new/choose) with details about\nyour setup (a reproduction repository would be even better).\n"
  },
  {
    "path": "errors/NUQS-303.md",
    "content": "# Multiple adapter contexts detected\n\n## Probable cause\n\nThis error occurs in [debug mode](https://nuqs.dev/docs/debugging) in\ncertain monorepo setups where references of the adapter context aren't the same\nin different packages, and cause a [`NUQS-404 - nuqs requires an adapter to work with your framework`](./NUQS-404.md) error.\n\n## Root cause\n\nAs described in the [React docs](https://react.dev/reference/react/useContext#my-component-doesnt-see-the-value-from-my-provider), this can happen with Context providers (which\nis what adapters are) being re-created in different modules and causing different\nreferences being used for a provider and consumers.\n\n## Possible solutions\n\nSee issue [#798](https://github.com/47ng/nuqs/issues/798) for more details.\n"
  },
  {
    "path": "errors/NUQS-404.md",
    "content": "# `nuqs` requires an adapter to work with your framework\n\n## Probable cause\n\nYou haven't wrapped the components calling `useQueryState(s)` with\nan adapter.\n\n[Adapters](https://nuqs.dev/docs/adapters) are based on React Context,\nand provide nuqs hooks with the interfaces to work with your framework:\nreacting to URL changes, and calling your router when you update your state.\n\n## Possible solutions\n\nFollow the setup instructions to import and wrap your application\nusing a suitable adapter:\n\n- [Next.js (app router)](https://nuqs.dev/docs/adapters#nextjs-app-router)\n- [Next.js (pages router)](https://nuqs.dev/docs/adapters#nextjs-pages-router)\n- [React SPA (eg: with Vite)](https://nuqs.dev/docs/adapters#react-spa)\n- [Remix](https://nuqs.dev/docs/adapters#remix)\n- [React Router v6](https://nuqs.dev/docs/adapters#react-router-v6)\n- [React Router v7](https://nuqs.dev/docs/adapters#react-router-v7)\n- [TanStack Router](https://nuqs.dev/docs/adapters#tanstack-router)\n\n### Test adapter\n\nIf you encounter this error outside of the browser, like in a test\nrunner (eg: Vitest or Jest), you may use the [testing adapter](https://nuqs.dev/docs/testing)\nfrom `nuqs/adapters/testing` to mock the initial search params and access\nsetup/assertion testing facilities.\n\n### Monorepo setups\n\nThis error can also occur in monorepo setups where components using nuqs hooks\nare in different packages resolving to different `nuqs` versions,\nleading to different context references being used.\n\nIf you [enable debugging](https://nuqs.dev/docs/debugging), you might see a\n[`NUQS-303 - Multiple adapter contexts detected`](./NUQS-303.md) error, confirming\nthis hypothesis.\n\nMake sure that all packages resolve to the same version\nof `nuqs` to prevent this issue from arising. See issue\n[#798](https://github.com/47ng/nuqs/issues/798) for more details and\npossible solutions.\n"
  },
  {
    "path": "errors/NUQS-409.md",
    "content": "# Multiple versions of the library are loaded\n\nThis error occurs if two different versions of `nuqs` are\nloaded in the same application.\n\nThis may happen if you are using a package that embeds `nuqs` and\nyou are also using `nuqs` directly.\n\n## Possible Solutions\n\nInspect your dependencies for duplicate versions of `nuqs` and\nuse the `resolutions` field in `package.json` to force all dependencies\nto use the same version.\n"
  },
  {
    "path": "errors/NUQS-414.md",
    "content": "# Max Safe URL Length Exceeded\n\nThis error occurs if your URL length exceeds 2,000 characters.\n\nThere are varying browser limitations for max URL lengths, [read more](https://nuqs.dev/docs/limits#max-url-lengths).\n\nURLs that are too long might break in some browsers, or not be able to be\nprocessed by some servers.\n\n## Possible Solutions\n\nKeeping your URLs short is a good practice: not all state has to live in the URL.\n\n- Server state/data is best managed in a local cache like TanStack Query or SWR.\n- Transient state (that doesn't need persisting or sharing) can be managed in local state.\n- Device-persistent state can be managed in local storage.\n\nWhen deciding to put state in the URL, ask yourself:\n\n- Do I need it to persist across page refresh?\n- Do I need to share it with others?\n- Do I need to link to it from other places?\n- Do I need to be able to bookmark it?\n- Do I need to be able to use the Back/Forward buttons to navigate to it?\n- Is it always going to be a small amount of data?\n\nIf the answer to any of these questions is no, then you might want to consider\nan alternative state storage solution.\n"
  },
  {
    "path": "errors/NUQS-422.md",
    "content": "# Invalid Options Combination.\n\nThis warning will show up if you combine `shallow: true` (the default) and `limitUrlUpdates: debounce` options.\n\nDebounce only makes sense for server-side data fetching, the returned client state is always updated **immediately**, so combining `limitUrlUpdates: debounce` with `shallow: true` will not work as expected.\n\nIf you are fetching client-side, you’ll want to debounce the state returned by the hooks instead (using a 3rd party `useDebounce` utility hook).\n\n## Solution\n\n- Set `shallow: false` to allow debounce to work properly, check the [documentation](https://nuqs.dev/docs/options#debounce) for more information.\n"
  },
  {
    "path": "errors/NUQS-429.md",
    "content": "# URL update rate-limited by the browser\n\nThis error occurs when too many URL updates are attempted in a short period of\ntime. For example, connecting a query state to a text input that updates on\nevery keypress, or a slider (`<input type=\"range\">`).\n\n## Possible Solutions\n\nThe library has a built-in throttling mechanism, that can be configured per\ninstance. See the [throttling](https://github.com/47ng/nuqs#throttling)\ndocs for more information.\n\n## Safari\n\nSafari has a very low rate-limit on URL updates: 100 updates per 30 seconds (or per 10 seconds on Safari 17 and above).\n"
  },
  {
    "path": "errors/NUQS-500.md",
    "content": "# Empty Search Params Cache\n\nThis error shows up on the server when trying to access a searchParam from\na cache created with `createSearchParamsCache`, but when the cache was not\nproperly populated at the top of the page.\n\n## A note on layouts\n\nThe error can also occur if your server component consuming the search params\ncache is mounted in a **layout** component. Those don't receive search params as\nthey are not re-rendered when the page renders.\n\nIn this case, your only option is to turn the server component into a client\ncomponent, and read the search params with `useQueryStates`. You can\n[feed it the same parser object](https://github.com/47ng/nuqs#accessing-searchparams-in-server-components)\nyou used to create the cache, and it you'll get the same\ntype safety.\n\n## Possible Solution\n\nRun the `parse` method and feed it the page's `searchParams`:\n\n```tsx\n// page.tsx\nimport {\n  createSearchParamsCache,\n  parseAsInteger,\n  parseAsString,\n  type SearchParams\n} from 'nuqs/server'\n\nconst cache = createSearchParamsCache({\n  q: parseAsString,\n  maxResults: parseAsInteger.withDefault(10)\n})\n\nexport default function Page({\n  searchParams\n}: {\n  searchParams: Promise<SearchParams>\n}) {\n  // ⚠️ Don't forget to call `parse` here:\n  const { q: query } = await cache.parse(searchParams)\n  return (\n    <div>\n      <h1>Search Results for {query}</h1>\n      <Results />\n    </div>\n  )\n}\n\nfunction Results() {\n  // In order to get search params from child server components:\n  const maxResults = cache.get('maxResults')\n  return <span>Showing up to {maxResults} results</span>\n}\n```\n"
  },
  {
    "path": "errors/NUQS-501.md",
    "content": "# Search params cache already populated\n\nThis error occurs when a [search params cache](https://github.com/47ng/nuqs#accessing-searchparams-in-server-components)\nis being fed searchParams more than once.\n\nInternally, the cache object will be frozen for the duration of the page render\nafter having been populated. This is to prevent search params from being modified\nwhile the page is being rendered.\n\n## Solutions\n\nLook into the stack trace where the error occurred and remove the second call to\n`parse` that threw the error.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"nuqs-monorepo\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"François Best\",\n    \"email\": \"contact@francoisbest.com\",\n    \"url\": \"https://francoisbest.com\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/47ng/nuqs\"\n  },\n  \"scripts\": {\n    \"dev\": \"turbo run dev\",\n    \"build\": \"turbo run build\",\n    \"test\": \"turbo run test --log-order=stream\",\n    \"prepare\": \"husky\",\n    \"lint\": \"pnpm run -w --parallel --stream '/^lint:/'\",\n    \"lint:prettier\": \"prettier --check ./packages/nuqs/src/**/*.ts\",\n    \"lint:sherif\": \"sherif\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/config-conventional\": \"^20.4.1\",\n    \"commitlint\": \"^20.4.1\",\n    \"husky\": \"^9.1.7\",\n    \"prettier\": \"3.6.2\",\n    \"publint\": \"^0.3.17\",\n    \"semantic-release\": \"^25.0.3\",\n    \"sherif\": \"^1.10.0\",\n    \"turbo\": \"^2.8.2\",\n    \"typescript\": \"^5.9.3\"\n  },\n  \"packageManager\": \"pnpm@10.28.2\",\n  \"prettier\": {\n    \"arrowParens\": \"avoid\",\n    \"semi\": false,\n    \"singleQuote\": true,\n    \"tabWidth\": 2,\n    \"trailingComma\": \"none\",\n    \"useTabs\": false\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\",\n      \"pre-push\": \"pnpm run lint\"\n    }\n  },\n  \"commitlint\": {\n    \"extends\": [\n      \"@commitlint/config-conventional\"\n    ],\n    \"rules\": {\n      \"type-enum\": [\n        2,\n        \"always\",\n        [\n          \"build\",\n          \"chore\",\n          \"ci\",\n          \"clean\",\n          \"doc\",\n          \"feat\",\n          \"fix\",\n          \"perf\",\n          \"ref\",\n          \"revert\",\n          \"style\",\n          \"test\"\n        ]\n      ],\n      \"subject-case\": [\n        0,\n        \"always\",\n        \"sentence-case\"\n      ],\n      \"body-leading-blank\": [\n        2,\n        \"always\",\n        true\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/docs/.gitignore",
    "content": "# deps\n/node_modules\n\n# generated content\n_map.ts\n.contentlayer\n.source\n\n# test & build\n/coverage\n/.next/\n/out/\n/build\n*.tsbuildinfo\n\n# misc\n.DS_Store\n*.pem\n/.pnp\n.pnp.js\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# others\n.env*.local\n.vercel\nnext-env.d.ts\n# Sentry Config File\n.sentryclirc\n\n# shadcn registry output\nregistry.json\npublic/r\n"
  },
  {
    "path": "packages/docs/README.md",
    "content": "# docs\n\nThis is a Next.js application generated with\n[Fumadocs](https://github.com/fuma-nama/fumadocs).\n\nRun development server:\n\n```bash\nnpm run dev\n# or\npnpm dev\n# or\nyarn dev\n```\n\nOpen http://localhost:3000 with your browser to see the result.\n\n## Learn More\n\nTo learn more about Next.js and Next Docs, take a look at the following\nresources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js\n  features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n- [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs\n"
  },
  {
    "path": "packages/docs/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.ts\",\n    \"css\": \"src/app/globals.css\",\n    \"baseColor\": \"zinc\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/src/components\",\n    \"utils\": \"@/src/lib/utils\"\n  }\n}\n"
  },
  {
    "path": "packages/docs/content/blog/2025.mdx",
    "content": "---\ntitle: '2025'\ndescription: A look back on an amazing year of OSS, meeting people, and milestones.\nauthor: François Best\ndate: 2025-12-31\n---\n\nimport { GitHubProfile } from '@/src/components/github-profile'\nimport { ReleaseContributionGraph } from '@/src/components/release-contribution-graph'\n\nTo say that **2025 was an incredible year** would be quite the understatement.\n\nThis wild ride started on the 3rd of January, when I learned my CFP for\n[React Paris](https://react.paris) had been accepted.\nOnly 3 days after setting my yearly goal of speaking at a conference.\nThis, honestly, has been the highlight of this year: speaking at _not one_,\nbut _three_ prestigious conferences, and meeting a ton of people along the way.\n\nFor context, at the beginning of 2025, nuqs was one year old\n(at least the rebranding from `next-usequerystate` on Jan 1st 2024),\nand we had just [opened it up to the rest of the React ecosystem](/blog/nuqs-2)\na couple of months prior.\n\n<ReleaseContributionGraph year={2025} />\n\nThe success it has encountered this year is mind-boggling:\n\n- ⭐ Stars: 5500 → 9800 (+4300 in 2025)\n- 📦 All-time NPM downloads: 7.5M → 41M (+33.5M in 2025)\n- 💖 Sponsors: 5 (+2,245€) → 20 (+2,802€)\n- 🫶 Sponsoring: 0 ($0) → 20 (-$3,316)\n\n<Callout title=\"About the emoji 👀\">\nI'm writing this article by hand, without any help from AI.\nI just like emoji. #SorryNotSorry\n</Callout>\n\n\nBut open-source is not about code, it's about **people**.\n\nIt's a social graph of members of the community, contributors, maintainers, and sponsors.\nAnd the increase of interest in nuqs has been a great opportunity for me\nto meet a lot of people, mainly through conferences.\n\n## Conferences\n\n### React Paris\n\nIt was my first time speaking at a big conference, and the speakers lineup was amazing:\n\n- <GitHubProfile name=\"Kent C Dodds\" handle=\"kentcdodds\"/>\n- <GitHubProfile name=\"Tejas Kumar\" handle=\"TejasQ\"/>\n- <GitHubProfile name=\"David Khourshid\" handle=\"DavidKPiano\" />\n- <GitHubProfile name=\"Aurora Scharff\" handle=\"aurorascharff\" />\n- <GitHubProfile name=\"Dominik Dorfmeister\" handle=\"TkDodo\" />\n- And many more…\n\nI had been chatting with <GitHubProfile name=\"Aurora\" handle=\"aurorascharff\" url=\"https://aurorascharff.no\"/> before the conference about some optimistic update problems\nshe discovered with nuqs, and was very pleased to learn that she incorporated it in\n[her talk](https://www.youtube.com/watch?v=dA-8FY5xlbk&list=PL53Z0yyYnpWitP8Zv01TSEQmKLvuRh_Dj&index=12)\n(happening right after mine).\nThe biggest surprise though was that <GitHubProfile name=\"David\" handle=\"DavidKPiano\" url=\"https://stately.ai/\"/> (whose talk was just before mine)\nalso talked about nuqs (and Aurora's) in his own talk,\n[\"Goodbye useState\"](https://www.youtube.com/watch?v=aGkscOKWQvQ&list=PL53Z0yyYnpWitP8Zv01TSEQmKLvuRh_Dj&index=18).\nA lovely program scheduling coincidence.\n\n<iframe\n  src=\"https://www.youtube-nocookie.com/embed/U__Rwsp8v78?si=sQgg-HJTMoy_mt7K&amp;start=34\"\n  title=\"YouTube video player\"\n  frameBorder=\"0\"\n  allow=\"autoplay; encrypted-media; picture-in-picture; web-share\"\n  referrerPolicy=\"strict-origin-when-cross-origin\"\n  allowFullScreen\n  className='aspect-video w-full max-w-2xl mx-auto mb-12'\n/>\n\nI also met <GitHubProfile name=\"Dominik\" handle=\"TkDodo\" url=\"https://tkdodo.eu\"/> IRL for the first time.\nHe told me he had just joined Sentry and wanted to use nuqs there, for which we needed to support a few things.\nHe graciously contributed those changes to the core package, and now Sentry\nis in the top 5 most popular OSS repos using nuqs 🙌 Thank you, Dominik! 🫶\n\n### Next.js Conf\n\nBack in April, nuqs joined the [Vercel OSS Program](https://vercel.com/open-source-program). My interest for it wasn't the credits\nfrom partners (I still haven't claimed any of them, as I don't need them), but the **community** of OSS builders it brings together.\n\nI was then invited to speak about nuqs at Next.js Conf in San Francisco. Due to a series of unfortunate events,\nthis experience could have been smoother. Maybe one day I'll write about the bad parts of it.\n\nBut let's focus on the good ones:\n\n- Along with two teammates from Brazil & Colombia, we won the pre-conf hackathon organised by [Clerk](https://clerk.com/) & [CodeTV](https://www.youtube.com/@codetv-dev).\nThose gains helped me sign the [Open Source Pledge](/blog/open-source-pledge).\n\n- My talk was very well received, and again, meeting people IRL that I had been chatting with online is great. The hallway track really is the best part of conferences.\n\n<iframe\n  src=\"https://www.youtube-nocookie.com/embed/qpczQVJMG1Y\"\n  title=\"YouTube video player\"\n  frameBorder=\"0\"\n  allow=\"autoplay; encrypted-media; picture-in-picture; web-share\"\n  referrerPolicy=\"strict-origin-when-cross-origin\"\n  allowFullScreen\n  className='aspect-video w-full max-w-2xl mx-auto mb-12'\n/>\n\n### React Advanced London\n\nJust one month after SF, I gave the same talk in London. Same-ish: the time kept getting shorter,\nand I kept on adding more into it, so I ended up speaking super fast! 😅\n\nIt also went very well, the room was packed, and the live Q&A on stage at the end was a very nice way to interact\nwith the audience. Thanks <GitHubProfile name=\"Amber Shand\" handle=\"amberleeshand\" url=\"https://www.ambershand.co.uk/\" /> for MCing! 🦆🦆😉\n\n## The Horde\n\nOf all the people I met online this year, one stands out: <GitHubProfile name=\"OrcDev\" handle=\"TheOrcDev\" url=\"https://orcdev.com\"/>,\nwho was one of the first YouTubers to spread the word about nuqs.\n\nWe became friends, convinced each other to apply for conferences (he also gave his first talk this year ⚔️),\nand started growing a community around our projects, composed of fellow OSS maintainers & builders.\n\nThis community is called [The Horde](https://join.thehorde.dev), and its purpose is to help devs starting with open source\nget visibility, help, and pool resources for a greater reach. All in orcish retro-gaming style. No warrior fights alone. ⚔️\n\n## 2026\n\nSo what are the plans for the new year?\n\nOn the personal side, I have been neck deep in [client work](mailto:consulting@francoisbest.com)\nfor the last few months, so the backlog of issues & PRs has been growing. Big thanks to regular contributors like <GitHubProfile name=\"Dominik Koch\" handle=\"mezotv\" url=\"https://dominikkoch.dev/\"/> & <GitHubProfile handle=\"TkDodo\" />\nfor their help 🫶, I'm also going to need to focus a bit of my time on nuqs to bring it back to a satisfying state:\na solid base on which we can plan the next big thing.\n\nFor this project to be sustainable, it needs a couple of things:\n\n1. Solid funding\n2. A community of contributors & co-maintainers\n\nimport { InlineSponsorsList } from '@/src/app/(pages)/_landing/sponsors'\n\nBig thanks to the [💖 sponsors](https://github.com/sponsors/franky47) who are making this project possible:\n\n<InlineSponsorsList className='mb-12'/>\n\nA few goals for this year:\n- Improve TanStack Router / Start support (even though you don't _need_ nuqs for it,\nI don't see why it shouldn't be supported as well as the other frameworks, to the extent of what's possible).\n- Rewrite the core to a single store so we can ship devtools, and unlock performance improvement and DX.\nThis could theoretically also unlock Solid/Svelte/Vue implementations, but the React ecosystem brings already enough work to maintain.\n- Ship nuqs@3.0.0 with runtime Standard Schema validation\n\n### Workshop\n\nTalks at conferences are great for introducing nuqs (I particularly like live-coding\nto tell a story through code), but ~30 minutes is usually too short for deep dives and\nstuding patterns in real-life apps.\n\nSo this year I'm working on a workshop around nuqs & type-safe URL state in general:\n\n- Why you need URL state (and when not to use it)\n- How to implement it in your app in a way that scales\n- How to maintain it over time\n\nThis workshop is aimed at teams that maintain large applications with complex URL state logic,\nso feel free to [contact me](mailto:workshop@francoisbest.com) if your company is interested.\n\n### Side quests\n\nAfter setting up my office for recording [YouTube videos](https://www.youtube.com/@47ng-dev)\n(a whopping 5 videos published this year 😅) and streaming [live on Twitch](https://www.twitch.tv/francoisbest),\nand helping friends like <GitHubProfile name=\"OrcDev\" handle=\"TheOrcDev\" url=\"https://www.youtube.com/@orcdev\"/>, <GitHubProfile handle=\"AlemTuzlak\" name=\"Alem Tuzlak\" url=\"https://www.youtube.com/@alemtuzlak\"/> (maintainer of TanStack AI & Devtools),\nand <GitHubProfile handle=\"varkoff\" name=\"Virgile Rietsch\" url=\"https://www.youtube.com/@Virgile-Rietsch\" /> with their audio setup, I realised something:\n\n**Most people starting on YouTube have a terrible sound.**\n\nIt's a shame, because their content would be otherwise great, but no matter if you shoot in 4K,\nif you sound like you're in the middle of an airport hall, I'm going to skip to the next video.\n\nSo I want to help change that. I've got a few techniques (gathered from my 8 years in the pro-audio industry)\nto help folks get a decent result out of the hardware they already have, without breaking the bank,\nand I'm working on a **mini-course** to help increase retention rates on videos through better sound.\n\n## Wrapping up\n\nI wish you a Happy New Year: may your projects be successful, and may you find friends along the way.\nBecause in this day & age of ubiquitous AI, it's easy to forget why we do this job: **for people**.\n"
  },
  {
    "path": "packages/docs/content/blog/beware-the-url-type-safety-iceberg.components.tsx",
    "content": "'use client'\n\nimport { useState } from 'react'\n\nconst states = {\n  pagination: {\n    pageIndex: 1,\n    pageSize: 50\n  },\n  filters: [\n    {\n      id: 'genre',\n      value: 'fantasy'\n    }\n  ],\n  orderBy: {\n    id: 'releaseYear',\n    desc: false\n  }\n}\n\nconst passThrough = <T,>(x: T) => x\n\nexport function URLComparison() {\n  const [highlight, setHighlight] = useState(false)\n  const [encoding, setEncoding] = useState(true)\n  const keyClass = highlight ? 'text-pink-700 dark:text-pink-400' : ''\n  const valueClass = highlight ? 'text-sky-700 dark:text-sky-400' : ''\n  const encode = encoding ? encodeURIComponent : passThrough\n  return (\n    <div className=\"rounded-md border border-dashed px-2 pt-2\">\n      <div className=\"flex gap-4 pl-1 text-sm\">\n        <label className=\"flex gap-2\">\n          <input\n            type=\"checkbox\"\n            checked={highlight}\n            onChange={e => setHighlight(e.target.checked)}\n          />\n          Highlighting\n        </label>\n        <label className=\"flex gap-2\">\n          <input\n            type=\"checkbox\"\n            checked={encoding}\n            onChange={e => setEncoding(e.target.checked)}\n          />\n          Encoding\n        </label>\n      </div>\n      <ol className=\"space-y-4\">\n        <li className=\"break-all\">\n          {'https://example.com'}?<span className={keyClass}>page</span>=\n          <span className={valueClass}>1</span>&\n          <span className={keyClass}>size</span>=\n          <span className={valueClass}>50</span>&\n          <span className={keyClass}>filters</span>=\n          <span className={valueClass}>genre:fantasy</span>&\n          <span className={keyClass}>sort</span>=\n          <span className={valueClass}>releaseYear:asc</span>\n        </li>\n        <li className=\"break-all\">\n          {'https://example.com'}?<span className={keyClass}>pagination</span>=\n          <span className={valueClass}>\n            {encode(JSON.stringify(states.pagination))}\n          </span>\n          &<span className={keyClass}>filters</span>=\n          <span className={valueClass}>\n            {encode(JSON.stringify(states.filters))}\n          </span>\n          &<span className={keyClass}>orderBy</span>=\n          <span className={valueClass}>\n            {encode(JSON.stringify(states.orderBy))}\n          </span>\n        </li>\n      </ol>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/blog/beware-the-url-type-safety-iceberg.mdx",
    "content": "---\ntitle: Beware The URL Type-Safety Iceberg\ndescription: Type-safe URL state is only the visible part. There are more dangers below.\nauthor: François Best\ndate: 2025-06-10\n---\n\nThe nuqs tagline, _\"**type-safe search params** state manager for React\"_, only represents\na small fraction of what nuqs does under the hood. Type-safety is only the tip of the iceberg.\nThere are hidden dangers beneath the surface that you (or, better, your tools) need to be aware of.\n\n## Read vs Write\n\nOne of the first things people do when they want type-safe URL state is to\nbring in validation libraries (Zod, Valibot, ArkType, anything [Standard Schema](https://standardschema.dev/) compliant)\nto parse `URLSearchParams{:ts}` into valid data types they can use in their apps.\n\nThat's **read** type-safety, and it is fairly easy to achieve.\n\nYou could stop here and wonder why you'd need another third-party library, until\nyou need to **write** search params with _complex_ state. Anything that doesn't\ntrivially stringify using `.toString(){:ts}` will need a _serialisation_ step that matches\nthe parsing <small>(math nerds call this property _bijectivity_)</small>.\n\nValidation libraries don't provide the reverse transform from an object or data type\nback to the string it got transformed from. To address this, nuqs comes with [built-in parsers](/docs/parsers/built-in)\nfor common data types, but you can also [make your own](/docs/parsers/making-your-own)\nto give your complex data types a beautiful, **compact** representation in the URL.\n\nCompactness here is the important property: URLs have a size limit, much like local storage\nor cookies.\n\nWhile 2000 characters is generally considered safe, the HTTP spec allows a bit more headroom at 8KB, but\npractical limitations will be those of the medium you share your URL through, and\nthe (un)willingness of users to click very long links. Remember:\n\n> The URL is the first piece of UI your users will see. Design it as such.\n\nSee for yourself: which link would you rather click on?\n\nimport { URLComparison } from './beware-the-url-type-safety-iceberg.components'\nimport { Suspense } from 'react'\n\n<Suspense fallback={\n  <div className=\"rounded-md border border-dashed px-2 pt-2\">\n    <div className=\"flex gap-4 pl-1 text-sm text-gray-600 dark:text-gray-400 animate-pulse\">\n      <label className='flex gap-2'>\n        <input type=\"checkbox\" disabled />\n        Highlighting\n      </label>\n      <label className=\"flex gap-2\">\n        <input type=\"checkbox\" disabled checked />\n        Encoding\n      </label>\n    </div>\n    <ol className=\"space-y-4\">\n      <li className=\"break-all\">\n        {'https://example.com?page=1&size=50&filters=genre:fantasy&sort=releaseYear:asc'}\n      </li>\n      <li className=\"break-all\">\n        {'https://example.com?pagination=%7B%22pageIndex%22%3A1%2C%22pageSize%22%3A50%7D&filters=%5B%7B%22id%22%3A%22genre%22%2C%22value%22%3A%22fantasy%22%7D%5D&orderBy=%7B%22id%22%3A%22releaseYear%22%2C%22desc%22%3Afalse%7D'}\n      </li>\n    </ol>\n  </div>\n}>\n  <URLComparison/>\n</Suspense>\n\n<Callout title=\"Tip: what state goes in the URL?\">\n  Only store state in the URL that you want to **share with others** (including your future self, with bookmarks\n  and history navigation).\n\n  Putting state there only because it's convenient for it to persist across page reloads will likely\n  make you overuse this pattern.\n</Callout>\n\n## Deserialise, parse, validate\n\nValidation libraries still have a very important purpose: making sure your state is\n**runtime-safe**. Even after deserialisation into the correct data type,\nthere may be invalid _values_ you want to avoid, and that you cannot represent with\nstatic typing alone. Things like:\n\n- A number between -90/+90 or -180/+180 for latitude/longitude coordinates\n- A string formatted in a certain way (like an email or a UUID)\n- A date greater than a given epoch\n\nOn read, validation should occur **after** deserialisation, on a data type relatively close\nto the desired output (which is ensured by parsing).\n\nSimilarly, on write, validation should occur **before** serialisation, to\nmake sure invalid states don't get persisted to the URL.\n\n\n## Time Safety\n\n80% of the nuqs codebase does not deal with _type safety_, but **time safety**.\n\nOne of the lesser known issues with the\n[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API)\n(that most routers use to update the URL without doing a full page load) is that\n**browsers rate-limit URL updates**, for security reasons.\n\nCalling updates too quickly will throw an error, potentially crashing your app\nin the process.\n\nNot all browsers are equal on this rate limiting: Chrome and Firefox allow about 50ms between calls\nto be safe, but Safari has much stricter limits, and requires about 120ms between calls.\n\nThis issue surfaces when binding URL state to a high-frequency input, like a text box\n`<input type=\"text\">{:html}` or a slider `<input type=\"range\">{:html}`.\n\nYou could solve this by keeping inputs _uncontrolled_ and deferring the URL update to\na later time (after a debounce timeout or the press of a button), but controlled inputs have their\npurpose. If you want to follow external URL updates to reset their state, or if other\nparts of the React tree need this state, call `useQueryState(s){:ts}` anywhere you need\naccess to that shared state (the same way you would use a global state manager like Zustand, Jotai, etc.),\nand nuqs will **lift the state _out_** into the URL for you.\n\nRather than requiring rigidity in userland, nuqs embraces browser limits and solves the time safety\nproblem with a **throttled queue** and **optimistic URL updates**.\n\nThis also allows **batching** state updates from different sources, and **stitching** them together\nautomatically, which is one of the most encountered pain points when doing this manually.\n\n```ts\nconst [lat, setLat] = useQueryState('lat', parseAsFloat)\nconst [lng, setLng] = useQueryState('lng', parseAsFloat)\n\nconst randomCoordinates = () => {\n  // These will be batched into a single URL update\n  setLat(Math.random() * 180 - 90)\n  setLng(Math.random() * 360 - 180)\n}\n```\n\n<Callout>\n  You might want to use [`useQueryStates{:ts}`](/docs/batching) for better type-safety when using\n  related states.\n</Callout>\n\nThe upcoming [debounce feature](https://github.com/47ng/nuqs/pull/900) makes this process\neven more apparent, with the additional complexity of having to handle aborting pending updates\nwhen the user navigates away from the page they're on. This prevents stale state updates from being\napplied on the wrong pathname or overriding link state (last user action wins).\n\n## Immutability\n\nOnce you've shared URLs out in the wild, they become **immutable**. But your application is anything but immutable.\n\nThe statefulness you introduce by adding URL state makes it akin to a database schema.\nEach shared URL is an immutable database snapshot that your app needs to be able to process\nthroughout its lifetime, to honour the promise encoded in those links. But it should not\nprevent you from making changes in the expected schema your app accepts.\n\nJust like database schemas, we can handle this with **migrations**:\n\n1. Capture old URLs with a middleware\n2. Migrate the old state schema into what the app currently expects\n3. Redirect to the updated URL to continue\n\nWe've [explored declarative ways](https://x.com/fortysevenfx/status/1842162844613112081) to do this,\nbut this is something that could benefit more than just nuqs users, so it may materialise\nas a complementary package.\n\n```ts\n// Note: this is a hypothetical API.\n\nconst applyMigrations = createURLMigration([\n  {\n    // Transform /?hello=world to /?name=world\n    type: \"rename-key\",\n    from: \"hello\",\n    to: \"name\",\n  },\n  {\n    // Transform /?hello=world&bye-bye=gone to /?hello=world\n    type: \"remove-key\",\n    key: \"bye-bye\",\n  },\n  {\n    // Convert /?date=2022-01-01T00:00:00Z to /?date=1640995200000\n    type: \"update-value\",\n    key: \"date\",\n    action: (value) => {\n      const date = parseAsIsoDateTime.parse(value);\n      if (date === null) {\n        return null;\n      }\n      return parseAsTimestamp.serialize(date);\n    },\n  },\n  {\n    type: \"custom\", // Full control (with great power…)\n    action: (request) => {\n      const searchParams = new URLSearchParams(request.nextUrl.searchParams);\n      const value = parseAsFoo.parse(searchParams.get('foo'))\n      searchParams.delete('foo')\n      searchParams.set('bar', parseAsBar.serialize(value))\n      return { applied: true, searchParams }\n    },\n  },\n]);\n\nexport function middleware(request: NextRequest) {\n  const result = applyMigrations(request);\n  if (result.applied) {\n    return result.response;\n  }\n  return NextReponse.next();\n}\n```\n\n## Time Travel\n\nWhen updating the URL, you can choose to either **replace** the current history entry with\nthe updated state, or **push** a new one (this is done with the\n`history: 'push' | 'replace'{:ts}` [option](/docs/options#history) in nuqs).\n\nPushing a new history entry allows you to use the Back/Forward buttons of the browser as an\nundo-redo feature, which looks like Redux Devtools' time travel state debugging.\n\nBut this comes at a price: you now have two sources of updates that can manipulate history:\n\n1. The original UI element that triggered the update\n2. The Back/Forward buttons\n\nThis is often encountered with a `?modalOpen=true` state, where the Back/Forward buttons\ncan conflict with the X button to close the modal. Depending on whether the user opened the\nmodal through the UI or landed on it via a link, the Back button might have different behaviours.\n\n**Breaking the Back button** leads to a frustrating user experience, and handling it properly involves a few steps:\n\n- Being aware that users can enter your app **at any state**\n- From that state, they can use **either** the Back button or your UI\n- Remember that Back/Forward is a contract for **navigation-like interactions**\n\nThe experimental [Navigation API](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API)\nshould hopefully make it easier to handle these cases in the future.\n\n## Conclusion\n\nAchieving type-safety for URL state is not the endgame, but it's the beginning of a journey:\nthere are other things that URL state management libraries and application code need to deal\nwith, to provide truly safe and durable state management.\n\nThis post is an excerpt of my talk at React Paris, watch it here for more details and tips:\n\n<iframe\n  src=\"https://www.youtube-nocookie.com/embed/U__Rwsp8v78?si=sQgg-HJTMoy_mt7K&amp;start=34\"\n  title=\"YouTube video player\"\n  frameBorder=\"0\"\n  allow=\"autoplay; encrypted-media; picture-in-picture; web-share\"\n  referrerPolicy=\"strict-origin-when-cross-origin\"\n  allowFullScreen\n  className='aspect-video w-full max-w-2xl mx-auto'\n/>\n"
  },
  {
    "path": "packages/docs/content/blog/nuqs-2.5-key-isolation.client.tsx",
    "content": "'use client'\n\nimport { Button } from '@/src/components/ui/button'\nimport { parseAsInteger, useQueryState } from 'nuqs'\nimport { NuqsAdapter } from 'nuqs/adapters/react'\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react'\n\nexport function KeyIsolationStyles() {\n  return (\n    <style key=\"flash-fade-keyframes\">{`\n        :root {\n          --flash-color: oklch(0.627 0.265 303.9); /* Tailwind purple-500 */\n        }\n        @keyframes flashOutline {\n          0% {\n            box-shadow: 0 0 6px 4px var(--flash-color);\n          }\n          100% {\n            box-shadow: 0 0 0px 0px transparent;\n          }\n        }\n        .flash-outline {\n          animation: flashOutline 250ms ease-out both;\n        }\n      `}</style>\n  )\n}\n\nexport function DemoSkeleton() {\n  return (\n    <figure className=\"flex animate-pulse flex-wrap justify-around gap-8 rounded-md border border-dashed p-4\">\n      <ComponentSkeleton />\n      <ComponentSkeleton />\n    </figure>\n  )\n}\n\nexport function WithoutKeyIsolationDemo() {\n  return (\n    <figure className=\"flex flex-wrap justify-around gap-2 rounded-md border border-dashed p-2\">\n      <Component id=\"a\" />\n      <Component id=\"b\" />\n    </figure>\n  )\n}\n\nexport function WithKeyIsolationDemo() {\n  const [hydrated, setHydrated] = useState(false)\n  useEffect(() => {\n    setHydrated(true)\n  }, [])\n  if (!hydrated) {\n    return <DemoSkeleton />\n  }\n  return (\n    <NuqsAdapter>\n      <figure className=\"flex flex-wrap justify-around gap-2 rounded-md border border-dashed p-2\">\n        <Component id=\"c\" />\n        <Component id=\"d\" />\n      </figure>\n    </NuqsAdapter>\n  )\n}\n\nfunction Component({ id }: { id: string }) {\n  const [count, setCount] = useQueryState(id, parseAsInteger.withDefault(0))\n  const renders = useRef(0)\n  renders.current += 1\n  const wrapperRef = useRef<HTMLDivElement>(null)\n  useLayoutEffect(() => {\n    const div = wrapperRef.current\n    if (!div) {\n      return\n    }\n    div.classList.remove('flash-outline')\n    div.offsetWidth // Trigger reflow to restart the animation\n    div.classList.add('flash-outline')\n  })\n\n  return (\n    <div className=\"rounded-xl p-1.5 [will-change:box-shadow]\" ref={wrapperRef}>\n      <Button\n        onClick={() => setCount(c => c + 1)}\n        className=\"min-w-42 tabular-nums\"\n      >\n        Increment \"{id}\": {count}\n      </Button>\n    </div>\n  )\n}\n\nfunction ComponentSkeleton() {\n  return (\n    <div className=\"rounded-xl p-1.5\">\n      <Button disabled className=\"min-w-42 tabular-nums\">\n        Loading demo...\n      </Button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/blog/nuqs-2.5.mdx",
    "content": "---\ntitle: nuqs 2.5\ndescription: Debounce, Standard Schema, TanStack Router, Key isolation, Global defaults, and more…\nauthor: François Best\ndate: 2025-08-22\n---\n\nnuqs@2.5.0 is available, try it now:\n\n```npm\nnpm install nuqs@latest\n```\n\nIt's a big release full of long-awaited features, bug fixes & improvements, including:\n\n- ⏱️ [Debounce](#debounce): only send network requests once users stopped typing in search inputs\n- ☑️ [Standard Schema](#standard-schema): connect validation & type inference to external tools (eg: tRPC)\n- ⚡ [Key isolation](#key-isolation): only re-render components when their part of the URL changes\n- 🏝️ [TanStack Router](#tanstack-router) support with type-safe routing _(🧪 experimental)_\n\n## Debounce\n\nWhile nuqs has always had a throttling system in place to adapt to [browers rate-limiting URL updates](/blog/beware-the-url-type-safety-iceberg#time-safety),\nthis system wasn't ideal for **high frequency inputs**, like `<input type=\"search\">{:html}`, or `<input type=\"range\">{:html}` sliders.\n\nFor those cases where the _final value_ is what matters, **debouncing** makes more sense than **throttling**.\n\n<Callout title=\"Do I need debounce?\">\n  Debounce only makes sense for **server-side data fetching** (RSCs & loaders, when combined with [`shallow: false{:ts}`](#shallow)),\n  to control when requests are made to the server. For example: it lets you avoid sending the first\n  character on its own when typing in a search input, by waiting for the user to finish typing.\n\n  The state returned by the hooks is always updated **immediately**: only the network requests\n  sent to the server are debounced.\n\n  If you are **fetching client-side** (eg: with TanStack Query), you'll want to debounce the\n  returned state instead (using a 3rd party `useDebounce` utility hook).\n</Callout>\n\nYou can now specify a new option, `limitUrlUpdates{:ts}`, that replaces `throttleMs{:ts}` and declares either a debouncing or throttling behaviour:\n\n```tsx\n// [!code word:limitUrlUpdates]\nimport { debounce, useQueryState } from 'nuqs'\n\nfunction DebouncedSearchInput() {\n  // Send updates to the server after 250ms of inactivity\n  const [search, setSearch] = useQueryState('search', {\n    defaultValue: '',\n    shallow: false,\n    limitUrlUpdates: debounce(250)\n  })\n\n  // You can still use controlled components:\n  // the local state updates instantly.\n  return (\n    <input\n      type=\"search\"\n      value={search}\n      onChange={e => setSearch(e.target.value)}\n    />\n  )\n}\n```\n\nRead the [complete documentation](/docs/options#rate-limiting-url-updates) for the API, explanations of what it does,\nand a list of tips when working with search inputs _(you might not want to **always** debounce)_.\n\n## Standard Schema\n\nYou can now use your **search params definitions objects**\n(the ones you feed to\n[`useQueryStates{:ts}`](/docs/batching#usequerystates),\n[`createLoader{:ts}`](/docs/server-side#loaders) and\n[`createSerializer{:ts}`](/docs/utilities#serializer-helper)) to derive a **Standard Schema validator**,\nthat you can use for basic runtime validation and type inference with other tools, like:\n- tRPC, to validate procedure inputs when feeding them your URL state\n- TanStack Router's `validateSearch{:ts}` ([see below](#tanstack-router))\n\n```ts\n// [!code word:validateSearchParams]\nimport {\n  createStandardSchemaV1,\n  parseAsInteger,\n  parseAsString,\n} from 'nuqs'\n\n// 1. Define your search params as usual\nexport const searchParams = {\n  searchTerm: parseAsString.withDefault(''),\n  maxResults: parseAsInteger.withDefault(10)\n}\n\n// 2. Create a Standard Schema compatible validator\nexport const validateSearchParams = createStandardSchemaV1(searchParams)\n\n// 3. Use it with other tools, like tRPC:\nrouter({\n  search: publicProcedure.input(validateSearchParams).query(...)\n})\n```\n\nRead the [complete documentation](/docs/utilities#standard-schema) for the options you can pass in.\n\n## Key isolation\n\nAlso known as _fine grained subscriptions_, and pioneered by TanStack Router, key isolation is the idea\nthat components listening to a search param key in the URL should **only re-render when the value for that\nkey changes**.\n\nWithout key isolation, any change of the URL re-renders every component listening to it.\n\nTake a look at those two counter buttons,\nand how clicking one re-renders both, _without key isolation_:\n\nimport { Suspense } from 'react'\nimport { DemoSkeleton, KeyIsolationStyles, WithKeyIsolationDemo, WithoutKeyIsolationDemo } from './nuqs-2.5-key-isolation.client'\n\n<KeyIsolationStyles/>\n\n<Suspense fallback={<DemoSkeleton/>}>\n  <WithoutKeyIsolationDemo />\n</Suspense>\n\nAnd this is what happens **with key isolation**:\n\n<Suspense fallback={<DemoSkeleton/>}>\n  <WithKeyIsolationDemo />\n</Suspense>\n\nKey isolation is now built-in for the following adapters:\n- React SPA\n- React Router (v6 & v7)\n- Remix\n- TanStack Router\n\n<Callout title=\"No Next.js? 😢\">\n Unfortunately, Next.js uses a single Context to carry a `URLSearchParams{:ts}` object, which changes\n reference whenever any search params change, and therefore re-renders every `useSearchParams{:ts}`\n call site.\n\n I'm working with the Next.js team to find solutions to this issue to improve performance for\n everyone (not just nuqs users).\n</Callout>\n\n## TanStack Router\n\nWe've added experimental support for TanStack Router, so you can load and use nuqs-enabled components\nfrom NPM, or shared between different frameworks in a monorepo.\n\nTanStack Router already has great APIs for type-safe URL state management, and we **encourage you to\nuse those in your application code**. This adapter serves mainly as a compatibility layer.\n\nThis also includes _limited_ support for connecting nuqs search params definitions to TSR's type-safe routing,\nvia the Standard Schema interface.\n\nRefer to the [complete documentation](/docs/adapters#tanstack-router) for what is supported.\n\n## Other changes\n\n### Global defaults for options\n\nYou can now specify different defaults for some [options](/docs/options), at the adapter level:\n\n```tsx\n// [!code word:defaultOptions]\n<NuqsAdapter\n  defaultOptions={{\n    shallow: false,                 // Always send network requests on updates\n    scroll: true,                   // Always scroll to the top of the page on updates\n    clearOnDefault: false,          // Keep default values in the URL\n    limitUrlUpdates: throttle(250), // Increase global throttle\n  }}\n>\n  {children}\n</NuqsAdapter>\n```\n\n### Preview support for Next.js 15.5 typed routes\n\nType-safe routing is now available as an option in Next.js 15.5.\n\nWhile I'm still working on designing an API to support this elegantly,\na little change to the serializer types can allow you to experiment with\nit in userland, using a copy-pastable utility function:\n\n```ts title=\"src/typed-links.ts\"\n// Copy this in your codebase\nimport { Route } from 'next'\nimport {\n  createSerializer,\n  type CreateSerializerOptions,\n  type ParserMap\n} from 'nuqs/server'\n\nexport function createTypedLink<Parsers extends ParserMap>(\n  route: Route,\n  parsers: Parsers,\n  options: CreateSerializerOptions<Parsers> = {}\n) {\n  const serialize = createSerializer<Parsers, Route, Route>(parsers, options)\n  return serialize.bind(null, route)\n}\n```\n\nUsage:\n\n```tsx\nimport { createTypedLink } from '@/src/typed-links'\nimport { parseAsFloat, parseAsIsoDate, parseAsString, type UrlKeys } from 'nuqs'\n\n// Reuse your search params definitions objects & urlKeys:\nconst searchParams = {\n  latitude: parseAsFloat.withDefault(0),\n  longitude: parseAsFloat.withDefault(0),\n}\nconst urlKeys: UrlKeys<typeof searchParams> = {\n  // Define shorthands in the URL\n  latitude: 'lat',\n  longitude: 'lng'\n}\n\n// This is a function bound to /map, with those search params & mapping:\n// [!code word:getMapLink]\nconst getMapLink = createTypedLink('/map', searchParams, { urlKeys })\n\nfunction MapLinks() {\n  return (\n    <Link\n      href={\n        getMapLink({ latitude: 48.86, longitude: 2.35 })\n        // → /map?lat=48.86&lng=2.35\n      }\n    >\n      Paris, France\n    </Link>\n  )\n}\n```\n\nThis is based on the same technique I used on React Router's type-safe `href`\nutility in this video:\n\n<iframe\n  src=\"https://www.youtube-nocookie.com/embed/vmONxheVFxQ\"\n  title=\"YouTube video player\"\n  frameBorder=\"0\"\n  allow=\"autoplay; encrypted-media; picture-in-picture; web-share\"\n  referrerPolicy=\"strict-origin-when-cross-origin\"\n  allowFullScreen\n  className='aspect-video w-full max-w-2xl mx-auto'\n/>\n\nI'll open an RFC discussion soon to define the API, with the goals in mind that:\n- It should support both Next.js & React Router typed routes <small>_(if we could connect to TSR too that'd be nice 👀)_</small>\n- It should handle static, dynamic & catch-all routes, with type-safe pathname params, search params, and hash.\n\n### Dependencies & bundle size\n\nnuqs is now a **zero runtime dependencies** library! 🙌\n\nWhile this release packed a lot of new features, we kept it\nunder **5.5kB** (minified + gzipped).\n\n## Full changelog\n\n### Features\n\nimport {PullRequestLine} from '@/src/components/ui/pr-line'\n\n<ul className=\"not-prose list-none space-y-1.5\">\n  <PullRequestLine number=\"855\"/>\n  <PullRequestLine number=\"900\"/>\n  <PullRequestLine number=\"953\"/>\n  <PullRequestLine number=\"965\"/>\n  <PullRequestLine number=\"1038\"/>\n  <PullRequestLine number=\"1062\"/>\n  <PullRequestLine number=\"1066\"/>\n  <PullRequestLine number=\"1079\"/>\n  <PullRequestLine number=\"1083\"/>\n</ul>\n\n### Bug fixes\n\n<ul className=\"not-prose list-none space-y-1.5\">\n  <PullRequestLine number=\"996\"><wbr/><small className='text-gray-500'>(helps with ESM/CJS interop)</small></PullRequestLine>\n  <PullRequestLine number=\"1057\" />\n  <PullRequestLine number=\"1063\" />\n  <PullRequestLine number=\"1073\" />\n</ul>\n\n### Documentation\n\n<ul className=\"not-prose list-none space-y-1.5\">\n  <PullRequestLine number=\"787\">→ <a href=\"/docs/community-adapters/inertia\" className=\"text-sm underline font-medium\">Read the docs</a></PullRequestLine>\n  <PullRequestLine number=\"976\" />\n  <PullRequestLine number=\"1000\" />\n  <PullRequestLine number=\"1004\" />\n  <PullRequestLine number=\"1005\" />\n  <PullRequestLine number=\"1017\" />\n  <PullRequestLine number=\"1021\" />\n  <PullRequestLine number=\"1025\" />\n  <PullRequestLine number=\"1027\" />\n  <PullRequestLine number=\"1032\" />\n  <PullRequestLine number=\"1037\" />\n  <PullRequestLine number=\"1041\" />\n  <PullRequestLine number=\"1043\" />\n  <PullRequestLine number=\"1046\" />\n  <PullRequestLine number=\"1051\" />\n  <PullRequestLine number=\"1052\" />\n  <PullRequestLine number=\"1056\" />\n  <PullRequestLine number=\"1058\" />\n  <PullRequestLine number=\"1070\" />\n  <PullRequestLine number=\"1082\" /> {/* so meta */}\n</ul>\n\n### Other changes\n\n<ul className=\"not-prose list-none space-y-1.5\">\n  <PullRequestLine number=\"985\" />\n  <PullRequestLine number=\"990\" />\n  <PullRequestLine number=\"1011\" />\n  <PullRequestLine number=\"1029\" />\n  <PullRequestLine number=\"1033\" />\n  <PullRequestLine number=\"1065\" />\n  <PullRequestLine number=\"1067\" />\n  <PullRequestLine number=\"1074\" />\n  <PullRequestLine number=\"1077\" />\n  <PullRequestLine number=\"1078\" />\n  <PullRequestLine number=\"1080\" />\n  <PullRequestLine number=\"1081\" />\n  <PullRequestLine number=\"1086\"><small className='text-gray-500 italic'>Congrats on your first OSS contribution! 🙌</small></PullRequestLine>\n</ul>\n\n{/* Raw data in case the PullRequestLine component becomes too expensive */}\n{/*\n### Features\n\n- [#855](https://github.com/47ng/nuqs/pull/855): Key isolation, by [@franky47](https://github.com/franky47)\n- [#900](https://github.com/47ng/nuqs/pull/900): Debounce, by [@franky47](https://github.com/franky47)\n- [#953](https://github.com/47ng/nuqs/pull/953): Add support for TanStack Router, by [@ahmedrowaihi](https://github.com/ahmedrowaihi)\n- [#965](https://github.com/47ng/nuqs/pull/965): Add Standard Schema interface, by [@franky47](https://github.com/franky47)\n- [#1038](https://github.com/47ng/nuqs/pull/1038): Add const modifier to literal parsers to auto-infer their arguments as literals, by [@neefrehman](https://github.com/neefrehman)\n- [#1062](https://github.com/47ng/nuqs/pull/1062): Export ./package.json in exports field for Module Federation support, by [@AfeefRazick](https://github.com/AfeefRazick)\n- [#1066](https://github.com/47ng/nuqs/pull/1066): defaultOptions for NuqsAdapter, by [@TkDodo](https://github.com/TkDodo)\n- [#1079](https://github.com/47ng/nuqs/pull/1079): Add support for more global default options at the adapter level, by [@franky47](https://github.com/franky47)\n- [#1083](https://github.com/47ng/nuqs/pull/1083): Allow specifying a different base type for the serializer, by [@franky47](https://github.com/franky47)\n\n### Bug fixes\n\n- [#996](https://github.com/47ng/nuqs/pull/996): Replace require by default conditional export field, by [@stefan-schubert-sbb](https://github.com/stefan-schubert-sbb)\n- [#1057](https://github.com/47ng/nuqs/pull/1057): Type inference for defaultValue of object syntax, by [@TkDodo](https://github.com/TkDodo)\n- [#1063](https://github.com/47ng/nuqs/pull/1063): Remove esm-only on TanStack Router export, by [@franky47](https://github.com/franky47)\n- [#1073](https://github.com/47ng/nuqs/pull/1073): Handle JSON in TanStack Router, by [@franky47](https://github.com/franky47)\n\n### Documentation\n\n- [#787](https://github.com/47ng/nuqs/pull/787): Add Inertia community adapter, by [@Joehoel](https://github.com/Joehoel)\n- [#976](https://github.com/47ng/nuqs/pull/976): Add blog section, by [@franky47](https://github.com/franky47)\n- [#1000](https://github.com/47ng/nuqs/pull/1000): Vercel OSS program, by [@franky47](https://github.com/franky47)\n- [#1004](https://github.com/47ng/nuqs/pull/1004): Add code.store & oxom as sponsors 💖, by [@franky47](https://github.com/franky47)\n- [#1005](https://github.com/47ng/nuqs/pull/1005): The URL type-safety iceberg, by [@franky47](https://github.com/franky47)\n- [#1017](https://github.com/47ng/nuqs/pull/1017): Fix non-null assertions, by [@franky47](https://github.com/franky47)\n- [#1021](https://github.com/47ng/nuqs/pull/1021): Add Deploy on Vercel button, by [@franky47](https://github.com/franky47)\n- [#1025](https://github.com/47ng/nuqs/pull/1025): Extend next-app example to include more features, by [@I-3B](https://github.com/I-3B)\n- [#1027](https://github.com/47ng/nuqs/pull/1027): Fix mobile navbar collapse & sticky, by [@franky47](https://github.com/franky47)\n- [#1032](https://github.com/47ng/nuqs/pull/1032): Fix 500 error on Vercel ISR, by [@franky47](https://github.com/franky47)\n- [#1037](https://github.com/47ng/nuqs/pull/1037): Add Aurora Scharff as a sponsor 💖, by [@franky47](https://github.com/franky47)\n- [#1041](https://github.com/47ng/nuqs/pull/1041): Fix transition docs to not call parser as function, by [@phelma](https://github.com/phelma)\n- [#1043](https://github.com/47ng/nuqs/pull/1043): Title is hidden behind headers on mobile, by [@awosky](https://github.com/awosky)\n- [#1046](https://github.com/47ng/nuqs/pull/1046): Update NUQS-404.md, by [@dmytro-palaniichuk](https://github.com/dmytro-palaniichuk)\n- [#1051](https://github.com/47ng/nuqs/pull/1051): Add effect schema parser page, by [@ethanniser](https://github.com/ethanniser)\n- [#1052](https://github.com/47ng/nuqs/pull/1052): Debounce docs edits, by [@franky47](https://github.com/franky47)\n- [#1056](https://github.com/47ng/nuqs/pull/1056): Migrate docs to Fumadocs 15, Tailwind CSS v4, by [@fuma-nama](https://github.com/fuma-nama)\n- [#1058](https://github.com/47ng/nuqs/pull/1058): Fix default value for shallow in React Router disclaimer, by [@franky47](https://github.com/franky47)\n- [#1070](https://github.com/47ng/nuqs/pull/1070): Prevent NaN appearing in pagination example, by [@87xie](https://github.com/87xie)\n- [#1082](https://github.com/47ng/nuqs/pull/1082): Add nuqs 2.5 release blog post, by [@franky47](https://github.com/franky47)\n\n### Other changes\n- [#985](https://github.com/47ng/nuqs/pull/985): Use React Compiler RC, by [@franky47](https://github.com/franky47)\n- [#990](https://github.com/47ng/nuqs/pull/990): Replace tsup with tsdown, by [@franky47](https://github.com/franky47)\n- [#1011](https://github.com/47ng/nuqs/pull/1011): Add RSS feed auto-discovery, by [@franky47](https://github.com/franky47)\n- [#1029](https://github.com/47ng/nuqs/pull/1029): Fix API stability test, by [@franky47](https://github.com/franky47)\n- [#1033](https://github.com/47ng/nuqs/pull/1033): Linting PR titles, by [@franky47](https://github.com/franky47)\n- [#1065](https://github.com/47ng/nuqs/pull/1065): Add TanStack Router to og:images, by [@franky47](https://github.com/franky47)\n- [#1067](https://github.com/47ng/nuqs/pull/1067): Improve stats page, by [@franky47](https://github.com/franky47)\n- [#1074](https://github.com/47ng/nuqs/pull/1074): Configure MDX types, by [@remcohaszing](https://github.com/remcohaszing)\n- [#1077](https://github.com/47ng/nuqs/pull/1077): Track beta versions adoption in the stats page, by [@franky47](https://github.com/franky47)\n- [#1078](https://github.com/47ng/nuqs/pull/1078): Update dependencies, by [@franky47](https://github.com/franky47)\n- [#1080](https://github.com/47ng/nuqs/pull/1080): Pass children as config in createElement to avoid ts-expect-error, by [@franky47](https://github.com/franky47)\n- [#1081](https://github.com/47ng/nuqs/pull/1081): Reduce the bundle size, by [@franky47](https://github.com/franky47)\n- [#1086](https://github.com/47ng/nuqs/pull/1086): Add publint step to CI workflow, by [@Amirmohammad-Bashiri](https://github.com/Amirmohammad-Bashiri)\n*/}\n\n<hr />\n\n## What's next?\n\nLong standing issues and feature requests include:\n\n- Support for **native arrays** by repeating keys in the URL (eg: `?foo=bar&foo=egg` gives you `['bar', 'egg']{:ts}`)\n- **Runtime validation** with Standard Schema (Zod, Valibot, ArkType etc), to validate what TypeScript can't represent (like number ranges & string formats).\n- Support for **typed links** in Next.js 15.5 and React Router's `href` utility.\n\n## Thanks\n\nI want to thank [sponsors](https://github.com/sponsors/franky47),\n[contributors](https://github.com/47ng/nuqs/graphs/contributors)\nand people who raised issues, discussions and reviewed PRs on\n[GitHub](https://github.com/47ng/nuqs), [Bluesky](https://bsky.app/profile/nuqs.dev) and [X/Twitter](https://x.com/nuqs47ng).\nYou are the growing community that drives this project forward,\nand I couldn't be happier with the response.\n\n### Sponsors\n\nimport { InlineSponsorsList } from '@/src/app/(pages)/_landing/sponsors'\nimport { VercelOssBadge } from '@/src/components/vercel-oss-badge'\n\n<a href=\"https://vercel.com/blog/spring25-oss-program\">\n  <VercelOssBadge className='mt-8'/>\n</a>\n\n<InlineSponsorsList className=\"not-prose my-8\" />\n\nThanks to these amazing people and companies, I'm able to dedicate more time to this project and make it better for everyone.\nJoin them on [💖 GitHub Sponsors](https://github.com/sponsors/franky47)!\n\n### Contributors\n\nHuge thanks to [@87xie](https://github.com/87xie), [@AfeefRazick](https://github.com/AfeefRazick), [@ahmedrowaihi](https://github.com/ahmedrowaihi), [@Amirmohammad-Bashiri](https://github.com/Amirmohammad-Bashiri), [@AmruthPillai](https://github.com/AmruthPillai), [@an-h2](https://github.com/an-h2), [@anhskohbo](https://github.com/anhskohbo), [@awosky](https://github.com/awosky), [@brandanking-decently](https://github.com/brandanking-decently), [@devhasson](https://github.com/devhasson), [@didemkkaslan](https://github.com/didemkkaslan), [@dinogit](https://github.com/dinogit), [@dmytro-palaniichuk](https://github.com/dmytro-palaniichuk), [@Elya29](https://github.com/Elya29), [@ericwang401](https://github.com/ericwang401), [@ethanniser](https://github.com/ethanniser), [@fuma-nama](https://github.com/fuma-nama), [@gensmusic](https://github.com/gensmusic), [@I-3B](https://github.com/I-3B), [@jaberamin9](https://github.com/jaberamin9), [@Joehoel](https://github.com/Joehoel), [@Kavan72](https://github.com/Kavan72), [@krisnaw](https://github.com/krisnaw), [@Manjit2003](https://github.com/Manjit2003), [@neefrehman](https://github.com/neefrehman), [@phelma](https://github.com/phelma), [@remcohaszing](https://github.com/remcohaszing), [@SeanCassiere](https://github.com/SeanCassiere), [@snelsi](https://github.com/snelsi), [@stefan-schubert-sbb](https://github.com/stefan-schubert-sbb), [@thewebartisan7](https://github.com/thewebartisan7), [@TkDodo](https://github.com/TkDodo), [@vanquishkuso](https://github.com/vanquishkuso), and [@Willem-Jaap](https://github.com/Willem-Jaap) for helping!\n"
  },
  {
    "path": "packages/docs/content/blog/nuqs-2.mdx",
    "content": "---\ntitle: nuqs 2\ndescription: Opening up to other React frameworks\nauthor: François Best\ndate: 2024-10-22\n---\n\nnuqs@2.0.0 is available, try it now:\n\n```npm\nnpm install nuqs@latest\n```\n\nIt's packing exciting features & improvements, including:\n\n- [Support for other React frameworks](#hello-react): Next.js, React SPA, Remix, React Router, and more to come\n- A built-in [testing adapter](#testing) to unit-test your components in isolation\n- [Bundle size improvements](#bundle-size-improvements)\n- Interactive documentation, with [community parsers](/docs/parsers/community)\n\n<hr />\n\n## Hello, React! 👋 ⚛️ [#hello-react]\n\nnuqs started as a Next.js-only hook, and v2 brings compatibility for other React frameworks:\n\n- Next.js 14 & 15 (app & pages routers)\n- React SPA\n- Remix\n- React Router\n\nNo code change is necessary in components that use nuqs hooks,\nmaking them **universal** across all supported frameworks.\n\nThe only new requirement is to wrap your React tree with an\n[adapter](/docs/adapters) for your framework.\n\nExample for a React SPA with Vite:\n\n```tsx title=\"src/main.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/react'\n\ncreateRoot(document.getElementById('root')!).render(\n  <NuqsAdapter>\n    <App />\n  </NuqsAdapter>\n)\n```\n\n<Callout>\n  The [adapters documentation](/docs/adapters) has examples for all supported\n  frameworks.\n</Callout>\n\n## Testing\n\nOne of the major pain points with nuqs v1 was testing components that used its hooks.\n\nNuqs v2 comes with a built-in [testing adapter](/docs/testing) that mocks URL behaviours,\nallowing you to test your components in isolation, outside of any framework runtime.\n\nYou can use it with any unit testing framework that renders React components\n(I recommend [Vitest](https://vitest.dev) & [Testing Library](https://testing-library.com/)).\n\n```tsx title=\"counter-button.test.tsx\"\n// [!code word:NuqsTestingAdapter]\nimport { render, screen } from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'\nimport { describe, expect, it, vi } from 'vitest'\nimport { CounterButton } from './counter-button'\n\nit('should increment the count when clicked', async () => {\n  const user = userEvent.setup()\n  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()\n  render(<CounterButton />, {\n    // 1. Setup the test by passing initial search params / querystring:\n    wrapper: ({ children }) => (\n      <NuqsTestingAdapter searchParams=\"?count=42\" onUrlUpdate={onUrlUpdate}>\n        {children}\n      </NuqsTestingAdapter>\n    )\n  })\n  // 2. Act\n  const button = screen.getByRole('button')\n  await user.click(button)\n  // 3. Assert changes in the state and in the (mocked) URL\n  expect(button).toHaveTextContent('count is 43')\n  expect(onUrlUpdate).toHaveBeenCalledOnce()\n  expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=43')\n  expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('43')\n  expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push')\n})\n```\n\nThe adapter conforms to the **setup** / **act** / **assert** testing strategy, allowing you\nto:\n\n1. Set the initial URL search params\n2. Let your test framework perform actions on your component\n3. Asserting on how the URL was changed as a result\n\n## Breaking changes & migration\n\nThe biggest breaking change is the introduction of [adapters](/docs/adapters).\nAnother one is related to deprecated APIs.\n\nThe `next-usequerystate` package that started this journey is no longer updated.\nAll updates are now published under the `nuqs` package name.\n\nThe minimum version of Next.js supported is now 14.2.0. It is compatible with\nNext.js 15, including the async `searchParams{:ts}` page prop in the [server-side cache](/docs/server-side).\n\nThere are some important behaviour changes, based on feedback from the community:\n\n- [`clearOnDefault{:ts}`](/docs/options#clear-on-default) is now `true{:ts}` by default\n- [`startTransition{:ts}`](/docs/options#transitions) no longer sets `shallow: false{:ts}`\n- [`parseAsJson{:ts}`](/docs/parsers/built-in#json) now requires a validation function\n\n<Callout>\n  Read the complete [migration guide](/docs/migrations/v2) to update your\n  applications.\n</Callout>\n\n## Bundle size improvements\n\nBy moving to **ESM-only**, and dropping hacks needed to support older versions of Next.js,\nthe bundle size is now **20% smaller** than v1. It's also **side-effects free** and **tree-shakable**.\n\n## What's next?\n\nThe community and I have a lot of ideas for the future of nuqs, including:\n\n- A unified, scalable, type-safe routing experience in all supported React frameworks\n- Community-contributed parsers & adapters\n- New options: debouncing, global defaults override\n- Middleware to migrate old URLs to new ones\n- Better Zod integration for type-safe & runtime-safe validation\n\n## Thanks\n\nI want to thank [sponsors](https://github.com/sponsors/franky47),\n[contributors](https://github.com/47ng/nuqs/graphs/contributors)\nand people who raised issues and discussions on\n[GitHub](https://github.com/47ng/nuqs) and [X/Twitter](https://x.com/nuqs47ng).\nYou are the growing community that drives this project forward,\nand I couldn't be happier with the response.\n\n### Sponsors\n\n- [Pontus Abrahamsson](https://x.com/pontusab), founder of [Midday.ai](https://midday.ai)\n- [Carl Lindesvard](https://x.com/CarlLindesvard), founder of [OpenPanel](https://openpanel.dev)\n- [Robin Wieruch](https://x.com/rwieruch), author of [The Road to Next](https://www.road-to-next.com/)\n- [Yoann Fleury](https://x.com/YoannFleuryDev)\n- [Sunghyun Cho](https://github.com/anaclumos)\n- [Jalol](https://github.com/mirislomovmirjalol)\n\nThanks to these amazing people, I'm able to dedicate more time to this project and make it better for everyone.\nJoin them on [GitHub Sponsors](https://github.com/sponsors/franky47)!\n\n### Contributors\n\nHuge thanks to [@andreisocaciu](https://github.com/andreisocaciu), [@tordans](https://github.com/tordans), [@prasannamestha](https://github.com/prasannamestha), @Talent30, [@neefrehman](https://github.com/neefrehman), [@chbg](https://github.com/chbg), [@dopry](https://github.com/dopry), [@weisisheng](https://github.com/weisisheng), [@hugotiger](https://github.com/hugotiger), [@iuriizaporozhets](https://github.com/iuriizaporozhets), [@rikbrown](https://github.com/rikbrown), [@mateogianolio](https://github.com/mateogianolio), [@timheerwagen](https://github.com/timheerwagen), [@psdmsft](https://github.com/psdmsft), and [@psdewar](https://github.com/psdewar) for helping!\n"
  },
  {
    "path": "packages/docs/content/blog/open-source-pledge-recipient.tsx",
    "content": "type OpenSourcePledgeRecipientProps = {\n  handle: string\n  name: string\n}\n\nexport function OpenSourcePledgeRecipient({\n  handle,\n  name\n}: OpenSourcePledgeRecipientProps) {\n  return (\n    <span>\n      <img\n        src={`https://github.com/${handle}.png`}\n        alt={`${name}'s avatar`}\n        role=\"presentation\"\n        width=\"16px\"\n        height=\"16px\"\n        className=\"not-prose my-1.5 mr-2 inline size-6 rounded-full align-middle\"\n      />\n      <a href={`https://github.com/sponsors/${handle}`}>{name}</a>\n    </span>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/blog/open-source-pledge.mdx",
    "content": "---\ntitle: Signing the Open Source Pledge\ndescription: Giving back to maintainers of the OSS projects nuqs depends on.\nauthor: François Best\ndate: 2025-11-27\n---\n\nToday is Thanksgiving, and one thing I'm thankful for is this **open-source community** I'm lucky to be a part of.\n\nAround the world and online, I have met folks who build cool things on the web,\nwhether OSS SaaS products or foundational libraries and building blocks for all to use and build on top of.\nThis open sharing of knowledge, code, and tools is one of the reasons why I left the music industry\n8 years ago and settled on the Web as my building platform of choice.\n\nAt the beginning of 2025, I had two goals:\n1. Speak at a conference _(done, three times 🇫🇷🇺🇸🇬🇧)_\n2. Sign the Open Source Pledge\n\n<a href=\"https://opensourcepledge.com\" className=\"p-8 max-w-xl mx-auto invert hue-rotate-180 dark:hue-rotate-0 dark:invert-0 block\">\n![Open Source Pledge logo](./opensourcepledge.png)\n</a>\n\nThis year has been a wild ride, but the combination of being backed by\n[GitHub sponsors](https://github.com/sponsors/franky47) (thank you! 💖)\nand the recent winning of the Clerk Hackathon allows me to sign the\n[Open Source Pledge](https://opensourcepledge.com) on behalf of nuqs for the first time this year.\n\nI'm giving a total of **2700€** (USD $3,132) divided equally between the\nmaintainers of dependencies that help me build nuqs, and/or that I use in my client projects:\n\nimport { OpenSourcePledgeRecipient as OSSPR } from './open-source-pledge-recipient'\n\n<ul className=\"list-none pl-0 ml-0\">\n  <li><OSSPR name=\"Andrey Sitnik\" handle=\"ai\" /> for [`size-limit`](https://npmjs.org/package/size-limit), to keep the bundle size small.</li>\n  <li><OSSPR name=\"Anthony Fu\" handle=\"antfu\" />'s [collective](https://opencollective.com/antfu) for [`vitest`](https://vitest.dev), to make sure everything works fine.</li>\n  <li><OSSPR name=\"Artem Zakharchenko\" handle=\"kettanaito\" /> for [`msw`](https://mswjs.io), the best network mocking library ever made.</li>\n  <li><OSSPR name=\"Bjorn Lu\" handle=\"bluwy\" /> for [`publint`](https://publint.dev), to make sure I follow best practices.</li>\n  <li><OSSPR name=\"Daishi Kato\" handle=\"dai-shi\" /> for [`jotai`](https://jotai.org), the gateway drug to signals for React devs.</li>\n  <li><OSSPR name=\"Dominik Dorfmeister\" handle=\"TkDodo\" /> for [`@tanstack/react-query`](https://tanstack.com/query) & contributing to nuqs.</li>\n  <li><OSSPR name=\"fregante\" handle=\"fregante\" /> for [Refined GitHub](https://github.com/refined-github/refined-github), so useful I can't use the stock UI anymore.</li>\n  <li><OSSPR name=\"Fuma Nama\" handle=\"fuma-nama\" /> for [`fumadocs`](https://fumadocs.dev), which makes these interactive docs possible.</li>\n  <li><OSSPR name=\"Kevin Deng\" handle=\"sxzz\" /> for [`tsdown`](https://tsdown.dev), the only bundler that ticked all the boxes.</li>\n  <li><OSSPR name=\"Lars Kappert\" handle=\"webpro\" /> for [`knip`](https://knip.dev), to find unused code and ✂️ knip it off.</li>\n  <li><OSSPR name=\"Matt Travi\" handle=\"travi\" /> for [`semantic-release`](https://semantic-release.gitbook.io/semantic-release), that lets me never think about version numbers again.</li>\n  <li><OSSPR name=\"Mohammad Bagher\" handle=\"Aslemammad\" /> for [`pkg.pr.new`](https://pkg.pr.new), the maintainer's superpower: preview builds on PRs.</li>\n  <li><OSSPR name=\"Nicolas Dubien\" handle=\"dubzzz\" /> for [`fast-check`](https://fast-check.dev), which fuzzy-tests URL encoding.</li>\n  <li><OSSPR name=\"SaltyAom\" handle=\"SaltyAom\" /> for [`elysia`](https://elysiajs.com), a cool type-safe API framework I want to play more with.</li>\n  <li><OSSPR name=\"Tom Lienard\" handle=\"QuiiBz\" /> for [`sherif`](https://npmjs.org/package/sherif) which enforces the Law in the nuqs monorepo.</li>\n  <li><OSSPR name=\"Fastify\" handle=\"fastify\" />, that I use daily for client work as a backend framework.</li>\n  <li><OSSPR name=\"Node.js\" handle=\"nodejs\" />, the heart of our FLOSS, community-driven ecosystem.</li>\n  <li><OSSPR name=\"PNPM\" handle=\"pnpm\" />, my package manager of choice that links it all together.</li>\n</ul>\n\n<br/>\n\nTo that list, we can add the following other OSS sponsorship payments I made during the year:\n\n<ul className=\"list-none pl-0 ml-0\">\n  <li><OSSPR name=\"Alem Tuzlak\" handle=\"alemtuzlak\" />: $10</li>\n  <li><OSSPR name=\"Dai\" handle=\"dai\" />: $174 <small>(I messed up `@dai-shi`'s sponsorship with the bulk CSV import, and so `@dai` got a lucky sponsorship 🙌)</small></li>\n</ul>\n\n<Callout title=\"Open Source Pledge summary for 2025\" id=\"total\">\n  In total, this amounts to <strong>$3,316</strong> paid on behalf of the nuqs project (with myself as the sole \"employee\") to OSS maintainers.\n</Callout>\n\nThank you to all of those wonderful folks and organisations, and happy Thanksgiving. 🫶"
  },
  {
    "path": "packages/docs/content/docs/about.mdx",
    "content": "---\ntitle: About\ndescription: About the nuqs library\n---\n\n## License\n\nReleased under the [MIT License](https://github.com/47ng/nuqs/blob/next/LICENSE), made with ❤️ by [François Best](https://francoisbest.com).\n\nUsing this package at work ? [Sponsor me](https://github.com/sponsors/franky47)\nto help with support and maintenance.\n\n## Contributors\n\n<img\n  alt=\"Project analytics and stats\"\n  src=\"https://repobeats.axiom.co/api/embed/3ee740e4729dce3992bfa8c74645cfebad8ba034.svg\"\n/>\n\n\n## About the name\n\n### How is it pronounced?\n\nUp to you. I say \"nucks\" (like ducks 🦆🦆).\n\n\n### What does nuqs mean?\n\n> **Never underestimate query strings**.\n\nKidding aside, it's the initials of the original name package name, `Next-UseQueryState`,\nwhich was too long and annoying to type.\n\nI realised after the fact that the word `nuqs` in Urdu & Arabic means \"flaw\" or \"defect\".\nIt's a good reminder that:\n\n> Perfection is a direction, not a destination.\n>\n> <figcaption>-- [James Wright](https://www.youtube.com/shorts/CH_d9lVRLWk)</figcaption>\n\nI probably should have checked the meaning of the word before using it,\nand apologise to any Urdu/Arabic-speaking user who might find it offensive.\n\nIt's also Klingon for \"What?!\", the kind of reaction you get when you\nmove from `useState{:ts}` to URL state for the first time. 🖖\n\n"
  },
  {
    "path": "packages/docs/content/docs/adapters.mdx",
    "content": "---\ntitle: Adapters\ndescription: Using nuqs in your React framework of choice\n---\n\nimport {\n  NextJS,\n  ReactRouter,\n  ReactRouterV7,\n  ReactSPA,\n  Remix,\n  TanStackRouter,\n  Vitest,\n} from '@/src/components/frameworks'\n\nSince version 2, you can now use nuqs in the following React frameworks, by\nwrapping it with a `NuqsAdapter{:ts}` context provider:\n\n- <NextJS className='inline mr-1.5' role=\"presentation\"/> [Next.js (app router)](#nextjs-app-router)\n- <NextJS className='inline mr-1.5' role=\"presentation\"/> [Next.js (pages router)](#nextjs-pages-router)\n- <ReactSPA className='inline mr-1.5' role=\"presentation\" /> [React SPA (eg: with Vite)](#react-spa)\n- <Remix className='inline mr-1.5' role=\"presentation\" /> [Remix](#remix)\n- <ReactRouter className='inline mr-1.5' role=\"presentation\" /> [React Router v6](#react-router-v6)\n- <ReactRouterV7 className='inline mr-1.5' role=\"presentation\" /> [React Router v7](#react-router-v7)\n- <TanStackRouter className='inline mr-1.5 not-prose' role=\"presentation\"/> [TanStack Router](#tanstack-router)\n\n## <NextJS className='inline mr-1.5 -mt-1' role=\"presentation\" /> Next.js [#nextjs]\n\n### App router [#nextjs-app-router]\n\nWrap your `{children}{:ts}` with the `NuqsAdapter{:ts}` component in your root layout file:\n\n```tsx title=\"src/app/layout.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport { type ReactNode } from 'react'\n\nexport default function RootLayout({\n  children\n}: {\n  children: ReactNode\n}) {\n  return (\n    <html>\n      <body>\n        <NuqsAdapter>{children}</NuqsAdapter>\n      </body>\n    </html>\n  )\n}\n```\n\n### Pages router [#nextjs-pages-router]\n\nWrap the `<Component>{:ts}` page outlet with the `NuqsAdapter{:ts}` component in your `_app.tsx` file:\n\n```tsx title=\"src/pages/_app.tsx\"\n// [!code word:NuqsAdapter]\nimport type { AppProps } from 'next/app'\nimport { NuqsAdapter } from 'nuqs/adapters/next/pages'\n\nexport default function MyApp({ Component, pageProps }: AppProps) {\n  return (\n    <NuqsAdapter>\n      <Component {...pageProps} />\n    </NuqsAdapter>\n  )\n}\n```\n\n### Unified (router-agnostic) [#nextjs-unified]\n\nIf your Next.js app uses **both the app and pages routers** and the adapter needs\nto be mounted in either, you can import the unified adapter, at the cost\nof a slightly larger bundle size (~100B).\n\n```tsx\nimport { NuqsAdapter } from 'nuqs/adapters/next'\n```\n\n<br/>\n\nThe main reason for adapters is to open up nuqs to other React frameworks:\n\n## <ReactSPA className='inline mr-1.5 -mt-1' role=\"presentation\"/> React SPA [#react-spa]\n\nExample, with Vite:\n\n```tsx title=\"src/main.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/react'\n\ncreateRoot(document.getElementById('root')!).render(\n  <NuqsAdapter>\n    <App />\n  </NuqsAdapter>\n)\n```\n\n<Callout title=\"Note\">\nBecause there is no known server in this configuration, the\n[`shallow: false{:ts}`](/docs/options#shallow) option will have no effect.\n\nSee below for some options:\n</Callout>\n\n### Full page navigation on <br className='hidden [#nd-toc_&]:block'/>`shallow: false{:ts}` [#full-page-navigation-on-shallow-false]\n\n<FeatureSupportMatrix introducedInVersion='2.4.0' support={{\n  supported: true,\n  frameworks: ['React SPA']\n}}/>\n\nYou can specify a flag to perform a full-page navigation when\nupdating query state configured with `shallow: false{:ts}`, to notify the web server\nthat the URL state has changed, if it needs it for server-side rendering other\nparts of the application than the static React bundle:\n\n```tsx title=\"src/main.tsx\"\n// [!code word:fullPageNavigationOnShallowFalseUpdates]\ncreateRoot(document.getElementById('root')!).render(\n  <NuqsAdapter fullPageNavigationOnShallowFalseUpdates>\n    <App />\n  </NuqsAdapter>\n)\n```\n\nThis may be useful for servers not written in JavaScript, like Django (Python),\nRails (Ruby), Laravel (PHP), Phoenix (Elixir) etc...\n\n## <Remix className='inline mr-1.5 -mt-1' role=\"presentation\"/> Remix [#remix]\n\n```tsx title=\"app/root.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/remix'\n\n// ...\n\nexport default function App() {\n  return (\n    <NuqsAdapter>\n      <Outlet />\n    </NuqsAdapter>\n  )\n}\n```\n\n## <ReactRouter className='inline mr-1.5 -mt-1' role=\"presentation\"/> React Router v6 [#react-router-v6]\n\n```tsx title=\"src/main.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/react-router/v6'\nimport { createBrowserRouter, RouterProvider } from 'react-router-dom'\nimport App from './App'\n\nconst router = createBrowserRouter([\n  {\n    path: '/',\n    element: <App />\n  }\n])\n\nexport function ReactRouter() {\n  return (\n    <NuqsAdapter>\n      <RouterProvider router={router} />\n    </NuqsAdapter>\n  )\n}\n```\n\n<Callout>\n\n Only `BrowserRouter` is supported. There may be support for `HashRouter`\n in the future (see issue [#810](https://github.com/47ng/nuqs/issues/810)), but\n support for `MemoryRouter` is not planned.\n\n</Callout>\n\n## <ReactRouterV7 className='inline mr-1.5 -mt-1' role=\"presentation\"/> React Router v7 [#react-router-v7]\n\n```tsx title=\"app/root.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/react-router/v7'\nimport { Outlet } from 'react-router'\n\n// ...\n\nexport default function App() {\n  return (\n    <NuqsAdapter>\n      <Outlet />\n    </NuqsAdapter>\n  )\n}\n```\n\n<Callout type=\"warn\" title=\"Deprecation notice\">\n  The generic import `nuqs/adapters/react-router` (pointing to v6)\n  is deprecated and will be removed in nuqs@3.0.0.\n\n  Please pin your imports to the specific version,\n  eg: `nuqs/adapters/react-router/v6` or `nuqs/adapters/react-router/v7`.\n\n  The main difference is where the React Router hooks are imported from:\n  `react-router-dom` for v6, and `react-router` for v7.\n</Callout>\n\n## <TanStackRouter className='inline mr-1.5 -mt-1 not-prose' role=\"presentation\"/> TanStack Router [#tanstack-router]\n\n```tsx title=\"src/routes/__root.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/tanstack-router'\nimport { Outlet, createRootRoute } from '@tanstack/react-router'\n\nexport const Route = createRootRoute({\n  component: () => (\n    <>\n      <NuqsAdapter>\n        <Outlet />\n      </NuqsAdapter>\n    </>\n  ),\n})\n```\n\n<Callout>\n\nTanStack Router support is experimental and does not yet cover TanStack Start.\n\n</Callout>\n\n### Type-safe routing via `validateSearch`\n\nTanStack Router comes with built-in type-safe search params support,\nand its APIs should likely be used in your application code for best DX.\n\nNevertheless, sometimes you may need to import a component that uses nuqs\n(from NPM or a shared library), and benefit from TanStack Router's type-safe routing.\n\nYou may do so via the [Standard Schema](/docs/utilities#standard-schema) support:\n\n```tsx\nimport { createFileRoute, Link } from '@tanstack/react-router'\nimport {\n  createStandardSchemaV1,\n  parseAsIndex,\n  parseAsString,\n  useQueryStates\n} from 'nuqs'\n\nconst searchParams = {\n  searchQuery: parseAsString.withDefault(''),\n  pageIndex: parseAsIndex.withDefault(0),\n}\n\nexport const Route = createFileRoute('/search')({\n  component: RouteComponent,\n  // [!code highlight:3]\n  validateSearch: createStandardSchemaV1(searchParams, {\n    partialOutput: true\n  })\n})\n\nfunction RouteComponent() {\n  // Consume nuqs state as usual:\n  const [{ searchQuery, pageIndex }] = useQueryStates(searchParams)\n  // But now TanStack Router knows about it too:\n  return (\n    <Link\n      to=\"/search\"\n      search={{\n        searchQuery: 'foo',\n        // note: we're not specifying pageIndex\n      }}\n    />\n  )\n}\n```\n\nNote that the `partialOutput{:ts}` flag allows specifying only a subset of\nthe search params for a given route. It also does not reflect those search\nin the URL automatically, following nuqs' behaviour more closely.\n\n<Callout title=\"Caveats\" type=\"warn\">\n\nDue to differences in how TanStack Router and nuqs handle serialisation and deserialisation\n(global in TanStack Router and per-key in nuqs), only _trivial_ state types are supported for\ntype-safe linking. Those include all string-based parsers (string, enum, literals),\nnumber-based (integer, float, number literal), boolean, and JSON.\n\nThe `urlKeys` feature to provide shorthand key names is also not supported for similar\nreasons.\n\n</Callout>\n\n## <Vitest className='inline mr-1.5 -mt-1' role=\"presentation\"/> Testing\n\n<Callout>\n  Documentation for the `NuqsTestingAdapter{:ts}` is on the [testing page](/docs/testing).\n</Callout>\n"
  },
  {
    "path": "packages/docs/content/docs/basic-usage.mdx",
    "content": "---\ntitle: Basic usage\ndescription: Replacing React.useState with useQueryState\n---\n\nimport {\n  DemoFallback,\n  BasicUsageDemo\n} from '@/content/docs/parsers/demos'\n\n<Callout title=\"Prerequisite\">\n  Have you setup your app with the appropriate [**adapter**](/docs/adapters)? Then you\n  are all set!\n</Callout>\n\nIf you are using `React.useState` to manage your local UI state,\nyou can replace it with `useQueryState` to sync it with the URL.\n\n```tsx\n'use client'\n\nimport { useQueryState } from 'nuqs'\n\nexport function Demo() {\n  const [name, setName] = useQueryState('name')\n  return (\n    <>\n      <input value={name || ''} onChange={e => setName(e.target.value)} />\n      <button onClick={() => setName(null)}>Clear</button>\n      <p>Hello, {name || 'anonymous visitor'}!</p>\n    </>\n  )\n}\n```\n\n<Suspense fallback={<DemoFallback/>}>\n  <BasicUsageDemo />\n</Suspense>\n\n`useQueryState` takes one required argument: the key to use in the query string.\n\nLike `React.useState`, it returns an array with the value present in the query\nstring as a string (or `null{:ts}` if none was found), and a state updater function.\n\nExample outputs for our demo example:\n\n| URL          | name value   | Notes                                                             |\n| ------------ | ------------ | ----------------------------------------------------------------- |\n| `/`          | `null{:ts}`  | No `name` key in URL                                              |\n| `/?name=`    | `''{:ts}`    | Empty string                                                      |\n| `/?name=foo` | `'foo'{:ts}` ||\n| `/?name=2`   | `'2'{:ts}`   | Always returns a string by default, see [Parsers](/docs/parsers) |\n\n<Callout title=\"Tip\">\n  Setting `null{:ts}` as a value will remove the key from the query string.\n</Callout>\n\n## Default values\n\nWhen the query string is not present in the URL, the default behaviour is to\nreturn `null{:ts}` as state.\n\nIt can make state updating and UI rendering tedious.\nTake this example of a simple counter stored in the URL:\n\n```tsx\nimport { useQueryState, parseAsInteger } from 'nuqs'\n\nexport default () => {\n  const [count, setCount] = useQueryState('count', parseAsInteger)\n  return (\n    <>\n      <pre>count: {count}</pre>\n      <button onClick={() => setCount(0)}>Reset</button>\n      {/* handling null values in setCount is annoying: */}\n      <button onClick={() => setCount(c => (c ?? 0) + 1)}>+</button>\n      <button onClick={() => setCount(c => (c ?? 0) - 1)}>-</button>\n      <button onClick={() => setCount(null)}>Clear</button>\n    </>\n  )\n}\n```\n\nYou can provide a default value as the second argument to `useQueryState` (or\nvia the `.withDefault{:ts}` builder method on parsers):\n\n```ts\nconst [search] = useQueryState('search', { defaultValue: '' })\n//      ^? string\n\nconst [count] = useQueryState('count', parseAsInteger)\n//      ^? number | null -> no default value = nullable\n\nconst [count] = useQueryState('count', parseAsInteger.withDefault(0))\n//      ^? number\n```\n\nIt makes it much easier to handle state updates:\n\n```tsx\nconst increment = () => setCount(c => c + 1) // c will never be null\nconst decrement = () => setCount(c => c - 1) // c will never be null\nconst clearCount = () => setCount(null) // Remove query from the URL\n```\n\n<Callout title=\"Note\">\n  The default value is internal to React, it will **not** be written to the\n  URL _unless you set it explicitly_ and use the [`clearOnDefault: false{:ts}` option](/docs/options#clear-on-default).\n</Callout>\n\n<Callout title=\"Tip\">\n  The default value is also returned if the value is _invalid_ for the parser.\n</Callout>\n\n<Callout title=\"Tip\">\n  Setting the state to `null{:ts}` when a default value is specified:\n  1. Clears the query from the URL\n  2. Returns the default value as state\n</Callout>\n"
  },
  {
    "path": "packages/docs/content/docs/batching.mdx",
    "content": "---\ntitle: useQueryStates\ndescription: How to read & update multiple search params at once\n---\n\n## Multiple updates (batching)\n\nYou can call as many state update functions as needed in a single event loop\ntick, and they will be applied to the URL asynchronously:\n\n```ts\nconst MultipleQueriesDemo = () => {\n  const [lat, setLat] = useQueryState('lat', parseAsFloat)\n  const [lng, setLng] = useQueryState('lng', parseAsFloat)\n  const randomCoordinates = React.useCallback(() => {\n    setLat(Math.random() * 180 - 90)\n    setLng(Math.random() * 360 - 180)\n  }, [])\n}\n```\n\nIf you wish to know when the URL has been updated, and what it contains, you can\nawait the Promise returned by the state updater function, which gives you the\nupdated URLSearchParameters object:\n\n```ts\nconst randomCoordinates = React.useCallback(() => {\n  setLat(42)\n  return setLng(12)\n}, [])\n\nrandomCoordinates().then((search: URLSearchParams) => {\n  search.get('lat') // 42\n  search.get('lng') // 12, has been queued and batch-updated\n})\n```\n\n<details>\n<summary><em>Implementation details (Promise caching)</em></summary>\n\nThe returned Promise is cached until the next flush to the URL occurs,\nso all calls to a setState (of any hook) in the same event loop tick will\nreturn the same Promise reference.\n\nDue to throttling of calls to the Web History API, the Promise may be cached\nfor several ticks. Batched updates will be merged and flushed once to the URL.\nThis means not every setState will reflect to the URL, if another one comes\noverriding it before flush occurs.\n\nThe returned React state will reflect all set values instantly,\nto keep UI responsive.\n\n---\n\n</details>\n\n## `useQueryStates`\n\nFor query keys that should always move together, you can use `useQueryStates`\nwith an object containing each key's type:\n\n```ts\nimport { useQueryStates, parseAsFloat } from 'nuqs'\n\nconst [coordinates, setCoordinates] = useQueryStates(\n  {\n    lat: parseAsFloat.withDefault(45.18),\n    lng: parseAsFloat.withDefault(5.72)\n  },\n  {\n    history: 'push'\n  }\n)\n\nconst { lat, lng } = coordinates\n\n// Set all (or a subset of) the keys in one go:\nconst search = await setCoordinates({\n  lat: Math.random() * 180 - 90,\n  lng: Math.random() * 360 - 180\n})\n```\n\n### Options\n\nThere are three places you can define [options](/docs/options) in `useQueryStates`:\n- As the second argument to the hook itself (global options, like `history: 'push'{:ts}` above)\n- On each parser, like `parseAsFloat.withOptions({ shallow: false }){:ts}`\n- At the call level when updating the state:\n\n```ts\nsetCoordinates(\n  {\n    lat: 42,\n    lng: 12\n  },\n  // [!code highlight:3]\n  {\n    shallow: false\n  }\n)\n```\n\nThe order of precedence is: call-level options > parser options > global options.\n\n<Callout title=\"Tip\">\nYou can clear all keys managed by a `useQueryStates{:ts}` hook by passing\n`null{:ts}` to the state updater function:\n\n```ts\nconst clearAll = () => setCoordinates(null)\n```\n\nThis will clear `lat` & `lng`, and leave other search params untouched.\n\n</Callout>\n\n### Shorter search params keys\n\n<FeatureSupportMatrix\n  introducedInVersion='1.20.0'\n  support={{\n    supported: false,\n    frameworks: ['TanStack Router']\n  }}\n  highlightUnsupported\n/>\n\nOne issue with tying the parsers object keys to the search params keys was that\nyou had to trade-off between variable names that make sense for your domain\nor business logic, and short, URL-friendly keys.\n\nYou can use a `urlKeys{:ts}` object in the hook options\nto remap the variable names to shorter keys:\n\n```ts\nconst [{ latitude, longitude }, setCoordinates] = useQueryStates(\n  {\n    // Use variable names that make sense in your codebase\n    latitude: parseAsFloat.withDefault(45.18),\n    longitude: parseAsFloat.withDefault(5.72)\n  },\n  {\n    urlKeys: {\n      // And remap them to shorter keys in the URL\n      latitude: 'lat',\n      longitude: 'lng'\n    }\n  }\n)\n\n// No changes in the setter API, but the keys are remapped to:\n// ?lat=45.18&lng=5.72\nsetCoordinates({\n  latitude: 45.18,\n  longitude: 5.72\n})\n```\n\nAs your application grows, you may want to reuse these parsers and urlKeys\ndefinitions across multiple components or nuqs features\n(like [loaders](/docs/server-side#loaders) or a [serializer](/docs/utilities#serializer-helper)).\n\nYou can use the `UrlKeys{:ts}` type helper for this:\n\n```ts\n// [!code word:UrlKeys]\nimport { type UrlKeys } from 'nuqs' // or 'nuqs/server'\n\nexport const coordinatesParsers = {\n  latitude: parseAsFloat.withDefault(45.18),\n  longitude: parseAsFloat.withDefault(5.72)\n}\n\nexport const coordinatesUrlKeys: UrlKeys<typeof coordinatesParsers> = {\n  latitude: 'lat',\n  longitude: 'lng'\n}\n```\n\n<FeatureSupportMatrix introducedInVersion='2.3.0' hideFrameworks />"
  },
  {
    "path": "packages/docs/content/docs/debugging.mdx",
    "content": "---\ntitle: Debugging\ndescription: Enabling debug logs and user timings markers\n---\n\n## Browser\n\nYou can enable debug logs in the browser by setting the `debug` item in localStorage\nto `nuqs`, and reload the page.\n\n```js\n// In your devtools:\nlocalStorage.setItem('debug', 'nuqs')\n```\n\nLog lines for both `useQueryState` and `useQueryStates` will be prefixed with\n`[nuq+]`, along with other internal debug logs.\n\n> Note: unlike the `debug` package, this will not work with wildcards, but\n> you can combine it: `localStorage.setItem('debug', '*,nuqs')`\n\n## Server (Node.js)\n\nDebug logs apply in any Node environment: server-side rendering (SSR), React\nServer Components (RSC), or when using `nuqs/server`. Hooks like\n`useQueryState` and `useQueryStates` can run on the server in these contexts.\nEnable logs by setting the `DEBUG` environment variable so it contains `nuqs`:\n\n```bash\nDEBUG=nuqs node server.js\n```\n\nOr when running your development server:\n\n```bash\nDEBUG=nuqs pnpm dev\n```\n\nYou can also define `DEBUG=nuqs` in your `.env` file or configure it through\nyour hosting environment.\n\nUnlike the browser version, this does not use `localStorage`. Debug mode is\nenabled when `process.env.DEBUG` contains the string `nuqs`.\n\nUser timings markers are also recorded, for advanced performance analysis using\nyour browser's devtools.\n\nProviding debug logs when opening an [issue](https://github.com/47ng/nuqs/issues)\nis always appreciated. 🙏\n"
  },
  {
    "path": "packages/docs/content/docs/installation.mdx",
    "content": "---\ntitle: Installation\ndescription: Getting started\n---\n\nimport {\n  NextJS,\n  ReactRouter,\n  ReactRouterV7,\n  ReactSPA,\n  Remix,\n  TanStackRouter,\n} from '@/src/components/frameworks'\n\nInstall the `nuqs` package with your favourite package manager:\n\n<HumanContent>\n```npm\nnpm install nuqs\n```\n</HumanContent>\n\n<LLMContent>\n- NPM: `npm install nuqs`\n- PNPM: `pnpm add nuqs`\n- Yarn: `yarn add nuqs`\n- Bun: `bun add nuqs`\n</LLMContent>\n\n## Which version should I use?\n\n`nuqs@^2` supports the following frameworks and their respective versions:\n\n- <NextJS className='inline mr-1.5' role=\"presentation\"/> [Next.js](/docs/adapters#nextjs): `next@>=14.2.0` <small className='text-muted-foreground'>(app & pages routers)</small>\n- <ReactSPA className='inline mr-1.5' role=\"presentation\" /> [React SPA](/docs/adapters#react-spa): `react@^18.3 || ^19`\n- <Remix className='inline mr-1.5' role=\"presentation\" /> [Remix](/docs/adapters#remix): `@remix-run/react@^2`\n- <ReactRouter className='inline mr-1.5' role=\"presentation\" /> [React Router v6](/docs/adapters#react-router-v6): `react-router-dom@^6`\n- <ReactRouterV7 className='inline mr-1.5' role=\"presentation\" /> [React Router v7](/docs/adapters#react-router-v7): `react-router@^7`\n- <TanStackRouter className='inline mr-1.5 not-prose' role=\"presentation\"/> [TanStack Router](/docs/adapters#tanstack-router): `@tanstack/react-router@^1`\n\n\n<Callout>\n  For older versions of Next.js, you may use `nuqs@^1` (documentation in `node_modules/nuqs/README.md`).\n</Callout>\n"
  },
  {
    "path": "packages/docs/content/docs/internal/design-system.mdx",
    "content": "---\ntitle: Design System\ndescription: Who needs Storybook anyway?\nexposeTo: [user]\n---\n\nimport { H1, H2, Description } from '@/src/components/typography'\n\n<Description>Who needs Storybook anyway? (using the `<Description>` component)</Description>\n\n## Typography\n\n# h1 Heading\n## h2 Heading\n### h3 Heading\n#### h4 Heading\n##### h5 Heading\n###### h6 Heading\n\nComparison with components:\n\n# H1 in Markdown with `code` block\n\n<H1 id=\"foo\">H1 using the `<H1>` component (with ID)</H1>\n\n<H1>H1 using the `<H1>` component (no ID)</H1>\n\n## H2 in Markdown\n\n<H2 id=\"bar\">H2 using the `<H2>` component (with ID)</H2>\n\n<H2>H2 using the `<H2>` component (no ID)</H2>\n\n## Callouts\n\nFrom [Fumadocs](https://fumadocs.dev/docs/ui/markdown#callouts), with slight design tweaks.\n\n### Inline callouts\n\n<Callout type=\"info\">Info</Callout>\n<Callout type=\"success\">Success</Callout>\n<Callout type=\"warning\">Warning</Callout>\n<Callout type=\"error\">Error</Callout>\n\n### With title\n\n<Callout title=\"Title\" type=\"info\">Info</Callout>\n<Callout title=\"Title\" type=\"success\">Success</Callout>\n<Callout title=\"Title\" type=\"warning\">Warning</Callout>\n<Callout title=\"Title\" type=\"error\">Error</Callout>\n\n## Code blocks\n\n```ts title=\"demo.ts\"\nconst hello = 'code blocks'\n```\n\nInline code blocks are supported too: `const foo = 'bar'{:ts}`.\n\n<Callout title=\"Code blocks in callouts?\">\n  ```ts\nexport const yep = \"we got'em too.\"\n  ```\n</Callout>\n\n## Feature support matrix\n\n<FeatureSupportMatrix introducedInVersion='2.5.0' />\n<FeatureSupportMatrix introducedInVersion='2.5.0' support={{ supported: true, frameworks: ['React Router (v6)', 'React Router (v7)', 'Remix']}}/>\n<FeatureSupportMatrix introducedInVersion='2.5.0' support={{ supported: true, frameworks: ['Next.js (app router)']}}/>\n<FeatureSupportMatrix introducedInVersion='2.5.0' support={{ supported: false, frameworks: ['Next.js (app router)']}}/>\n<FeatureSupportMatrix introducedInVersion='2.5.0' deprecatedInVersion='2.6.0' />\n<FeatureSupportMatrix introducedInVersion='2.5.0' deprecatedInVersion='2.6.0' support={{ supported: true, frameworks: ['React Router (v6)', 'React Router (v7)', 'Remix']}}/>\n<FeatureSupportMatrix introducedInVersion='2.5.0' deprecatedInVersion='2.6.0' support={{ supported: true, frameworks: ['Next.js (app router)']}}/>\n<FeatureSupportMatrix introducedInVersion='2.5.0' deprecatedInVersion='2.6.0' support={{ supported: false, frameworks: ['Next.js (app router)']}}/>\n\n"
  },
  {
    "path": "packages/docs/content/docs/limits.mdx",
    "content": "---\ntitle: Limits\ndescription: There are some limits you should be aware of when using nuqs or URL params in general.\n---\n\n## URL update throttling\n\nBrowsers rate-limit the History API, updates to the URL are queued and throttled to a default of 50ms, which seems to satisfy most browsers even when sending high-frequency query updates, like binding to a text input or a slider.\n\nSafari's rate limits are much stricter and require a throttle of 120ms (320ms for older versions of Safari).\n\nNuqs handles this out of the box so you don't run into those rate-limits, however it is possible to set your own custom throttles.\n\nFor more info how to set custom throttles see [Rate-limiting URL updates](/docs/options#rate-limiting-url-updates).\n\n## Max URL lengths\n\nMost modern browsers enforce a max URL length, which can vary:\n\n- **Chrome:** ~2 MB (practically, you might encounter issues at around 2,000 characters).\n- **Firefox:** ~65,000 characters.\n- **Safari:** Generally has more restrictive limits (around 80,000 characters).\n- **IE/Edge:** Historically limited to 2,083 characters (IE), although Edge has relaxed this limit.\n\nAdditionally, transport mechanisms like social media, messaging apps, and emails may impose significantly lower limits on URL length. Long URLs may be truncated, wrapped, or rendered unusable when shared on these platforms.\n\nKeep in mind that not all application state should be stored in URLs. Exceeding the 2,000-character range may indicate the need to reconsider your state management approach.\n"
  },
  {
    "path": "packages/docs/content/docs/meta.json",
    "content": "{\n  \"title\": \"Documentation\",\n  \"root\": true,\n  \"pages\": [\n    \"---Getting started---\",\n    \"installation\",\n    \"adapters\",\n    \"basic-usage\",\n    \"---Guide---\",\n    \"parsers\",\n    \"options\",\n    \"batching\",\n    \"server-side\",\n    \"limits\",\n    \"---Going further---\",\n    \"utilities\",\n    \"debugging\",\n    \"testing\",\n    \"troubleshooting\",\n    \"seo\",\n    \"tips-tricks\",\n    \"about\",\n    \"migrations/v2\"\n  ]\n}\n"
  },
  {
    "path": "packages/docs/content/docs/migrations/v2.mdx",
    "content": "---\ntitle: Migration guide to v2\ndescription: How to update your code to use nuqs@2.0.0\n---\n\nHere's a summary of the breaking changes in `nuqs@2.0.0`:\n\n- [Enable support for other React frameworks via **adapters**](#adapters)\n- [Behaviour changes](#behaviour-changes)\n- [ESM-only package](#esm-only)\n- [Deprecated exports have been removed](#deprecated-exports)\n- [Renamed `nuqs/parsers` to `nuqs/server`](#renamed-nuqsparsers-to-nuqsserver)\n- [Debug printout detection](#debug-printout-detection)\n- [Dropping `next-usequerystate`](#dropping-next-usequerystate)\n- [Type changes](#type-changes)\n\n## Adapters\n\nThe biggest change is that `nuqs@2.0.0` now supports other React frameworks,\nproviding type-safe URL state for all.\n\nYou will need to wrap your app with the appropriate [adapter](/docs/adapters)\nfor your framework or router, to let the hooks know how to interact with it.\n\nAdapters are currently available for:\n- Next.js (app & pages routers)\n- React SPA\n- Remix\n- React Router\n- Testing environments (Vitest, Jest, etc.)\n\nIf you are coming from nuqs v1 (which only supported Next.js), you'll need to\nwrap your app with the appropriate `NuqsAdapter`:\n\n### Next.js\n\n<Callout title=\"Minimum required version: next@>=14.2.0\" type=\"warn\">\n\nEarly versions of Next.js 14 were in flux with regards to shallow routing.\nSupporting those earlier versions required a lot of hacks, workarounds, and\nperformance penalties, which were removed in `nuqs@2.0.0`.\n\n</Callout>\n\n#### App router\n\n```tsx title=\"src/app/layout.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport { type ReactNode } from 'react'\n\nexport default function RootLayout({\n  children\n}: {\n  children: ReactNode\n}) {\n  return (\n    <html>\n      <body>\n        <NuqsAdapter>{children}</NuqsAdapter>\n      </body>\n    </html>\n  )\n}\n```\n\n#### Pages router\n\n```tsx title=\"src/pages/_app.tsx\"\n// [!code word:NuqsAdapter]\nimport type { AppProps } from 'next/app'\nimport { NuqsAdapter } from 'nuqs/adapters/next/pages'\n\nexport default function MyApp({ Component, pageProps }: AppProps) {\n  return (\n    <NuqsAdapter>\n      <Component {...pageProps} />\n    </NuqsAdapter>\n  )\n}\n```\n\n#### Unified (router-agnostic)\n\nIf your Next.js app uses **both the app and pages routers** and the adapter needs\nto be mounted in either, you can import the unified adapter, at the cost\nof a slightly larger bundle size (~100B).\n\n```tsx\nimport { NuqsAdapter } from 'nuqs/adapters/next'\n```\n\n### Other adapters\n\nAlbeit not part of a migration from v1, you can now use nuqs in other React\nframeworks via their respective [adapters](/docs/adapters).\n\nHowever, there's one more adapter that might be of interest to you, and solves\na long-standing issue with testing components using nuqs hooks:\n\n### Testing adapter\n\nUnit-testing components that used nuqs v1 was a hassle, as it required mocking\nthe Next.js router internals, causing abstraction leaks.\n\nIn v2, you can now wrap your components to test with the [`NuqsTestingAdapter`](/docs/testing),\nwhich provides a convenient setup & assertion API for your tests.\n\nHere's an example with Vitest & Testing Library:\n\n```tsx title=\"counter-button-example.test.tsx\"\nimport { render, screen } from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'\nimport { describe, expect, it, vi } from 'vitest'\nimport { CounterButton } from './counter-button'\n\nit('should increment the count when clicked', async () => {\n  const user = userEvent.setup()\n  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()\n  render(<CounterButton />, {\n    // Setup the test by passing initial search params / querystring:\n    wrapper: ({ children }) => (\n      <NuqsTestingAdapter searchParams=\"?count=1\" onUrlUpdate={onUrlUpdate}>\n        {children}\n      </NuqsTestingAdapter>\n    )\n  })\n  // Act\n  const button = screen.getByRole('button')\n  await user.click(button)\n  // Assert changes in the state and in the (mocked) URL\n  expect(button).toHaveTextContent('count is 2')\n  expect(onUrlUpdate).toHaveBeenCalledOnce()\n  expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=2')\n  expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('2')\n  expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push')\n})\n```\n\n## Behaviour changes\n\nSetting the `startTransition{:ts}` option no longer sets `shallow: false{:ts}` automatically.\nThis is to align with other frameworks that don't have a concept\nof shallow/deep routing.\n\nYou'll have to set both to keep sending updates to the server and getting a loading\nstate in Next.js:\n\n```diff\nuseQueryState('q', {\n  startTransition: true,\n+ shallow: false\n})\n```\n\nThe `\"use client\"{:ts}` directive was not included in the client import\n(`import {} from 'nuqs'{:ts}`). It has now been added, meaning that server-side code\nneeds to import from `nuqs/server` to avoid errors like:\n\n```txt\nError: Attempted to call withDefault() from the server but withDefault is on\nthe client. It's not possible to invoke a client function from the server, it can\nonly be rendered as a Component or passed to props of a Client\nComponent.\n```\n\n## ESM only\n\n`nuqs@2.0.0` is now an [ESM-only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)\npackage. This should not be much of an issue since Next.js supports ESM in\napp code since version 12, but if you are bundling `nuqs` code into an\nintermediate CJS library to be consumed in Next.js, you'll run into import issues:\n\n```txt\n[ERR_REQUIRE_ESM]: require() of ES Module not supported\n```\n\nIf converting your library to ESM is not possible, your main option is to\ndynamically import `nuqs`:\n\n```ts\nconst { useQueryState } = await import('nuqs')\n```\n\n## Deprecated exports\n\nSome of the v1 API was marked as deprecated back in September 2023, and has been\nremoved in `nuqs@2.0.0`.\n\n### `queryTypes` parsers object\n\nThe `queryTypes{:ts}` object has been removed in favor of individual parser exports,\nfor better tree-shaking.\n\nReplace with `parseAsXYZ{:ts}` to match:\n\n```diff\n- import { queryTypes } from 'nuqs'\n+ import { parseAsString, parseAsInteger, ... } from 'nuqs'\n\n- useQueryState('q',    queryTypes.string.withOptions({ ... }))\n- useQueryState('page', queryTypes.integer.withDefault(1))\n+ useQueryState('q',    parseAsString.withOptions({ ... }))\n+ useQueryState('page', parseAsInteger.withDefault(1))\n```\n\n### `subscribeToQueryUpdates`\n\nNext.js 14.1.0 makes `useSearchParams{:ts}` reactive to shallow search params updates,\nwhich makes this internal helper function redundant. See [#425](https://github.com/47ng/nuqs/pull/425) for context.\n\n## Renamed `nuqs/parsers` to `nuqs/server`\n\nWhen introducing the server cache in [#397](https://github.com/47ng/nuqs/pull/397), the dedicated export for parsers was\nreused as it didn't include the `\"use client\"{:ts}` directive. Since it now contains\nmore than parsers and probably will be extended with server-only code in the future,\nit has been renamed to a clearer export name.\n\nFind and replace all occurrences of `nuqs/parsers` to `nuqs/server` in your code:\n\n```diff\n- import { parseAsInteger, createSearchParamsCache } from 'nuqs/parsers'\n+ import { parseAsInteger, createSearchParamsCache } from 'nuqs/server'\n```\n\n## Debug printout detection\n\nAfter the rename to `nuqs`, the debugging printout detection logic handled either\n`next-usequerystate` or `nuqs` being present in the `localStorage.debug{:ts}` variable.\n`nuqs@2.0.0` only checks for the presence of the `nuqs` substring to enable logs.\n\nUpdate your local dev environments to match by running this once in the devtools console:\n\n```ts\nif (localStorage.debug) {\n  localStorage.debug = localStorage.debug.replace('next-usequerystate', 'nuqs')\n}\n```\n\n## Dropping next-usequerystate\n\nThis package started under the name `next-usequerystate`, and was renamed to\n`nuqs` in January 2024. The old package name was kept as an alias for the v1\nrelease line.\n\n`nuqs` version 2 and onwards no longer mirror to the `next-usequerystate` package name.\n\n## Type changes\n\nThe following breaking changes only apply to exported types:\n\n- The `Options{:ts}` type is no longer generic\n- The `UseQueryStatesOptions{:ts}` is now a type rather than an interface, and is now\ngeneric over the type of the object you pass to `useQueryStates{:ts}`.\n- [`parseAsJson{:ts}`](/docs/parsers/built-in#json) now requires a runtime\nvalidation function to infer the type of the parsed JSON data.\n"
  },
  {
    "path": "packages/docs/content/docs/options.client.tsx",
    "content": "'use client'\n\nimport { QuerySpy } from '@/src/components/query-spy'\nimport { Button } from '@/src/components/ui/button'\nimport { Checkbox } from '@/src/components/ui/checkbox'\nimport { Label } from '@/src/components/ui/label'\nimport { parseAsInteger, useQueryState } from 'nuqs'\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport { useState } from 'react'\n\nexport function DemoSkeleton() {\n  return (\n    <figure className=\"flex animate-pulse flex-wrap justify-around gap-2 rounded-xl border border-dashed p-1 pb-2\">\n      <ComponentSkeleton />\n      <ComponentSkeleton />\n      <ComponentSkeleton />\n    </figure>\n  )\n}\n\nfunction sortAlphabetically(search: URLSearchParams): URLSearchParams {\n  search.sort()\n  return search\n}\nfunction passThrough(search: URLSearchParams): URLSearchParams {\n  return search\n}\n\nexport function AlphabeticalSortDemo() {\n  const [enableSorting, setEnableSorting] = useState(true)\n\n  return (\n    <NuqsAdapter\n      processUrlSearchParams={enableSorting ? sortAlphabetically : passThrough}\n    >\n      <>\n        <Label className=\"flex items-center gap-2\">\n          <Checkbox\n            checked={enableSorting}\n            onCheckedChange={checked => setEnableSorting(checked === true)}\n          />{' '}\n          Enable alphabetical sorting on updates\n        </Label>\n        <figure className=\"not-prose mt-4 mb-8 flex flex-wrap justify-around gap-2 rounded-xl border border-dashed p-1 pb-2\">\n          <QuerySpy keepKeys={['a', 'b', 'c']} />\n          <ComponentToggle id=\"a\" />\n          <ComponentToggle id=\"b\" />\n          <ComponentToggle id=\"c\" />\n        </figure>\n      </>\n    </NuqsAdapter>\n  )\n}\n\nexport function TimestampDemo() {\n  return (\n    <NuqsAdapter\n      processUrlSearchParams={search => {\n        search.set('ts', Date.now().toString())\n        return search\n      }}\n    >\n      <>\n        <figure className=\"flex flex-wrap justify-around gap-2 rounded-xl border border-dashed p-1 pb-2\">\n          <QuerySpy keepKeys={['d', 'e', 'f', 'ts']} />\n          <ComponentIncrement id=\"d\" />\n          <ComponentIncrement id=\"e\" />\n          <ComponentIncrement id=\"f\" />\n        </figure>\n      </>\n    </NuqsAdapter>\n  )\n}\n\nfunction ComponentIncrement({ id }: { id: string }) {\n  const [count, setCount] = useQueryState(id, parseAsInteger.withDefault(0))\n  return (\n    <div className=\"rounded-xl p-1.5\">\n      <Button\n        onClick={() => setCount(c => c + 1)}\n        className=\"min-w-42 tabular-nums\"\n      >\n        Increment \"{id}\": {count}\n      </Button>\n    </div>\n  )\n}\n\nfunction ComponentToggle({ id }: { id: string }) {\n  const [, setCount] = useQueryState(id, parseAsInteger.withDefault(0))\n  return (\n    <div className=\"rounded-xl p-1.5\">\n      <Button\n        onClick={() => setCount(c => (c ? 0 : 1))}\n        className=\"min-w-42 tabular-nums\"\n      >\n        Toggle \"{id}\"\n      </Button>\n    </div>\n  )\n}\n\nfunction ComponentSkeleton() {\n  return (\n    <div className=\"rounded-xl p-1.5\">\n      <Button disabled className=\"min-w-42 tabular-nums\">\n        Loading demo...\n      </Button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/docs/options.mdx",
    "content": "---\ntitle: Options\ndescription: Configuring nuqs\n---\n\nBy default, `nuqs` will update search params:\n1. On the client only (not sending requests to the server),\n2. by replacing the current history entry,\n3. without scrolling to the top of the page.\n4. with a throttle rate adapted to your browser\n\nThese default behaviours can be [configured](#global-defaults-override),\nalong with a few additional options.\n\n## Passing options\n\nOptions can be passed at the hook level via the builder pattern:\n\n```ts\n// [!code word:withOptions]\nconst [state, setState] = useQueryState(\n  'foo',\n  parseAsString.withOptions({ history: 'push' })\n)\n```\n\nOr when calling the state updater function, as a second parameter:\n\n```ts\n// [!code word:scroll]\nsetState('foo', { scroll: true })\n```\n\nCall-level options will override hook level options.\n\n## History\n\nBy default, state updates are done by **replacing** the current history entry with\nthe updated query when state changes.\n\nYou can see this as a sort of `git squash{:shell}`, where all state-changing\noperations are merged into a single browsing history entry.\n\nYou can also opt-in to **push** a new history entry for each state change,\nper key, which will let you use the Back button to navigate state\nupdates:\n\n```ts\n// Append state changes to history:\n// [!code word:history]\nuseQueryState('foo', { history: 'push' })\n```\n\n<Callout title=\"Watch out!\" type=\"warn\">\nBreaking the Back button can lead to a bad user experience. Make sure to use this\noption only if the search params to update contribute to a navigation-like\nexperience (eg: tabs, modals). Overriding the Back behaviour must be a UX\nenhancement rather than a nuisance.\n\n_-- \"With great power comes great responsibility.\"_\n</Callout>\n\n## Shallow\n\nBy default, query state updates are done in a _client-first_ manner: there are\nno network calls to the server.\n\nThis is equivalent to the `shallow` option of the Next.js router set to `true{:ts}`.\n\nTo opt-in to notifying the server on query updates, you can set `shallow` to `false{:ts}`:\n\n```ts\n// [!code word:shallow]\nuseQueryState('foo', { shallow: false })\n```\n\nNote that the shallow option only makes sense if your page can be server-side rendered.\nTherefore, it has no effect in React SPA.\n\nFor server-side renderable frameworks, you would pair `shallow: false{:ts}` with:\n\n- In Next.js app router: the `searchParams` page prop to render the RSC tree based on the updated query state.\n- In Next.js pages router: the `getServerSideProps` function\n- In Remix & React Router: a `loader` function\n\n### In React Router based frameworks\n\nWhile the `shallow: true{:ts}` default behaviour is uncommon for Remix and React Router,\nwhere loaders are always supposed to run on URL changes, nuqs gives you control\nof this behaviour, by opting in to running loaders only if they do need to access\nthe relevant search params.\n\nOne caveat is that the stock `useSearchParams{:ts}` hook from those frameworks doesn't\nreflect shallow-updated search params, so we provide you with one that does:\n\n```tsx\nimport { useOptimisticSearchParams } from 'nuqs/adapters/remix' // or '…/react-router/v6' or '…/react-router/v7'\n\nfunction Component() {\n  // Note: this is read-only, but reactive to all URL changes\n  const searchParams = useOptimisticSearchParams()\n  return <div>{searchParams.get('foo')}</div>\n}\n```\n\nThis concept of _\"shallow routing\"_ is done via updates to the browser's\n[History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API).\n\n<Callout title=\"Why not using shouldRevalidate?\">\n [`shouldRevalidate`](https://reactrouter.com/start/framework/route-module#shouldrevalidate)\n is the idomatic way of opting out of running loaders on navigation, but nuqs uses\n the opposite approach: opting in to running loaders only when needed.\n\n In order to avoid specifying `shouldRevalidate` for every route, nuqs chose to\n patch the history methods to enable shallow routing by default (on its own updates)\n in React Router based frameworks.\n</Callout>\n\n## Scroll\n\nThe Next.js router scrolls to the top of the page on navigation updates,\nwhich may not be desirable when updating the query string with local state.\n\nQuery state updates won't scroll to the top of the page by default, but you\ncan opt-in to this behaviour:\n\n```ts\n// [!code word:scroll]\nuseQueryState('foo', { scroll: true })\n```\n\n## Rate-limiting URL updates\n\nBecause of browsers rate-limiting the History API, updates **to the\nURL** are queued and throttled to a default of 50ms, which seems to satisfy\nmost browsers even when sending high-frequency query updates, like binding\nto a text input or a slider.\n\nSafari's rate limits are much stricter and use a default throttle of 120ms\n(320ms for older versions of Safari).\n\n<Callout title=\"Note\">\nthe state returned by the hook is always updated **instantly**, to keep UI responsive.\nOnly changes to the URL, and server requests when using `shallow: false{:ts}`, are throttled or debounced.\n</Callout>\n\nThis [throttle](#throttle) time is configurable, and also allows you to [debounce](#debounce) updates\ninstead.\n\n<Callout title=\"Which one should I use?\">\nThrottle will emit the first update immediately, then batch updates at a slower\npace **regularly**. This is recommended for most low-frequency updates.\n\nDebounce will push back the moment when the URL is updated when you set your state,\nmaking it **eventually consistent**. This is recommended for high-frequency\nupdates where the last value is more interesting than the intermediate ones,\nlike a search input or moving a slider.\n\nRead more about [debounce vs throttle](https://kettanaito.com/blog/debounce-vs-throttle).\n</Callout>\n\n### Throttle\n\nIf you want to increase the throttle time -- for example to reduce the amount\nof requests sent to the server when paired with `shallow: false{:ts}` -- you can\nspecify it under the `limitUrlUpdates{:ts}` option:\n\n```ts\n// [!code word:limitUrlUpdates]\nuseQueryState('foo', {\n  // Send updates to the server maximum once every second\n  shallow: false,\n  limitUrlUpdates: {\n    method: 'throttle',\n    timeMs: 1000\n  }\n})\n\n// or using the shorthand:\nimport { throttle } from 'nuqs'\n\nuseQueryState('foo', {\n  shallow: false,\n  limitUrlUpdates: throttle(1000)\n})\n```\n\nIf multiple hooks set different throttle values on the same event loop tick,\nthe highest value will be used. Also, values lower than 50ms will be ignored,\nto avoid rate-limiting issues.\n[Read more](https://francoisbest.com/posts/2023/storing-react-state-in-the-url-with-nextjs#batching--throttling).\n\nSpecifying a `+Infinity{:ts}` value for throttle time will **disable** updates to the\nURL or the server, but all `useQueryState(s){:ts}` hooks will still update their\ninternal state and stay in sync with each other.\n\n<Callout title=\"Deprecation notice\">\nThe `throttleMs{:ts}` option has been deprecated in `nuqs@2.5.0` and will be removed\nin a later major upgrade.\n\nTo migrate:\n1. `import { throttle } from 'nuqs' {:ts}`\n2.  Replace `{ throttleMs: 100 }{:ts}` with `{ limitUrlUpdates: throttle(100) }{:ts}` in your options.\n</Callout>\n\n### Debounce\n\n<Callout type=\"warning\" title=\"Do I need debounce?\">\n  Debounce only makes sense for **server-side data fetching** (RSCs & loaders, when combined with [`shallow: false{:ts}`](#shallow)),\n  to control when requests are made to the server. For example: it lets you avoid sending the first\n  character on its own when typing in a search input, by waiting for the user to finish typing.\n\n  If you are **fetching client-side** (eg: with TanStack Query), you'll want to debounce the\n  state returned by the hooks instead (using a 3rd party `useDebounce` utility hook).\n</Callout>\n\nIn addition to throttling, you can apply a debouncing mechanism to URL updates,\nto delay the moment where the URL gets updated with the latest value.\n\n<Callout>\n  The returned state is always updated **immediately**: only the network requests\n  sent to the server are debounced.\n</Callout>\n\nThis can be useful for high frequency state updates where the server only cares about\nthe final value, not all the intermediary ones while typing in a search input\nor moving a slider.\n\nWe recommend you opt-in to debouncing on specific state updates, rather than\ndefining it for the whole search param.\n\nLet's take the example of a search input. You'll want to update it:\n\n1. When the user is typing text, with debouncing\n2. When the user clears the input, by sending an immediate update\n3. When the user presses Enter, by sending an immediate update\n\nYou can see the debounce case is the outlier here, and actually conditioned on\nthe set value, so we can specify it using the state updater function:\n\n```tsx\n// [!code word:debounce:1]\nimport { useQueryState, parseAsString, debounce } from 'nuqs';\n\nfunction Search() {\n  const [search, setSearch] = useQueryState(\n    'q',\n    parseAsString\n      .withDefault('')\n      .withOptions({ shallow: false })\n  )\n\n  return (\n    <input\n      value={search}\n      onChange={(e) =>\n        setSearch(e.target.value, {\n          // Send immediate update if resetting, otherwise debounce at 500ms\n          // [!code word:debounce:1]\n          limitUrlUpdates: e.target.value === '' ? undefined : debounce(500)\n        })\n      }\n      onKeyPress={(e) => {\n        if (e.key === 'Enter') {\n          // Send immediate update\n          setSearch(e.target.value)\n        }\n      }}\n    />\n  )\n}\n```\n\n### Resetting\n\nYou can use the `defaultRateLimit{:ts}` import to reset debouncing or throttling to\nthe default:\n\n```ts\n// [!code word:defaultRateLimit]\nimport { debounce, defaultRateLimit } from 'nuqs'\n\nconst [, setState] = useQueryState('foo', {\n  limitUrlUpdates: debounce(1000)\n})\n\n// This state update isn't debounced\nsetState('bar', { limitUrlUpdates: defaultRateLimit })\n```\n\n## Transitions\n\nWhen combined with `shallow: false{:ts}`, you can use React's `useTransition{:ts}` hook\nto get loading states while the server is re-rendering server components with\nthe updated URL.\n\nPass in the `startTransition{:ts}` function from `useTransition{:ts}` to the options\nto enable this behaviour:\n\n```tsx\n'use client'\n\nimport React from 'react'\nimport { useQueryState, parseAsString } from 'nuqs'\n\nfunction ClientComponent({ data }) {\n  // 1. Provide your own useTransition hook:\n  // [!code word:startTransition:1]\n  const [isLoading, startTransition] = React.useTransition()\n  const [query, setQuery] = useQueryState(\n    'query',\n    // 2. Pass the `startTransition` as an option:\n    // [!code word:startTransition:1]\n    parseAsString.withOptions({ startTransition, shallow: false })\n  )\n  // 3. `isLoading` will be true while the server is re-rendering\n  // and streaming RSC payloads, when the query is updated via `setQuery`.\n\n  // Indicate loading state\n  if (isLoading) return <div>Loading...</div>\n\n  // Normal rendering with data\n  return <div>...</div>\n}\n```\n\n<Callout>\n  In `nuqs@1.x.x`, passing `startTransition{:ts}` as an option automatically sets\n  `shallow: false{:ts}`.\n\n  This is no longer the case in `nuqs@>=2.0.0`: you'll need to set it explicitly.\n</Callout>\n\n## Clear on default\n\nWhen the state is set to the default value, the search parameter is\nremoved from the URL, instead of being reflected explicitly.\n\nHowever, sometimes you might want to keep the search parameter in the URL,\nbecause **default values _can_ change**, and the meaning of the URL along with it.\n\n<Callout title=\"Example of defaults changing\">\n  In `nuqs@1.x.x`, `clearOnDefault{:ts}` was `false{:ts}` by default.<br/>\n  in `nuqs@2.0.0`, `clearOnDefault{:ts}` is now `true{:ts}` by default, in response\n  to [user feedback](https://x.com/fortysevenfx/status/1841610237540696261).\n</Callout>\n\nIf you want to keep the search parameter in the URL when it's set to the default\nvalue, you can set `clearOnDefault{:ts}` to `false{:ts}`:\n\n```ts\n// [!code word:clearOnDefault]\nuseQueryState('search', {\n  defaultValue: '',\n  clearOnDefault: false\n})\n```\n\n<Callout title=\"Tip\">\n  Clearing the key-value pair from the query string can always be done by setting the state to `null{:ts}`.\n</Callout>\n\nThis option compares the set state against the default value using `==={:ts}`\nreference equality, so if you are using a [custom parser](/docs/parsers/making-your-own)\nfor a state type that wouldn't work with reference equality, you should provide\nthe `eq{:ts}` function to your parser (this is done for you in built-in parsers):\n\n```ts\nconst dateParser = createParser({\n  parse: (value: string) => new Date(value.slice(0, 10)),\n  serialize: (date: Date) => date.toISOString().slice(0, 10),\n  eq: (a: Date, b: Date) => a.getTime() === b.getTime() // [!code highlight]\n})\n```\n\n## Adapter props\n\nThe following options are global and can be passed directly\non the [`<NuqsAdapter>{:tsx}`](/docs/adapters) as props, and apply to its whole\nchildren tree.\n\n### Global defaults override\n\n<FeatureSupportMatrix introducedInVersion='2.5.0' />\n\nDefault values for some options can be configured globally via the `defaultOptions{:ts}`\nadapter prop:\n\n```tsx\n// [!code word:defaultOptions]\n<NuqsAdapter\n  defaultOptions={{\n    shallow: false,\n    scroll: true,\n    clearOnDefault: false,\n    limitUrlUpdates: throttle(250),\n  }}\n>\n  {children}\n</NuqsAdapter>\n```\n\n### Processing `URLSearchParams`\n\n<FeatureSupportMatrix introducedInVersion='2.6.0' />\n\nYou can pass a `processUrlSearchParams{:ts}` callback to the adapter,\nwhich will be called _after_ the `URLSearchParams{:ts}` have been merged\nwhen performing a state update, and _before_ they are sent to the adapter\nfor updating the URL.\n\nThink of it as a sort of **middleware** for processing search params.\n\n#### Alphabetical Sort\n\nSort the search parameters alphabetically:\n\n```tsx\n// [!code word:processUrlSearchParams]\n<NuqsAdapter\n  processUrlSearchParams={(search) => {\n    // Note: you can modify search in-place,\n    // or return a copy, as you wish.\n    search.sort()\n    return search\n  }}\n>\n  {children}\n</NuqsAdapter>\n```\n\nimport { Suspense } from 'react'\nimport { AlphabeticalSortDemo, DemoSkeleton } from './options.client'\n\n_Try toggling **c**, then **b**, then **a**, and note how the URL remains ordered:_\n\n<Suspense fallback={<DemoSkeleton/>}>\n  <AlphabeticalSortDemo />\n</Suspense>\n\n#### Timestamp\n\nAdd a timestamp to the search parameters:\n\n```tsx\n// [!code word:processUrlSearchParams]\n<NuqsAdapter\n  processUrlSearchParams={(search) => {\n    search.set('ts', Date.now().toString())\n    return search\n  }}\n>\n  {children}\n</NuqsAdapter>\n```\n\nimport { TimestampDemo } from './options.client'\n\n<Suspense fallback={<DemoSkeleton/>}>\n  <TimestampDemo />\n</Suspense>\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/built-in.mdx",
    "content": "---\ntitle: Built-in parsers\ndescription: When using strings is not enough\n---\n\nimport {\n  DemoFallback,\n  IntegerParserDemo,\n  StringParserDemo,\n  FloatParserDemo,\n  HexParserDemo,\n  IndexParserDemo,\n  BooleanParserDemo,\n  StringLiteralParserDemo,\n  DateISOParserDemo,\n  DatetimeISOParserDemo,\n  DateTimestampParserDemo,\n  JsonParserDemo,\n  NativeArrayParserDemo\n} from '@/content/docs/parsers/demos'\n\nSearch params are strings by default, but chances are your state is more complex than that.\n\nYou might want to use numbers, booleans, Dates, objects, arrays, or even custom types.\nThis is where **parsers** come in.\n\n`nuqs` provides built-in parsers for the most common types, and allows you to [define your own](/docs/parsers/making-your-own).\n\n## String\n\n```ts\nimport { parseAsString } from 'nuqs'\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <StringParserDemo />\n</Suspense>\n\n<Callout title=\"Type-safety tip\">\n`parseAsString` is a noop: it does not perform any validation when parsing,\nand will accept **any** value.\n\nIf you're expecting a certain set of string values, like `'foo' | 'bar'{:ts}`,\nsee [Literals](#literals) for ensuring type-runtime safety.\n\n</Callout>\n\nIf search params are strings by default, what's the point of this _\"parser\"_ ?\n\nIt becomes useful if you're declaring a search params object, and/or you want\nto use the builder pattern to specify [default values](/docs/basic-usage#default-values)\nand [options](/docs/options):\n\n```ts\nexport const searchParamsParsers = {\n  q: parseAsString.withDefault('').withOptions({\n    shallow: false\n  })\n}\n```\n\n## Numbers\n\n### Integers\n\nTransforms the search param string into an integer with `parseInt` (base 10).\n\n```ts\nimport { parseAsInteger } from 'nuqs'\n\nuseQueryState('int', parseAsInteger.withDefault(0))\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <IntegerParserDemo />\n</Suspense>\n\n### Floating point\n\nSame as integer, but uses `parseFloat` under the hood.\n\n```ts\nimport { parseAsFloat } from 'nuqs'\n\nuseQueryState('float', parseAsFloat.withDefault(0))\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <FloatParserDemo />\n</Suspense>\n\n### Hexadecimal\n\nEncodes integers in hexadecimal.\n\n```ts\nimport { parseAsHex } from 'nuqs'\n\nuseQueryState('hex', parseAsHex.withDefault(0x00))\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <HexParserDemo />\n</Suspense>\n\n<Callout title=\"Going further\">\n  Check out the [Hex Colors](/playground/hex-colors) playground for a demo.\n</Callout>\n\n### Index\n\nSame as integer, but adds a `+1` offset to the serialized querystring (and `-1` when parsing).\nUseful for pagination indexes.\n\n```ts\nimport { parseAsIndex } from 'nuqs'\n\nconst [pageIndex] = useQueryState('page', parseAsIndex.withDefault(0))\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <IndexParserDemo />\n</Suspense>\n\n## Boolean\n\n```ts\nimport { parseAsBoolean } from 'nuqs'\n\nuseQueryState('bool', parseAsBoolean.withDefault(false))\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <BooleanParserDemo />\n</Suspense>\n\n## Literals\n\nThese parsers extend the basic integer and float parsers, but validate against\nsome expected values, defined as [TypeScript literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).\n\n```ts\nimport { parseAsStringLiteral, type inferParserType } from 'nuqs'\n\n// Create parser\nconst parser = parseAsStringLiteral(['asc', 'desc'])\n\n// Optional: extract the type\ntype SortOrder = inferParserType<typeof parser> // 'asc' | 'desc'\n```\n\n<Callout title=\"Should I declare values inline or outside the parser?\">\n  It depends®. Declaring them inline is shorter, and makes the parser\n  the source of truth for type inference with `inferParserType{:ts}`,\n  but it locks the values inside the parser.\n\n  Declaring them outside allows reading and iterating over the values at runtime.\n  Don't forget to add `as const{:ts}` though, otherwise the type will widen as a `string{:ts}`.\n</Callout>\n\n### String literals\n\n```ts\n// [!code word:as const]\nimport { parseAsStringLiteral } from 'nuqs'\n\n// List accepted values\nconst sortOrder = ['asc', 'desc'] as const\n\n// Then pass it to the parser\nparseAsStringLiteral(sortOrder)\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <StringLiteralParserDemo />\n</Suspense>\n\n### Numeric literals\n\n```ts\nimport { parseAsNumberLiteral } from 'nuqs'\n\nparseAsNumberLiteral([1, 2, 3, 4, 5, 6])\n```\n\n```ts\n// [!code word:as const]\nimport { parseAsNumberLiteral } from 'nuqs'\n\n// List accepted values\nconst diceSides = [1, 2, 3, 4, 5, 6] as const\n\n// Then pass it to the parser\nparseAsNumberLiteral(diceSides)\n```\n\n## Enums\n\nString enums are a bit more verbose than string literals, but `nuqs` supports them.\n\n```ts\nenum Direction {\n  up = 'UP',\n  down = 'DOWN',\n  left = 'LEFT',\n  right = 'RIGHT'\n}\n\nparseAsStringEnum<Direction>(Object.values(Direction))\n```\n\n<Callout title=\"Note\">\n  The query string value will be the **value** of the enum, not its name (here:\n  `?direction=UP`).\n</Callout>\n\n## Dates & timestamps\n\nThere are three parsers that give you a `Date` object, their difference is\non how they encode the value into the query string.\n\n### ISO 8601 Datetime\n\n```ts\nimport { parseAsIsoDateTime } from 'nuqs'\n```\n\n<Suspense>\n  <DatetimeISOParserDemo />\n</Suspense>\n\n### ISO 8601 Date\n\n<FeatureSupportMatrix introducedInVersion='2.1.0' />\n\n```ts\nimport { parseAsIsoDate } from 'nuqs'\n```\n\n<Suspense>\n  <DateISOParserDemo />\n</Suspense>\n\n<Callout>\n  The Date is parsed without the time zone offset, making it at 00:00:00 UTC.\n</Callout>\n\n### Timestamp\n\nMiliseconds since the Unix epoch.\n\n```ts\nimport { parseAsTimestamp } from 'nuqs'\n```\n\n<Suspense>\n  <DateTimestampParserDemo />\n</Suspense>\n\n## Arrays\n\nAll of the parsers on this page can be used to parse arrays of their respective types.\n\n```ts\nimport { parseAsArrayOf, parseAsInteger } from 'nuqs'\n\nparseAsArrayOf(parseAsInteger)\n\n// Optionally, customise the separator\nparseAsArrayOf(parseAsInteger, ';')\n```\n\n## JSON\n\nIf primitive types are not enough, you can encode JSON in the query string.\n\nPass it a [Standard Schema](https://standardschema.dev) (eg: a Zod schema)\nto validate and infer the type of the parsed data:\n\n```ts\n// [!code word:parseAsJson]\nimport { parseAsJson } from 'nuqs'\nimport { z } from 'zod'\n\nconst schema = z.object({\n  pkg: z.string(),\n  version: z.number(),\n  worksWith: z.array(z.string())\n})\n\n// This parser is a function, don't forget to call it\n// with your schema as an argument.\nconst [json, setJson] = useQueryState('json', parseAsJson(schema))\n\nsetJson({\n  pkg: 'nuqs',\n  version: 2,\n  worksWith: ['Next.js', 'React', 'Remix', 'React Router', 'and more']\n})\n```\n\n<Suspense>\n  <JsonParserDemo />\n</Suspense>\n\nUsing other validation libraries is possible: `parseAsJson{:ts}` accepts\nany Standard Schema compatible input (eg: ArkType, Valibot),\nor a custom validation function (eg: Yup, Joi, etc):\n\n```ts\nimport { object, string, number } from 'yup';\n\nlet userSchema = object({\n  name: string().required(),\n  age: number().required().positive().integer(),\n});\n\nparseAsJson(userSchema.validateSync)\n```\n\n<Callout title=\"Note\">\n  Validation functions must either throw an error or\n  return `null{:ts}` for invalid data. Only **synchronous** validation is supported.\n</Callout>\n\n## Native Arrays\n\n<FeatureSupportMatrix introducedInVersion='2.7.0' />\n\nIf you want to use the native URL format for arrays, repeating the same key multiple times like:\n\nimport { Querystring } from '@/src/components/querystring'\n\n<Querystring path=\"/products\" value='?tag=books&tag=tech&tag=design' />\n\nyou can now use `MultiParsers{:ts}` like `parseAsNativeArrayOf{:ts}` to read and write those values in a fully type-safe way.\n\n```tsx\nimport { useQueryState, parseAsNativeArrayOf, parseAsInteger } from 'nuqs'\n\nconst [projectIds, setProjectIds] = useQueryState(\n  'project',\n  parseAsNativeArrayOf(parseAsInteger)\n)\n\n// ?project=123&project=456 → [123, 456]\n```\n\n<Suspense fallback={<DemoFallback />}>\n  <NativeArrayParserDemo />\n</Suspense>\n\n<Callout title=\"Note: empty array default\">\n  `parseAsNativeArrayOf{:ts}` has a built-in default value of empty array (`.withDefault([]){:ts}`) so that you don't have to handle `null{:ts}` cases.\n\n  Calls to `.withDefault(){:ts}` can be chained, so you can use it to set a custom default.\n</Callout>\n\n## Using parsers server-side\n\nFor shared code that may be imported in the Next.js app router, you should import\nparsers from `nuqs/server` to use them in both server & client code,\nas it doesn't include the `'use client'{:ts}` directive.\n\n```ts\nimport { parseAsString } from 'nuqs/server'\n```\n\nImporting from `nuqs` will only work in client code, and will throw bundling errors\nwhen using functions (like `.withDefault{:ts}` & `.withOptions{:ts}`)\nacross shared code.\n\nFor all other frameworks, you can use either interchangeably, as they don't\ncare about the `'use client'{:ts}` directive.\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/effect-schema-demo.tsx",
    "content": "'use client'\n\nimport { CodeBlock } from '@/src/components/code-block.client'\nimport { QuerySpy } from '@/src/components/query-spy'\nimport { ContainerQueryHelper } from '@/src/components/responsive-helpers'\nimport { Button } from '@/src/components/ui/button'\nimport { Label } from '@/src/components/ui/label'\nimport { cn } from '@/src/lib/utils'\nimport { Either, Equal, Schema } from 'effect'\nimport { createParser, useQueryState } from 'nuqs'\nimport React from 'react'\n\nfunction createSchemaParser<T, E extends string>(schema: Schema.Schema<T, E>) {\n  const encoder = Schema.encodeUnknownEither(schema)\n  const decoder = Schema.decodeUnknownEither(schema)\n  return createParser({\n    parse: queryValue => {\n      const result = decoder(queryValue)\n      return Either.getOrNull(result)\n    },\n    serialize: value => {\n      const result = encoder(value)\n      return Either.getOrThrowWith(\n        result,\n        cause =>\n          new Error(`Failed to encode value: ${value}`, {\n            cause\n          })\n      )\n    },\n    eq: (a, b) => Equal.equals(a, b)\n  })\n}\n\nclass User extends Schema.Class<User>('User')({\n  name: Schema.String,\n  age: Schema.Positive\n}) {}\n\nconst ToBase64UrlEncodedJson = Schema.compose(\n  Schema.StringFromBase64Url,\n  Schema.parseJson()\n)\nconst schema = Schema.compose(ToBase64UrlEncodedJson, User)\nconst parser = createSchemaParser(schema).withDefault(\n  new User({ name: 'John Vim', age: 25 })\n)\n\ntype DemoContainerProps = React.ComponentProps<'section'> & {\n  demoKey: string\n}\n\nfunction DemoContainer({\n  children,\n  className,\n  demoKey,\n  ...props\n}: DemoContainerProps) {\n  return (\n    <section\n      className={cn(\n        'not-prose flex flex-wrap items-center gap-2 rounded-xl border border-dashed p-2',\n        className\n      )}\n      {...props}\n    >\n      <QuerySpy className=\"rounded-md\" keepKeys={[demoKey]} />\n      {children}\n      <ContainerQueryHelper />\n    </section>\n  )\n}\n\nexport function EffectSchemaDemo() {\n  const [user, setUser] = useQueryState('effectUser', parser)\n\n  const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const name = e.target.value\n    if (!name) {\n      setUser(null)\n      return\n    }\n\n    const currentAge = user?.age ?? 25\n    setUser(new User({ name, age: currentAge }))\n  }\n\n  const handleAgeChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const age = parseInt(e.target.value)\n    if (!age || age <= 0) {\n      return // Positive constraint from schema\n    }\n\n    const currentName = user?.name ?? ''\n    if (!currentName) {\n      return // Need name to create valid User\n    }\n\n    setUser(new User({ name: currentName, age }))\n  }\n\n  return (\n    <DemoContainer\n      className=\"flex-col items-stretch gap-4\"\n      demoKey=\"effectUser\"\n    >\n      <div className=\"flex flex-col gap-4 sm:flex-row\">\n        <div className=\"flex-1 space-y-2\">\n          <Label htmlFor=\"user-name\">Name</Label>\n          <input\n            id=\"user-name\"\n            className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n            value={user?.name ?? ''}\n            onChange={handleNameChange}\n            placeholder=\"Enter your name...\"\n            autoComplete=\"off\"\n          />\n        </div>\n        <div className=\"flex-1 space-y-2\">\n          <Label htmlFor=\"user-age\">Age (positive integer)</Label>\n          <input\n            id=\"user-age\"\n            type=\"number\"\n            min=\"1\"\n            className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n            value={user?.age ?? ''}\n            onChange={handleAgeChange}\n            placeholder=\"Enter your age...\"\n            autoComplete=\"off\"\n          />\n        </div>\n      </div>\n\n      <div className=\"flex flex-col items-center gap-4 lg:flex-row\">\n        <CodeBlock\n          title=\"Parsed User Object\"\n          code={user ? JSON.stringify(user, null, 2) : 'null'}\n          className=\"flex-1\"\n          allowCopy={false}\n        />\n        <Button variant=\"secondary\" onClick={() => setUser(null)}>\n          Clear\n        </Button>\n      </div>\n    </DemoContainer>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/effect-schema.mdx",
    "content": "---\ntitle: Effect Schema Parsers\ndescription: Use Effect Schema to parse and serialize URL state.\n---\n\n[Effect](https://effect.website) is a popular TypeScript framework, with its own schema validation library: [Effect Schema](https://effect.website/docs/schema/introduction/)\n\nEffect Schemas have the unique property of encoding two way transformations between different types. This makes them a perfect fit for using in a nuqs parser.\n\n\n```ts\nimport { createParser } from 'nuqs'\nimport { Schema, Either, Equal } from 'effect'\n\nfunction createSchemaParser<T, E extends string>(schema: Schema.Schema<T, E>) {\n  const encoder = Schema.encodeUnknownEither(schema);\n  const decoder = Schema.decodeUnknownEither(schema);\n  return createParser({\n    parse: (queryValue) => {\n      const result = decoder(queryValue);\n      return Either.getOrNull(result);\n    },\n    serialize: (value) => {\n      const result = encoder(value);\n      return Either.getOrThrowWith(\n        result,\n        (cause) =>\n          new Error(`Failed to encode value: ${value}`, {\n            cause,\n          }),\n      );\n    },\n    eq: (a, b) => Equal.equals(a, b),\n  });\n}\n```\n\n## Example\n\n```ts\nimport { Schema } from 'effect'\n\nclass User extends Schema.Class<User>('User')({\n  name: Schema.String,\n  age: Schema.Positive\n}) {}\n\nconst ToBase64UrlEncodedJson = Schema.compose(Schema.StringFromBase64Url, Schema.parseJson())\nconst schema = Schema.compose(ToBase64UrlEncodedJson, User)\n\nconst parser = createSchemaParser(schema).withDefault(new User({ name: 'John Vim', age: 25 }))\n\nconst [user, setUser] = useQueryState('user', parser)\n```\n\n## Interactive Demo\n\nimport { Suspense } from 'react'\nimport { EffectSchemaDemo } from './effect-schema-demo'\n\n<Suspense>\n  <EffectSchemaDemo />\n</Suspense>"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/meta.json",
    "content": "{\n  \"title\": \"Community\",\n  \"pages\": [\"tanstack-table\", \"effect-schema\", \"zod-codecs\"]\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/tanstack-table.generator.tsx",
    "content": "'use client'\n\nimport { CodeBlock } from '@/src/components/code-block.client'\nimport { Querystring } from '@/src/components/querystring'\nimport { Label } from '@/src/components/ui/label'\nimport {\n  Pagination,\n  PaginationButton,\n  PaginationContent,\n  PaginationItem,\n  PaginationNext,\n  PaginationPrevious\n} from '@/src/components/ui/pagination'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from '@/src/components/ui/select'\nimport { Separator } from '@/src/components/ui/separator'\nimport {\n  parseAsIndex,\n  parseAsInteger,\n  parseAsString,\n  useQueryState\n} from 'nuqs'\nimport { useDeferredValue } from 'react'\n\nconst NUM_PAGES = 5\n\nexport function TanStackTablePagination() {\n  const [pageIndexUrlKey, setPageIndexUrlKey] = useQueryState(\n    'pageIndexUrlKey',\n    parseAsString.withDefault('page')\n  )\n  const [pageSizeUrlKey, setPageSizeUrlKey] = useQueryState(\n    'pageSizeUrlKey',\n    parseAsString.withDefault('perPage')\n  )\n  const [page, setPage] = useQueryState(\n    pageIndexUrlKey,\n    parseAsIndex.withDefault(0)\n  )\n  const [pageSize, setPageSize] = useQueryState(\n    pageSizeUrlKey,\n    parseAsInteger.withDefault(10)\n  )\n\n  const parserCode = useDeferredValue(`import {\n  parseAsIndex,\n  parseAsInteger,\n  parseAsString,\n  useQueryStates\n} from 'nuqs'\n\nconst paginationParsers = {\n  pageIndex: parseAsIndex.withDefault(0),\n  pageSize: parseAsInteger.withDefault(10)\n}\nconst paginationUrlKeys = {\n  pageIndex: '${pageIndexUrlKey}',\n  pageSize: '${pageSizeUrlKey}'\n}\n\nexport function usePaginationSearchParams() {\n  return useQueryStates(paginationParsers, {\n    urlKeys: paginationUrlKeys\n  })\n}`)\n\n  const internalState = useDeferredValue(`{\n  // zero-indexed\n  pageIndex: ${page},\n  pageSize: ${pageSize}\n}`)\n\n  return (\n    <section>\n      <div className=\"flex flex-wrap items-center justify-start gap-2 rounded-xl border border-dashed p-1\">\n        <Pagination className=\"not-prose mx-0 w-auto items-center gap-2\">\n          <PaginationContent>\n            <PaginationItem>\n              <PaginationPrevious\n                disabled={page <= 0}\n                onClick={() => setPage(p => Math.max(0, p - 1))}\n              />\n            </PaginationItem>\n            {Array.from({ length: NUM_PAGES }, (_, index) => (\n              <PaginationItem key={index}>\n                <PaginationButton\n                  isActive={page === index}\n                  onClick={() => setPage(index)}\n                >\n                  {index + 1}\n                </PaginationButton>\n              </PaginationItem>\n            ))}\n            <PaginationItem>\n              <PaginationNext\n                disabled={page >= NUM_PAGES - 1}\n                onClick={() => setPage(p => Math.min(NUM_PAGES - 1, p + 1))}\n              />\n            </PaginationItem>\n          </PaginationContent>\n        </Pagination>\n        <Label className=\"ml-auto flex items-center gap-2\">\n          Items per page\n          <Select\n            value={pageSize.toFixed()}\n            onValueChange={value => setPageSize(parseInt(value))}\n          >\n            <SelectTrigger className=\"w-24\">\n              <SelectValue placeholder=\"10\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"10\">10</SelectItem>\n              <SelectItem value=\"25\">25</SelectItem>\n              <SelectItem value=\"50\">50</SelectItem>\n              <SelectItem value=\"100\">100</SelectItem>\n            </SelectContent>\n          </Select>\n        </Label>\n      </div>\n      <p className=\"mb-0\">\n        Configure and copy-paste this parser into your application:\n      </p>\n      <div className=\"flex flex-col gap-6 xl:flex-row\">\n        <CodeBlock\n          title=\"search-params.pagination.ts\"\n          lang=\"ts\"\n          icon={\n            <svg\n              fill=\"none\"\n              viewBox=\"0 0 128 128\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              className=\"size-4\"\n              role=\"presentation\"\n            >\n              <rect fill=\"currentColor\" height=\"128\" rx=\"6\" width=\"128\" />\n              <path\n                clipRule=\"evenodd\"\n                d=\"m74.2622 99.468v14.026c2.2724 1.168 4.9598 2.045 8.0625 2.629 3.1027.585 6.3728.877 9.8105.877 3.3503 0 6.533-.321 9.5478-.964 3.016-.643 5.659-1.702 7.932-3.178 2.272-1.476 4.071-3.404 5.397-5.786 1.325-2.381 1.988-5.325 1.988-8.8313 0-2.5421-.379-4.7701-1.136-6.6841-.758-1.9139-1.85-3.6159-3.278-5.1062-1.427-1.4902-3.139-2.827-5.134-4.0104-1.996-1.1834-4.246-2.3011-6.752-3.353-1.8352-.7597-3.4812-1.4975-4.9378-2.2134-1.4567-.7159-2.6948-1.4464-3.7144-2.1915-1.0197-.7452-1.8063-1.5341-2.3598-2.3669-.5535-.8327-.8303-1.7751-.8303-2.827 0-.9643.2476-1.8336.7429-2.6079s1.1945-1.4391 2.0976-1.9943c.9031-.5551 2.0101-.9861 3.3211-1.2929 1.311-.3069 2.7676-.4603 4.3699-.4603 1.1658 0 2.3958.0877 3.6928.263 1.296.1753 2.6.4456 3.911.8109 1.311.3652 2.585.8254 3.824 1.3806 1.238.5552 2.381 1.198 3.43 1.9285v-13.1051c-2.127-.8182-4.45-1.4245-6.97-1.819s-5.411-.5917-8.6744-.5917c-3.3211 0-6.4674.3579-9.439 1.0738-2.9715.7159-5.5862 1.8336-7.844 3.353-2.2578 1.5195-4.0422 3.4553-5.3531 5.8075-1.311 2.3522-1.9665 5.1646-1.9665 8.4373 0 4.1785 1.2017 7.7433 3.6052 10.6945 2.4035 2.9513 6.0523 5.4496 10.9466 7.495 1.9228.7889 3.7145 1.5633 5.375 2.323 1.6606.7597 3.0954 1.5486 4.3044 2.3668s2.1628 1.7094 2.8618 2.6736c.7.9643 1.049 2.06 1.049 3.2873 0 .9062-.218 1.7462-.655 2.5202s-1.1 1.446-1.9885 2.016c-.8886.57-1.9956 1.016-3.3212 1.337-1.3255.321-2.8768.482-4.6539.482-3.0299 0-6.0305-.533-9.0021-1.6-2.9715-1.066-5.7245-2.666-8.2591-4.799zm-23.5596-34.9136h18.2974v-11.5544h-51v11.5544h18.2079v51.4456h14.4947z\"\n                className=\"fill-background\"\n                fillRule=\"evenodd\"\n              />\n            </svg>\n          }\n          className=\"flex-grow\"\n          code={parserCode}\n        />\n        <aside className=\"w-full space-y-4 xl:w-64\">\n          <Querystring\n            value={`?${pageIndexUrlKey}=${page + 1}&${pageSizeUrlKey}=${pageSize}`}\n          />\n          <CodeBlock\n            title=\"Internal state\"\n            code={internalState}\n            allowCopy={false}\n          />\n          <Separator className=\"my-8\" />\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"pageIndexKey\">Page index URL key</Label>\n            <input\n              id=\"pageIndexKey\"\n              className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n              value={pageIndexUrlKey}\n              onChange={e => {\n                setPage(null)\n                setPageIndexUrlKey(e.target.value)\n              }}\n              placeholder=\"e.g., page\"\n              autoComplete=\"off\"\n            />\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"pageSizeKey\">Page size URL key</Label>\n            <input\n              id=\"pageSizeKey\"\n              value={pageSizeUrlKey}\n              className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n              onChange={e => {\n                setPageSize(null)\n                setPageSizeUrlKey(e.target.value)\n              }}\n              placeholder=\"e.g., limit\"\n              autoComplete=\"off\"\n            />\n          </div>\n        </aside>\n      </div>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/tanstack-table.mdx",
    "content": "---\ntitle: TanStack Table Parsers\ndescription: Store your table state in the URL, with style.\n---\n\n[TanStack Table](https://tanstack.com/table) is a popular library for managing\ntabular content in React (and other frameworks).\n\nBy default, it will store its state in memory, losing all filters, sorts and\npagination when the page is refreshed. This is a prime example where URL state\nshines.\n\n## Pagination\n\nTanStack Table stores pagination under two pieces of state:\n\n- `pageIndex`: a zero-based integer representing the current page\n- `pageSize`: an integer representing the number of items per page\n\nYou will likely want the URL to follow your UI and be one-based for the page index:\n\nimport { TanStackTablePagination } from './tanstack-table.generator'\n\n<Suspense>\n  <TanStackTablePagination />\n</Suspense>\n\n## Filtering\n\n<Callout>\n  This section is empty for now, [contributions](https://github.com/47ng/nuqs) are welcome!\n</Callout>\n\n## Sorting\n\n<Callout>\n  This section is empty for now, [contributions](https://github.com/47ng/nuqs) are welcome!\n</Callout>\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/zod-codecs.demo.tsx",
    "content": "'use client'\n\nimport { CodeBlock } from '@/src/components/code-block.client'\nimport { QuerySpy } from '@/src/components/query-spy'\nimport { Button } from '@/src/components/ui/button'\nimport { Input } from '@/src/components/ui/input'\nimport { Label } from '@/src/components/ui/label'\nimport { Dices } from 'lucide-react'\nimport { useQueryState } from 'nuqs'\nimport { userJsonBase64Parser } from './zod-codecs.lib'\nimport { ZodCodecsDemoSkeleton } from './zod-codecs.skeleton'\n\nexport function ZodCodecsDemo() {\n  const [user, setUser] = useQueryState(\n    'user',\n    userJsonBase64Parser.withDefault({\n      name: 'John Doe',\n      age: 42\n    })\n  )\n\n  const handleReset = () => {\n    setUser({\n      name: 'John Doe',\n      age: 42\n    })\n  }\n\n  const handleRandomize = () => {\n    const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank']\n    const randomName = names[Math.floor(Math.random() * names.length)]\n    const randomAge = Math.floor(Math.random() * 50) + 18\n    setUser({\n      name: randomName,\n      age: randomAge\n    })\n  }\n\n  return (\n    <ZodCodecsDemoSkeleton>\n      <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n        <div className=\"space-y-2\">\n          <Label htmlFor=\"user-name\">Name</Label>\n          <Input\n            id=\"user-name\"\n            value={user.name}\n            placeholder=\"Enter your name...\"\n            onChange={e => setUser(old => ({ ...old, name: e.target.value }))}\n          />\n        </div>\n        <div className=\"space-y-2\">\n          <Label htmlFor=\"user-age\">Age</Label>\n          <Input\n            id=\"user-age\"\n            type=\"number\"\n            min=\"1\"\n            max=\"120\"\n            value={user.age}\n            onChange={e =>\n              setUser(old => ({\n                ...old,\n                age: Number(e.target.valueAsNumber) || 0\n              }))\n            }\n          />\n        </div>\n      </div>\n\n      <div className=\"flex gap-2\">\n        <Button onClick={handleRandomize}>\n          <Dices size={18} className=\"mr-2 inline-block\" role=\"presentation\" />\n          Randomize\n        </Button>\n        <Button onClick={handleReset} variant=\"outline\">\n          Clear\n        </Button>\n      </div>\n\n      <div className=\"space-y-4\">\n        <div>\n          <div className=\"mb-2 flex items-center gap-2\">\n            <Label className=\"text-sm font-medium\">Encoded in the URL:</Label>\n          </div>\n          <QuerySpy keepKeys={['user']} />\n        </div>\n        <div>\n          <Label className=\"text-sm font-medium\">Current Data:</Label>\n          <CodeBlock\n            code={JSON.stringify(user, null, 2)}\n            lang=\"json\"\n            className=\"mt-2\"\n            allowCopy={false}\n          />\n        </div>\n        <div className=\"not-prose\">\n          <strong className=\"mt-4 mb-2\">How it works</strong>\n          <p className=\"text-muted-foreground mt-2 mb-2 text-sm\">\n            On write (updating the URL):\n          </p>\n          <div>\n            <ol className=\"text-muted-foreground ml-2 list-inside list-decimal space-y-1 text-sm\">\n              <li>User object is JSON stringified</li>\n              <li>JSON string is encoded as UTF-8 bytes</li>\n              <li>Bytes are encoded as base64url string</li>\n              <li>Result is stored in the URL query parameter</li>\n            </ol>\n            <p className=\"text-muted-foreground mt-2 text-sm\">\n              On read, the process is reversed to decode the URL string back\n              into the original object.\n            </p>\n          </div>\n        </div>\n      </div>\n    </ZodCodecsDemoSkeleton>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/zod-codecs.lib.ts",
    "content": "import { createParser } from 'nuqs/server'\nimport { z } from 'zod'\n\nfunction createZodCodecParser<\n  Input extends z.ZodCoercedString<string> | z.ZodPipe<any, any>,\n  Output extends z.ZodType\n>(\n  codec: z.ZodCodec<Input, Output> | z.ZodPipe<Input, Output>,\n  eq: (a: z.output<Output>, b: z.output<Output>) => boolean = (a, b) => a === b\n) {\n  return createParser<z.output<Output>>({\n    parse(query) {\n      return codec.parse(query)\n    },\n    serialize(value) {\n      return codec.encode(value)\n    },\n    eq\n  })\n}\n\n// --\n\n// All parsers from the Zod docs:\nconst jsonCodec = <T extends z.core.$ZodType>(schema: T) =>\n  z.codec(z.string(), schema, {\n    decode: (jsonString, ctx) => {\n      try {\n        return JSON.parse(jsonString)\n      } catch (err: any) {\n        ctx.issues.push({\n          code: 'invalid_format',\n          format: 'json',\n          input: jsonString,\n          message: err.message\n        })\n        return z.NEVER\n      }\n    },\n    encode: value => JSON.stringify(value)\n  })\n\nconst base64urlToBytes = z.codec(z.base64url(), z.instanceof(Uint8Array), {\n  decode: base64urlString => z.util.base64urlToUint8Array(base64urlString),\n  encode: bytes => z.util.uint8ArrayToBase64url(bytes)\n})\n\nconst utf8ToBytes = z.codec(z.string(), z.instanceof(Uint8Array), {\n  decode: str => new TextEncoder().encode(str),\n  encode: bytes => new TextDecoder().decode(bytes)\n})\nconst bytesToUtf8 = invertCodec(utf8ToBytes)\n\n// --\n\nfunction invertCodec<A extends z.ZodType, B extends z.ZodType>(\n  codec: z.ZodCodec<A, B>\n): z.ZodCodec<B, A> {\n  return z.codec<B, A>(codec.out, codec.in, {\n    decode(value, ctx) {\n      try {\n        return codec.encode(value)\n      } catch (err) {\n        ctx.issues.push({\n          code: 'invalid_format',\n          format: 'invert.decode',\n          input: String(value),\n          message: err instanceof z.ZodError ? err.message : String(err)\n        })\n        return z.NEVER\n      }\n    },\n    encode(value, ctx) {\n      try {\n        return codec.decode(value)\n      } catch (err) {\n        ctx.issues.push({\n          code: 'invalid_format',\n          format: 'invert.encode',\n          input: String(value),\n          message: err instanceof z.ZodError ? err.message : String(err)\n        })\n        return z.NEVER\n      }\n    }\n  })\n}\n\n// --\n\nconst userSchema = z.object({\n  name: z.string(),\n  age: z.number()\n})\n\n// Composition always wins.\nconst codec = base64urlToBytes.pipe(bytesToUtf8).pipe(jsonCodec(userSchema))\n\nexport const userJsonBase64Parser = createZodCodecParser(\n  codec,\n  (a, b) => a === b || (a.name === b.name && a.age === b.age)\n)\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/zod-codecs.mdx",
    "content": "---\ntitle: Zod codecs\ndescription: Using Zod codecs for (de)serialisation in custom nuqs parser\n---\n\nSince `zod@^4.1`, you can use [codecs](https://zod.dev/codecs)\nto add bidirectional serialisation / deserialisation to your validation schemas:\n\n```ts\nimport { z } from 'zod'\n\n// Similar to parseAsTimestamp in nuqs:\nconst dateTimestampCodec = z.codec(z.string().regex(/^\\d+$/), z.date(), {\n  decode: (query) => new Date(parseInt(query)),\n  encode: (date) => date.valueOf().toFixed()\n})\n```\n\n## Demo\n\n<iframe\n  src=\"https://www.youtube-nocookie.com/embed/k4lWvklUxUE\"\n  title=\"YouTube video player\"\n  frameBorder=\"0\"\n  allow=\"autoplay; encrypted-media; picture-in-picture; web-share\"\n  referrerPolicy=\"strict-origin-when-cross-origin\"\n  allowFullScreen\n  className='aspect-video w-full max-w-2xl mx-auto mb-8'\n/>\n\nimport { ZodCodecsDemo } from './zod-codecs.demo'\nimport { ZodCodecsDemoSkeleton } from './zod-codecs.skeleton'\nimport { Suspense } from 'react'\n\n<Suspense fallback={(\n  <ZodCodecsDemoSkeleton className='animate-pulse'>\n    <div className='h-32 bg-muted/25 rounded-md flex items-center justify-center text-sm text-muted-foreground'>\n      Loading demo…\n    </div>\n  </ZodCodecsDemoSkeleton>\n)}>\n  <ZodCodecsDemo/>\n</Suspense>\n\nimport { ZodCodecsSource } from './zod-codecs.source'\n\nSource code:\n\n<ZodCodecsSource/>\n\n## Refinements\n\nThe cool part is being able to add string constraints to the first type in a codec.\nIt has to be rooted as a string data type (because that's what the URL\nwill give us), but you can add **refinements**:\n\n```ts\nz.codec(z.uuid(), ...)\nz.codec(z.email(), ...)\nz.codec(z.base64url(), ...)\n```\n\nSee the [complete list](https://zod.dev/api?id=string-formats) of string-based\nrefinements you can use.\n\n<Callout title=\"Caveats\" type=\"warning\">\n  As stated in the Zod docs, you [cannot use transforms in codecs](https://zod.dev/codecs#transforms).\n</Callout>"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/zod-codecs.skeleton.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle\n} from '@/src/components/ui/card'\nimport { cn } from '@/src/lib/utils'\nimport type { ComponentProps } from 'react'\n\nexport function ZodCodecsDemoSkeleton({\n  children,\n  className,\n  ...props\n}: ComponentProps<'div'>) {\n  return (\n    <Card className={cn('border-dashed py-4', className)} {...props}>\n      <CardHeader className=\"px-4\">\n        <CardTitle className=\"text-xl\">Zod Codecs Demo</CardTitle>\n        <CardDescription>\n          This demo shows how Zod codecs can transform complex data structures\n          into URL-safe strings using base64url encoding and JSON serialization.\n        </CardDescription>\n      </CardHeader>\n      <CardContent className=\"space-y-6 px-4\">{children}</CardContent>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/community/zod-codecs.source.tsx",
    "content": "import { CodeBlock } from '@/src/components/code-block'\nimport { readFile } from 'node:fs/promises'\n\nexport async function ZodCodecsSource() {\n  const filePath =\n    process.cwd() + '/content/docs/parsers/community/zod-codecs.lib.ts'\n  const source = await readFile(filePath, 'utf8')\n  return <CodeBlock code={source.trim()} />\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/demos.tsx",
    "content": "'use client'\n\nimport { CodeBlock } from '@/src/components/code-block.client'\nimport { QuerySpy } from '@/src/components/query-spy'\nimport { ContainerQueryHelper } from '@/src/components/responsive-helpers'\nimport { Button } from '@/src/components/ui/button'\nimport { Checkbox } from '@/src/components/ui/checkbox'\nimport {\n  Pagination,\n  PaginationButton,\n  PaginationContent,\n  PaginationItem,\n  PaginationNext,\n  PaginationPrevious\n} from '@/src/components/ui/pagination'\nimport { Slider } from '@/src/components/ui/slider'\nimport { cn } from '@/src/lib/utils'\nimport {\n  ChevronDown,\n  ChevronUp,\n  Dices,\n  Minus,\n  Star,\n  Trash2\n} from 'lucide-react'\nimport {\n  createMultiParser,\n  createParser,\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsHex,\n  parseAsIndex,\n  parseAsInteger,\n  parseAsIsoDate,\n  parseAsIsoDateTime,\n  parseAsJson,\n  parseAsNativeArrayOf,\n  parseAsStringLiteral,\n  parseAsTimestamp,\n  ParserBuilder,\n  SingleParser,\n  useQueryState\n} from 'nuqs'\nimport React from 'react'\nimport { z } from 'zod'\n\nexport function DemoFallback() {\n  return (\n    <section className=\"flex h-[100px] animate-pulse items-center justify-center rounded-xl border border-dashed text-zinc-500 sm:h-[104px]\">\n      Demo loading...\n    </section>\n  )\n}\n\ntype DemoContainerProps = React.ComponentProps<'section'> & {\n  demoKey: string\n}\n\nfunction DemoContainer({\n  children,\n  className,\n  demoKey,\n  ...props\n}: DemoContainerProps) {\n  return (\n    <section\n      className={cn(\n        'not-prose flex flex-wrap items-center gap-2 rounded-xl border border-dashed p-2',\n        className\n      )}\n      {...props}\n    >\n      <QuerySpy className=\"rounded-md\" keepKeys={[demoKey]} />\n      {children}\n    </section>\n  )\n}\n\nexport function BasicUsageDemo() {\n  const [name, setName] = useQueryState('name', { defaultValue: '' })\n  return (\n    <DemoContainer className=\"flex-col items-stretch\" demoKey=\"name\">\n      <input\n        className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n        value={name}\n        onChange={e => setName(e.target.value || null)}\n        placeholder=\"Enter your name...\"\n        autoComplete=\"off\"\n      />\n      <div className=\"flex flex-1 items-center gap-2\">\n        <span className=\"mr-auto ml-2 text-sm text-zinc-500\">\n          {`Hello, ${name || 'anonymous visitor'}!`}\n        </span>\n        <Button variant=\"secondary\" onClick={() => setName(null)}>\n          Clear\n        </Button>\n      </div>\n    </DemoContainer>\n  )\n}\n\nexport function StringParserDemo() {\n  const [value, setValue] = useQueryState('string', { defaultValue: '' })\n  return (\n    <DemoContainer demoKey=\"string\">\n      <input\n        className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n        value={value}\n        onChange={e => setValue(e.target.value || null)}\n        placeholder=\"Type something here...\"\n        autoComplete=\"off\"\n      />\n      <Button\n        variant=\"secondary\"\n        onClick={() => setValue(null)}\n        className=\"ml-auto\"\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function IntegerParserDemo() {\n  const [value, setValue] = useQueryState('int', parseAsInteger)\n  return (\n    <DemoContainer demoKey=\"int\">\n      <input\n        type=\"number\"\n        className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n        value={value ?? ''} // Handle empty input\n        onChange={e => {\n          if (e.target.value === '') {\n            setValue(null)\n          } else {\n            setValue(e.target.valueAsNumber)\n          }\n        }}\n        placeholder=\"What's your favourite number?\"\n        autoComplete=\"off\"\n      />\n      <Button\n        variant=\"secondary\"\n        onClick={() => setValue(null)}\n        className=\"ml-auto\"\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function FloatParserDemo() {\n  const [value, setValue] = useQueryState(\n    'float',\n    parseAsFloat.withDefault(0).withOptions({ throttleMs: 100 })\n  )\n  return (\n    <DemoContainer demoKey=\"float\">\n      <Slider\n        value={[value]}\n        onValueChange={([v]) => setValue(v).catch()}\n        className=\"w-auto flex-1\"\n        min={-1}\n        max={1}\n        step={0.001}\n      />\n      <Button variant=\"secondary\" onClick={() => setValue(null)}>\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function HexParserDemo() {\n  const [value, setValue] = useQueryState(\n    'hex',\n    parseAsHex.withDefault(0).withOptions({ throttleMs: 100 })\n  )\n  return (\n    <DemoContainer demoKey=\"hex\">\n      <Slider\n        value={[value]}\n        onValueChange={([v]) => setValue(v).catch(console.error)}\n        className=\"w-auto flex-1\"\n        min={0}\n        max={255}\n      />\n      <Button variant=\"secondary\" onClick={() => setValue(null)}>\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function IndexParserDemo() {\n  const numPages = 5\n  const [pageIndex, setPageIndex] = useQueryState(\n    'page',\n    parseAsIndex.withDefault(0).withOptions({ clearOnDefault: false })\n  )\n  return (\n    <DemoContainer demoKey=\"page\">\n      <Pagination className=\"not-prose items-center gap-2\">\n        <PaginationContent>\n          <PaginationItem>\n            <PaginationPrevious\n              disabled={pageIndex === 0}\n              onClick={() => setPageIndex(p => Math.max(0, p - 1))}\n            />\n          </PaginationItem>\n          {Array.from({ length: numPages }, (_, i) => (\n            <PaginationItem key={i}>\n              <PaginationButton\n                isActive={pageIndex === i}\n                onClick={() => setPageIndex(i)}\n              >\n                {i + 1}\n              </PaginationButton>\n            </PaginationItem>\n          ))}\n          <PaginationItem>\n            <PaginationNext\n              disabled={pageIndex === numPages - 1}\n              onClick={() => setPageIndex(p => Math.min(numPages - 1, p + 1))}\n            />\n          </PaginationItem>\n        </PaginationContent>\n      </Pagination>\n      <CodeBlock\n        className=\"my-0 h-10 flex-1 rounded-sm border-none [&>div]:py-2\"\n        code={`pageIndex: ${pageIndex} // internal state is zero-indexed`}\n        allowCopy={false}\n      />\n      <Button\n        variant=\"secondary\"\n        onClick={() => setPageIndex(null)}\n        className=\"ml-auto\"\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function BooleanParserDemo() {\n  const [value, setValue] = useQueryState(\n    'bool',\n    parseAsBoolean.withDefault(false)\n  )\n  return (\n    <DemoContainer demoKey=\"bool\">\n      <Checkbox\n        id=\"boolean-demo\"\n        checked={value ?? false}\n        onCheckedChange={e => setValue(Boolean(e))}\n        className=\"ml-3\"\n      />\n      <label\n        htmlFor=\"boolean-demo\"\n        className=\"text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n      >\n        Checked: <code>{String(value)}</code>\n      </label>\n      <Button\n        variant=\"secondary\"\n        className=\"ml-auto\"\n        onClick={() => setValue(null)}\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function StringLiteralParserDemo() {\n  const [value, setValue] = useQueryState(\n    'sort',\n    parseAsStringLiteral(['asc', 'desc'])\n  )\n  return (\n    <DemoContainer demoKey=\"sort\">\n      <Button\n        onClick={() =>\n          setValue(old => {\n            if (old === 'asc') {\n              return 'desc'\n            }\n            if (old === 'desc') {\n              return 'asc'\n            }\n            return 'asc'\n          })\n        }\n      >\n        {value === 'asc' ? (\n          <ChevronUp className=\"mr-2\" />\n        ) : value === 'desc' ? (\n          <ChevronDown className=\"mr-2\" />\n        ) : (\n          <Minus className=\"mr-2\" />\n        )}\n        {value === null ? (\n          <span>No order defined</span>\n        ) : (\n          <span>Sort {value === 'asc' ? 'Ascending' : 'Descending'}</span>\n        )}\n      </Button>\n      <Button\n        variant=\"secondary\"\n        className=\"ml-auto\"\n        onClick={() => setValue(null)}\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function DateParserDemo({\n  queryKey,\n  parser,\n  type\n}: {\n  queryKey: string\n  parser: ParserBuilder<Date>\n  type: 'date' | 'datetime-local'\n}) {\n  const [value, setValue] = useQueryState(queryKey, parser)\n  return (\n    <DemoContainer className=\"@container relative\" demoKey={queryKey}>\n      <ContainerQueryHelper />\n      <div className=\"flex w-full flex-col items-stretch gap-2 @md:flex-row\">\n        <div className=\"flex flex-1 items-center gap-2\">\n          <input\n            type={type}\n            className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-[2] rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n            value={\n              value?.toISOString().slice(0, type === 'date' ? 10 : 19) ?? ''\n            }\n            onChange={e => {\n              if (e.target.value === '') {\n                setValue(null)\n              } else {\n                // Force back the date to UTC to avoid lossy SerDe conversion.\n                // We could use the valueAsDate, but it's not supported in Chrome.\n                // See https://github.com/47ng/nuqs/pull/704\n                setValue(new Date(e.target.value + 'Z'))\n              }\n            }}\n          />\n          <span className=\"px-2 font-medium text-zinc-500\">UTC</span>\n        </div>\n        <div className=\"flex flex-1 gap-2 @md:flex-initial\">\n          <Button\n            className=\"w-full @md:w-auto\"\n            onClick={() => setValue(new Date())}\n          >\n            Now\n          </Button>\n          <Button\n            className=\"w-full @md:w-auto\"\n            variant=\"secondary\"\n            onClick={() => setValue(null)}\n          >\n            Clear\n          </Button>\n        </div>\n      </div>\n    </DemoContainer>\n  )\n}\n\nexport function DatetimeISOParserDemo() {\n  return (\n    <DateParserDemo\n      type=\"datetime-local\"\n      queryKey=\"iso\"\n      parser={parseAsIsoDateTime}\n    />\n  )\n}\n\nexport function DateISOParserDemo() {\n  return <DateParserDemo type=\"date\" queryKey=\"date\" parser={parseAsIsoDate} />\n}\n\nexport function DateTimestampParserDemo() {\n  return (\n    <DateParserDemo\n      type=\"datetime-local\"\n      queryKey=\"ts\"\n      parser={parseAsTimestamp}\n    />\n  )\n}\n\nconst jsonParserSchema = z.object({\n  pkg: z.string(),\n  version: z.number(),\n  worksWith: z.array(z.string())\n})\n\nexport function JsonParserDemo() {\n  const [value, setValue] = useQueryState('json', parseAsJson(jsonParserSchema))\n  return (\n    <DemoContainer demoKey=\"json\" className=\"items-start\">\n      <pre className=\"bg-background flex-1 rounded-md border p-2 text-sm text-zinc-500\">\n        {JSON.stringify(value, null, 2)}\n      </pre>\n      <Button\n        onClick={() =>\n          setValue({\n            pkg: 'nuqs',\n            version: 2,\n            worksWith: [\n              'Next.js',\n              'React',\n              'Remix',\n              'React Router',\n              'TanStack Router',\n              'and more'\n            ]\n          })\n        }\n      >\n        Try it\n      </Button>\n      <Button\n        variant=\"secondary\"\n        className=\"ml-auto\"\n        onClick={() => setValue(null)}\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nconst STAR = '★'\ntype Rating = 1 | 2 | 3 | 4 | 5\n\nconst parseAsStarRating = createParser({\n  parse(queryValue) {\n    const inBetween = queryValue.split(STAR)\n    const isValid = inBetween.length > 1 && inBetween.every(s => s === '')\n    if (!isValid) return null\n    const numStars = inBetween.length - 1\n    return Math.min(5, numStars) as Rating\n  },\n  serialize(value) {\n    return Array.from({ length: value }, () => STAR).join('')\n  }\n})\n\nexport function CustomParserDemo() {\n  const [value, setValue] = useQueryState('rating', parseAsStarRating)\n  return (\n    <DemoContainer demoKey=\"rating\">\n      <div className=\"group\">\n        <StarButton index={1} value={value} setValue={setValue} />\n        <StarButton index={2} value={value} setValue={setValue} />\n        <StarButton index={3} value={value} setValue={setValue} />\n        <StarButton index={4} value={value} setValue={setValue} />\n        <StarButton index={5} value={value} setValue={setValue} />\n      </div>\n      <Button\n        variant=\"secondary\"\n        className=\"ml-auto\"\n        onClick={() => setValue(null)}\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\nexport function NativeArrayParserDemo() {\n  const [value, setValue] = useQueryState(\n    'nativeArray',\n    parseAsNativeArrayOf(parseAsInteger)\n  )\n  return (\n    <DemoContainer demoKey=\"nativeArray\">\n      <Button\n        onClick={() =>\n          setValue(prev => prev.concat(Math.floor(Math.random() * 500) + 1))\n        }\n      >\n        <Dices size={18} className=\"mr-2 inline-block\" role=\"presentation\" />\n        Add random number\n      </Button>\n      <Button\n        onClick={() => setValue(prev => prev.slice(0, -1))}\n        disabled={value.length === 0}\n      >\n        <Trash2 size={16} className=\"mr-2 inline-block\" role=\"presentation\" />\n        Remove last number\n      </Button>\n      <Button\n        variant=\"secondary\"\n        onClick={() => setValue([])}\n        className=\"ml-auto\"\n      >\n        Clear\n      </Button>\n      <CodeBlock\n        lang=\"json\"\n        code={JSON.stringify(value)}\n        allowCopy={false}\n        className=\"my-0 w-full\"\n      />\n    </DemoContainer>\n  )\n}\n\nexport function CustomMultiParserDemo() {\n  const parseAsFromTo = createParser({\n    parse: value => {\n      const [min = null, max = null] = value\n        .split('~')\n        .map(parseAsInteger.parse)\n      if (min === null) return null\n      if (max === null) return { eq: min }\n      return { gte: min, lte: max }\n    },\n    serialize: value => {\n      return value.eq !== undefined\n        ? String(value.eq)\n        : `${value.gte}~${value.lte}`\n    }\n  })\n\n  const parseAsKeyValue = createParser({\n    parse: value => {\n      const [key, val] = value.split(':')\n      if (!key || !val) return null\n      return { key, value: val }\n    },\n    serialize: value => {\n      return `${value.key}:${value.value}`\n    }\n  })\n\n  const parseAsFilters = <TItem extends {}>(\n    itemParser: SingleParser<TItem>\n  ) => {\n    return createMultiParser({\n      parse: values => {\n        const keyValue = values\n          .map(parseAsKeyValue.parse)\n          .filter(v => v !== null)\n\n        const result = Object.fromEntries(\n          keyValue.flatMap(({ key, value }) => {\n            const parsedValue: TItem | null = itemParser.parse(value)\n            return parsedValue === null ? [] : [[key, parsedValue]]\n          })\n        )\n\n        return Object.keys(result).length === 0 ? null : result\n      },\n      serialize: values => {\n        return Object.entries(values)\n          .map(([key, value]) => {\n            if (!itemParser.serialize) return null\n            return parseAsKeyValue.serialize({\n              key,\n              value: itemParser.serialize(value)\n            })\n          })\n          .filter(v => v !== null)\n      }\n    })\n  }\n\n  const [filters, setFilters] = useQueryState(\n    'filters',\n    parseAsFilters(parseAsFromTo).withDefault({})\n  )\n\n  return (\n    <DemoContainer demoKey=\"filters\">\n      <div>\n        <label className=\"text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n          Rating:\n        </label>\n        <input\n          type=\"number\"\n          className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n          value={filters.rating?.eq ?? ''}\n          min={0}\n          max={5}\n          onChange={e => {\n            setFilters(prev => ({\n              ...prev,\n              rating: { eq: e.target.value === '' ? 0 : e.target.valueAsNumber }\n            }))\n          }}\n          autoComplete=\"off\"\n        />\n      </div>\n      <div>\n        <label className=\"text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n          Price From:\n        </label>\n        <input\n          type=\"number\"\n          className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n          value={filters.price?.gte ?? 0}\n          step={10}\n          max={1000}\n          onChange={e => {\n            setFilters(prev => ({\n              ...prev,\n              price: {\n                lte: prev.price?.lte ?? 0,\n                gte: e.target.value === '' ? 0 : e.target.valueAsNumber\n              }\n            }))\n          }}\n          autoComplete=\"off\"\n        />\n      </div>\n      <div>\n        <label className=\"text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n          Price To:\n        </label>\n        <input\n          type=\"number\"\n          className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n          value={filters.price?.lte ?? 0}\n          step={10}\n          max={1000}\n          onChange={e => {\n            setFilters(prev => ({\n              ...prev,\n              price: {\n                gte: prev.price?.gte ?? 0,\n                lte: e.target.value === '' ? 0 : e.target.valueAsNumber\n              }\n            }))\n          }}\n          autoComplete=\"off\"\n        />\n      </div>\n      <Button\n        variant=\"secondary\"\n        onClick={() => setFilters(null)}\n        className=\"mt-auto ml-auto\"\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n\n  return (\n    <DemoContainer demoKey=\"filters\">\n      {Object.entries(filters).map(([key, value]) => {\n        if (value.eq !== undefined) {\n          return (\n            <div key={key}>\n              <label className=\"text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n                {key}:{' '}\n              </label>\n              <input\n                key={key}\n                type=\"number\"\n                className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n                value={value.eq}\n                onChange={e => {\n                  setFilters(prev => ({\n                    ...prev,\n                    [key]: { eq: e.target.valueAsNumber }\n                  }))\n                }}\n                placeholder=\"What's your favourite number?\"\n                autoComplete=\"off\"\n              />\n            </div>\n          )\n        }\n        return (\n          <div key={key}>\n            <label className=\"text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\">\n              {key}:{' '}\n            </label>\n            <input\n              key={key}\n              type=\"number\"\n              className=\"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 flex-1 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n              value={value.eq}\n              onChange={e => {\n                setFilters(prev => ({\n                  ...prev,\n                  [key]: { eq: e.target.valueAsNumber }\n                }))\n              }}\n              placeholder=\"What's your favourite number?\"\n              autoComplete=\"off\"\n            />\n          </div>\n        )\n      })}\n      <Button\n        variant=\"secondary\"\n        onClick={() => setFilters(null)}\n        className=\"ml-auto\"\n      >\n        Clear\n      </Button>\n    </DemoContainer>\n  )\n}\n\ntype StarButtonProps = Omit<React.ComponentProps<typeof Button>, 'value'> & {\n  index: Rating\n  value: Rating | null\n  setValue: (value: Rating | null) => void\n}\n\nfunction StarButton({ index, value, setValue, ...props }: StarButtonProps) {\n  return (\n    <Button\n      size=\"icon\"\n      variant=\"ghost\"\n      onClick={() => setValue(index)}\n      {...props}\n    >\n      <Star\n        className={cn(\n          'star',\n          value !== null && value >= index && 'fill-current'\n        )}\n      />\n    </Button>\n  )\n}\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/making-your-own.mdx",
    "content": "---\ntitle: Custom parsers\ndescription: Making your own parsers for custom data types & pretty URLs\n---\n\nimport {\n  CustomParserDemo,\n  CustomMultiParserDemo\n} from '@/content/docs/parsers/demos'\n\nYou may wish to customise the rendered query string for your data type.\nFor this, `nuqs` exposes the `createParser{:ts}` function to make your own parsers.\n\nYou pass it two functions:\n1. `parse{:ts}`: a function that takes a string and returns the parsed value, or `null{:ts}` if invalid.\n2. `serialize{:ts}`: a function that takes the parsed value and returns a string.\n\n```ts\nimport { createParser } from 'nuqs'\n\nconst parseAsStarRating = createParser({\n  // [!code word:parse]\n  parse(queryValue) {\n    const inBetween = queryValue.split('★')\n    const isValid = inBetween.length > 1 && inBetween.every(s => s === '')\n    if (!isValid) return null\n    const numStars = inBetween.length - 1\n    return Math.min(5, numStars)\n  },\n  // [!code word:serialize]\n  serialize(value) {\n    return Array.from({length: value}, () => '★').join('')\n  }\n})\n```\n\n<Suspense>\n  <CustomParserDemo/>\n</Suspense>\n\n## Equality function\n\nFor state types that can't be compared by the `==={:ts}` operator, you'll need to\nprovide an `eq{:ts}` function as well:\n\n```ts\n\n// Eg: TanStack Table sorting state\n// /?sort=foo:asc → { id: 'foo', desc: false }\nconst parseAsSort = createParser({\n  parse(query) {\n    const [key = '', direction = ''] = query.split(':')\n    const desc = parseAsStringLiteral(['asc', 'desc']).parse(direction) ?? 'asc'\n    return {\n      id: key,\n      desc: desc === 'desc'\n    }\n  },\n  serialize(value) {\n    return `${value.id}:${value.desc ? 'desc' : 'asc'}`\n  },\n  // [!code highlight:3]\n  eq(a, b) {\n    return a.id === b.id && a.desc === b.desc\n  }\n})\n```\n\nThis is used for the [`clearOnDefault{:ts}`](/docs/options#clear-on-default) option,\nto check if the current value is equal to the default value.\n\n## Custom Multi Parsers\n\nThe parsers we've seen until now are `SingleParsers{:ts}`: they operate on **the first occurence** of the\nkey in the URL, and give you a string value to parse when it's available.\n\n`MultiParsers{:ts}` work similar to `SingleParsers{:ts}`, except that they operate on arrays, to support **key repetition**:\n\nimport { Querystring } from '@/src/components/querystring'\n\n<Querystring path=\"/\" value=\"?tag=type-safe&tag=url-state&tag=react\" />\n\nThis means:\n\n1. `parse{:ts}` takes an `Array<string>{:ts}`. It receives all matching values of the key it operates on, and returns the parsed value, or `null{:ts}` if invalid.\n2. `serialize{:ts}` takes the parsed value and returns an `Array<string>{:ts}`, where each item will be separately added to the URL.\n\nYou can then compose & reduce this array to form **complex data types**:\n\n<Suspense>\n  <CustomMultiParserDemo />\n</Suspense>\n\n```tsx\n/**\n * 100~200 <=> { gte: 100, lte: 200 }\n * 150     <=> { eq: 150 }\n */\nconst parseAsFromTo = createParser({\n  parse: value => {\n    const [min = null, max = null] = value.split('~').map(parseAsInteger.parse)\n    if (min === null) return null\n    if (max === null) return { eq: min }\n    return { gte: min, lte: max }\n  },\n  serialize: value => {\n    return value.eq !== undefined ? String(value.eq) : `${value.gte}~${value.lte}`\n  }\n})\n\n/**\n * foo:bar <=> { key: 'foo', value: 'bar' }\n */\nconst parseAsKeyValue = createParser({\n  parse: value => {\n    const [key, val] = value.split(':')\n    if (!key || !val) return null\n    return { key, value: val }\n  },\n  serialize: value => {\n    return `${value.key}:${value.value}`\n  }\n})\n\nconst parseAsFilters = <TItem extends {}>(itemParser: SingleParser<TItem>) => {\n  return createMultiParser({\n    parse: values => {\n      const keyValue = values.map(parseAsKeyValue.parse).filter(v => v !== null)\n\n      const result = Object.fromEntries(\n        keyValue.flatMap(({ key, value }) => {\n          const parsedValue: TItem | null = itemParser.parse(value)\n          return parsedValue === null ? [] : [[key, parsedValue]]\n        })\n      )\n\n      return Object.keys(result).length === 0 ? null : result\n    },\n    serialize: values => {\n      return Object.entries(values).map(([key, value]) => {\n        if (!itemParser.serialize) return null\n        return parseAsKeyValue.serialize({ key, value: itemParser.serialize(value) })\n      }).filter(v => v !== null)\n    }\n  })\n}\n\nconst [filters, setFilters] = useQueryState(\n  'filters',\n  parseAsFilters(parseAsFromTo).withDefault({})\n)\n```\n\n## Caveat: lossy serializers\n\nIf your serializer loses precision or doesn't accurately represent\nthe underlying state value, you will lose this precision when\nreloading the page or restoring state from the URL (eg: on navigation).\n\nExample:\n\n```ts\nconst geoCoordParser = {\n  parse: parseFloat,\n  serialize: v => v.toFixed(4) // Loses precision\n}\n\nconst [lat, setLat] = useQueryState('lat', geoCoordParser)\n```\n\nHere, setting a latitude of 1.23456789 will render a URL query string\nof `lat=1.2345`, while the internal `lat` state will be correctly\nset to 1.23456789.\n\nUpon reloading the page, the state will be incorrectly set to 1.2345.\n"
  },
  {
    "path": "packages/docs/content/docs/parsers/meta.json",
    "content": "{\n  \"title\": \"Parsers\",\n  \"pages\": [\"built-in\", \"making-your-own\", \"community\"]\n}\n"
  },
  {
    "path": "packages/docs/content/docs/seo.mdx",
    "content": "---\ntitle: SEO\ndescription: Pitfalls and best practices for SEO with query strings\n---\n\nIf your page uses query strings for local-only state, you should add a\ncanonical URL to your page, to tell SEO crawlers to ignore the query string\nand index the page without it.\n\nIn the Next.js app router, this is done via the metadata object:\n\n```ts\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  alternates: {\n    canonical: '/url/path/without/querystring'\n  }\n}\n```\n\nIf however the query string is defining what content the page is displaying\n(eg: YouTube's watch URLs, like `https://www.youtube.com/watch?v=dQw4w9WgXcQ`),\nyour canonical URL should contain relevant query strings, and you can still\nuse your parsers to read it, and to serialize the canonical URL.\n\n```ts title=\"/app/watch/page.tsx\"\nimport type { Metadata, ResolvingMetadata } from 'next'\nimport { notFound } from \"next/navigation\";\nimport {\n  createParser,\n  parseAsString,\n  createLoader,\n  createSerializer,\n  type SearchParams,\n  type UrlKeys\n} from 'nuqs/server'\n\nconst youTubeVideoIdRegex = /^[^\"&?\\/\\s]{11}$/i\nconst youTubeSearchParams = {\n  videoId: createParser({\n    parse(query) {\n      if (!youTubeVideoIdRegex.test(query)) {\n        return null\n      }\n      return query\n    },\n    serialize(videoId) {\n      return videoId\n    }\n  })\n}\nconst youTubeUrlKeys: UrlKeys<typeof youTubeSearchParams> = {\n  videoId: 'v'\n}\nconst loadYouTubeSearchParams = createLoader(\n  youTubeSearchParams,\n  {\n    urlKeys: youTubeUrlKeys\n  }\n)\nconst serializeYouTubeSearchParams = createSerializer(\n  youTubeSearchParams,\n  {\n    urlKeys: youTubeUrlKeys\n  }\n)\n\n// --\n\ntype Props = {\n  searchParams: Promise<SearchParams>\n}\n\nexport async function generateMetadata({\n  searchParams\n}: Props): Promise<Metadata> {\n  const { videoId } = await loadYouTubeSearchParams(searchParams)\n  if (!videoId) {\n    notFound()\n  }\n  return {\n    alternates: {\n      canonical: serializeYouTubeSearchParams('/watch', { videoId })\n      // /watch?v=dQw4w9WgXcQ\n    }\n  }\n}\n```\n"
  },
  {
    "path": "packages/docs/content/docs/server-side.mdx",
    "content": "---\ntitle: Server-Side usage\ndescription: Type-safe search params on the server\n---\n\n## Loaders\n\n<FeatureSupportMatrix introducedInVersion='2.3.0' />\n\nTo parse search params server-side, you can use a _loader_ function.\n\nYou create one using the `createLoader{:ts}` function, by passing it your search params\ndescriptor object:\n\n```tsx title=\"searchParams.tsx\"\n// [!code word:createLoader]\nimport { parseAsFloat, createLoader } from 'nuqs/server'\n\n// Describe your search params, and reuse this in useQueryStates / createSerializer:\nexport const coordinatesSearchParams = {\n  latitude: parseAsFloat.withDefault(0),\n  longitude: parseAsFloat.withDefault(0)\n}\n\nexport const loadSearchParams = createLoader(coordinatesSearchParams)\n```\n\nHere, `loadSearchParams{:ts}` is a function that parses search params and returns\nstate variables to be consumed server-side (the same state type that [`useQueryStates{:ts}`](/docs/batching) returns).\n\n<Tabs items={[\"Next.js (app router)\", \"Next.js (pages router)\", \"API routes\", \"Remix / React Router\", \"React / client-side\"]}>\n\n```tsx tab=\"Next.js (app router)\" title=\"app/page.tsx\"\n// [!code word:loadSearchParams]\nimport { loadSearchParams } from './search-params'\nimport type { SearchParams } from 'nuqs/server'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Page({ searchParams }: PageProps) {\n  const { latitude, longitude } = await loadSearchParams(searchParams)\n  return <Map\n    lat={latitude}\n    lng={longitude}\n  />\n\n  // Pro tip: you don't *have* to await the result.\n  // Pass the Promise object to children components wrapped in <Suspense>\n  // to benefit from PPR / dynamicIO and serve a static outer shell\n  // immediately, while streaming in the dynamic parts that depend on\n  // the search params when they become available.\n}\n```\n\n```ts tab=\"Next.js (pages router)\" title=\"pages/index.tsx\"\n// [!code word:loadSearchParams]\nimport type { GetServerSidePropsContext } from 'next'\n\nexport async function getServerSideProps({ query }: GetServerSidePropsContext) {\n  const { latitude, longitude } = loadSearchParams(query)\n  // Do some server-side calculations with the coordinates\n  return {\n    props: { ... }\n  }\n}\n```\n\n```tsx tab=\"Remix / React Router\" title=\"app/routes/_index.tsx\"\n// [!code word:loadSearchParams]\nexport function loader({ request }: LoaderFunctionArgs) {\n  const { latitude, longitude } = loadSearchParams(request) // request.url works too\n  // Do some server-side calculations with the coordinates\n  return ...\n}\n```\n\n```tsx tab=\"React / client-side\"\n// Note: you can also use this client-side (or anywhere, really),\n// for a one-off parsing of non-reactive search params:\n\nloadSearchParams('https://example.com?latitude=42&longitude=12')\nloadSearchParams(location.search)\nloadSearchParams(new URL(...))\nloadSearchParams(new URLSearchParams(...))\n```\n\n```tsx tab=\"API routes\"\n// App router, eg: app/api/location/route.ts\nexport async function GET(request: Request) {\n  const { latitude, longitude } = loadSearchParams(request)\n  // ...\n}\n\n// Pages router, eg: pages/api/location.ts\nimport type { NextApiRequest, NextApiResponse } from 'next'\nexport default function handler(\n  request: NextApiRequest,\n  response: NextApiResponse\n) {\n  const { latitude, longitude } = loadSearchParams(request.query)\n}\n```\n\n</Tabs>\n\n<Callout type=\"warn\" title=\"Note\">\n  Loaders **don't validate** your data. If you expect positive integers\n  or JSON-encoded objects of a particular shape, you'll need to feed the result\n  of the loader to a schema validation library, like [Zod](https://zod.dev).\n\n  Built-in validation support is coming. [Read the RFC](https://github.com/47ng/nuqs/discussions/446).\n  Alternatively, you can build validation into [custom parsers](/docs/parsers/making-your-own).\n</Callout>\n\nThe loader function will accept the following input types to parse search params from:\n- A string containing a fully qualified URL: `https://example.com/?foo=bar`\n- A string containing just search params: `?foo=bar` (like `location.search{:ts}`)\n- A `URL{:ts}` object\n- A `URLSearchParams{:ts}` object\n- A `Request{:ts}` object\n- A `Record<string, string | string[] | undefined>{:ts}` (eg: `{ foo: 'bar' }{:ts}`)\n- A `Promise{:ts}` of any of the above, in which case it also returns a Promise.\n\n### Strict mode\n\n<FeatureSupportMatrix introducedInVersion='2.5.0' />\n\nIf a search param contains an invalid value for the associated parser (eg: `?count=banana` for `parseAsInteger{:ts}`),\nthe default behaviour is to return the [default value](/docs/basic-usage#default-values) if specified, or `null{:ts}` otherwise.\n\nYou can turn on **strict mode** to instead throw an error on invalid values when running the loader:\n\n```ts\nconst loadSearchParams = createLoader({\n  count: parseAsInteger.withDefault(0)\n})\n\n// Default: will return { count: 0 }\nloadSearchParams('?count=banana')\n\n// Strict mode: will throw an error\nloadSearchParams('?count=banana', { strict: true })\n// [nuqs] Error while parsing query `banana` for key `count`\n```\n\n## Cache\n\n<FeatureSupportMatrix introducedInVersion='1.13.0' support={{ supported: true, frameworks: ['Next.js (app router)']}} />\n\nIf you wish to access the searchParams in a deeply nested Server Component\n(ie: not in the Page component), you can use `createSearchParamsCache{:ts}`\nto do so in a type-safe manner.\n\nThink of it as a loader combined with a way to propagate the parsed values down\nthe RSC tree, like Context would on the client.\n\n```ts title=\"searchParams.ts\"\nimport {\n  createSearchParamsCache,\n  parseAsInteger,\n  parseAsString\n} from 'nuqs/server'\n// Note: import from 'nuqs/server' to avoid the \"use client\" directive\n\nexport const searchParamsCache = createSearchParamsCache({\n  // List your search param keys and associated parsers here:\n  q: parseAsString.withDefault(''),\n  maxResults: parseAsInteger.withDefault(10)\n})\n```\n\n```tsx title=\"page.tsx\"\nimport { searchParamsCache } from './searchParams'\nimport { type SearchParams } from 'nuqs/server'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams> // Next.js 15+: async searchParams prop\n}\n\nexport default async function Page({ searchParams }: PageProps) {\n  // ⚠️ Don't forget to call `parse` here.\n  // You can access type-safe values from the returned object:\n  const { q: query } = await searchParamsCache.parse(searchParams)\n  return (\n    <div>\n      <h1>Search Results for {query}</h1>\n      <Results />\n    </div>\n  )\n}\n\nfunction Results() {\n  // Access type-safe search params in children server components:\n  const maxResults = searchParamsCache.get('maxResults')\n  return <span>Showing up to {maxResults} results</span>\n}\n```\n\nThe cache will only be valid for the current page render\n(see React's [`cache`](https://react.dev/reference/react/cache) function).\n\nNote: the cache only works for **server components**, but you may share your\nparser declaration with `useQueryStates` for type-safety in client components:\n\n```ts title=\"searchParams.ts\"\nimport {\n  parseAsFloat,\n  createSearchParamsCache\n} from 'nuqs/server'\n\nexport const coordinatesParsers = {\n  lat: parseAsFloat.withDefault(45.18),\n  lng: parseAsFloat.withDefault(5.72)\n}\nexport const coordinatesCache = createSearchParamsCache(coordinatesParsers)\n\n```\n\n```tsx title=\"page.tsx\"\nimport { coordinatesCache } from './searchParams'\nimport { Server } from './server'\nimport { Client } from './client'\n\nexport default async function Page({ searchParams }) {\n  // Note: you can also use strict mode here:\n  await coordinatesCache.parse(searchParams, { strict: true })\n  return (\n    <>\n      <Server />\n      <Suspense>\n        <Client />\n      </Suspense>\n    </>\n  )\n}\n\n```\n\n```tsx title=\"server.tsx\"\nimport { coordinatesCache } from './searchParams'\n\nexport function Server() {\n  const { lat, lng } = coordinatesCache.all()\n  // or access keys individually:\n  const lat = coordinatesCache.get('lat')\n  const lng = coordinatesCache.get('lng')\n  return (\n    <span>\n      Latitude: {lat} - Longitude: {lng}\n    </span>\n  )\n}\n\n```\n\n```tsx title=\"client.tsx\"\n'use client'\n\nimport { useQueryStates } from 'nuqs'\nimport { coordinatesParsers } from './searchParams'\n\nexport function Client() {\n  const [{ lat, lng }, setCoordinates] = useQueryStates(coordinatesParsers)\n  // ...\n}\n```\n\n### Shorter search params keys\n\nJust like with `useQueryStates{:ts}`, you can\ndefine a [`urlKeys{:ts}`](/docs/batching#shorter-search-params-keys) object to map the variable names defined by the parser to\nshorter keys in the URL. They will be translated on read and your codebase\ncan only refer to variable names that make sense for your domain or business logic.\n\n```ts title=\"searchParams.ts\"\nexport const coordinatesParsers = {\n  // Use human-readable variable names throughout your codebase\n  latitude: parseAsFloat.withDefault(45.18),\n  longitude: parseAsFloat.withDefault(5.72)\n}\nexport const coordinatesCache = createSearchParamsCache(coordinatesParsers, {\n  urlKeys: {\n    // Remap them to read from shorter keys in the URL\n    latitude: 'lat',\n    longitude: 'lng'\n  }\n})\n```\n"
  },
  {
    "path": "packages/docs/content/docs/testing.mdx",
    "content": "---\ntitle: Testing\ndescription: Some tips on testing components that use `nuqs`\n---\n\nSince nuqs 2, you can unit-test components that use `useQueryState(s){:ts}` hooks\nwithout needing to mock anything, by using a dedicated testing adapter that will\nfacilitate **setting up** your tests (with initial search params) and **asserting**\non URL changes when **acting** on your components.\n\n## Testing hooks with React Testing Library\n\nWhen testing hooks that rely on nuqs' `useQueryState(s){:ts}` with React Testing Library's\n[`renderHook{:ts}`](https://testing-library.com/docs/react-testing-library/api/#renderhook) function,\nyou can use `withNuqsTestingAdapter{:ts}` to get a wrapper component to pass to the\n`renderHook{:ts}` call:\n\n```tsx\nimport { withNuqsTestingAdapter } from 'nuqs/adapters/testing'\n\nconst { result } = renderHook(() => useTheHookToTest(), {\n  wrapper: withNuqsTestingAdapter({\n    searchParams: { count: \"42\" },\n  }),\n})\n```\n\n<FeatureSupportMatrix introducedInVersion='2.2.0' hideFrameworks />\n\n## Testing components with Vitest\n\nHere is an example for Vitest and Testing Library to test a button rendering\na counter:\n\n<Tabs items={['Vitest v1', 'Vitest v2']}>\n\n```tsx title=\"counter-button.test.tsx\" tab=\"Vitest v1\"\n// [!code word:withNuqsTestingAdapter]\nimport { render, screen } from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport { withNuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'\nimport { describe, expect, it, vi } from 'vitest'\nimport { CounterButton } from './counter-button'\n\nit('should increment the count when clicked', async () => {\n  const user = userEvent.setup()\n  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()\n  render(<CounterButton />, {\n    // 1. Setup the test by passing initial search params / querystring:\n    wrapper: withNuqsTestingAdapter({ searchParams: '?count=42', onUrlUpdate })\n  })\n  // 2. Act\n  const button = screen.getByRole('button')\n  await user.click(button)\n  // 3. Assert changes in the state and in the (mocked) URL\n  expect(button).toHaveTextContent('count is 43')\n  expect(onUrlUpdate).toHaveBeenCalledOnce()\n  const event = onUrlUpdate.mock.calls[0]![0]!\n  expect(event.queryString).toBe('?count=43')\n  expect(event.searchParams.get('count')).toBe('43')\n  expect(event.options.history).toBe('push')\n})\n```\n\n```tsx title=\"counter-button.test.tsx\" tab=\"Vitest v2\"\n// [!code word:withNuqsTestingAdapter]\nimport { render, screen } from '@testing-library/react'\nimport userEvent from '@testing-library/user-event'\nimport { withNuqsTestingAdapter, type OnUrlUpdateFunction } from 'nuqs/adapters/testing'\nimport { describe, expect, it, vi } from 'vitest'\nimport { CounterButton } from './counter-button'\n\nit('should increment the count when clicked', async () => {\n  const user = userEvent.setup()\n  const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n  render(<CounterButton />, {\n    // 1. Setup the test by passing initial search params / querystring:\n    wrapper: withNuqsTestingAdapter({ searchParams: '?count=42', onUrlUpdate })\n  })\n  // 2. Act\n  const button = screen.getByRole('button')\n  await user.click(button)\n  // 3. Assert changes in the state and in the (mocked) URL\n  expect(button).toHaveTextContent('count is 43')\n  expect(onUrlUpdate).toHaveBeenCalledOnce()\n  const event = onUrlUpdate.mock.calls[0]![0]!\n  expect(event.queryString).toBe('?count=43')\n  expect(event.searchParams.get('count')).toBe('43')\n  expect(event.options.history).toBe('push')\n})\n```\n\n</Tabs>\n\nSee issue [#259](https://github.com/47ng/nuqs/issues/259) for more testing-related discussions.\n\n## Jest and ESM\n\nSince nuqs 2 is an [ESM-only package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c),\nthere are a few hoops you need to jump through to make it work with Jest.\nThis is extracted from the [Jest ESM guide](https://jestjs.io/docs/ecmascript-modules).\n\n1. Add the following options to your jest.config.ts file:\n\n```ts title=\"jest.config.ts\"\nconst config: Config = {\n  // <Other options here>\n  // [!code highlight:2]\n  extensionsToTreatAsEsm: [\".ts\", \".tsx\"],\n  transform: {}\n};\n```\n\n2. Change your test command to include the `--experimental-vm-modules` flag:\n\n```json title=\"package.json\"\n// [!code word:--experimental-vm-modules]\n{\n  \"scripts\": {\n    \"test\": \"NODE_OPTIONS=\\\"$NODE_OPTIONS --experimental-vm-modules\\\" jest\"\n  }\n}\n```\n\n<Callout>\nAdapt accordingly for Windows with [`cross-env`](https://www.npmjs.com/package/cross-env).\n</Callout>\n\n## API\n\n`withNuqsTestingAdapter{:ts}` accepts the following arguments:\n\n- `searchParams{:ts}`: The initial search params to use for the test. These can be a\n  query string, a `URLSearchParams` object or a record object with string values.\n\n```tsx\nwithNuqsTestingAdapter({\n  searchParams: '?q=hello&limit=10'\n})\n\nwithNuqsTestingAdapter({\n  searchParams: new URLSearchParams('?q=hello&limit=10')\n})\n\nwithNuqsTestingAdapter({\n  searchParams: {\n    q: 'hello',\n    limit: '10' // Values are serialized strings\n  }\n})\n```\n\n- `onUrlUpdate{:ts}`: a function that will be called when the URL is updated\n  by the component. It receives an object with:\n  - the new search params as an instance of `URLSearchParams{:ts}`\n  - the new rendered query string (for convenience)\n  - the options used to update the URL.\n\n- `hasMemory{:ts}`: by default, the testing adapter is **immutable**,\nmeaning it will always use the initial search params as a base for URL\nupdates. This encourages testing units of behaviour in a single test.\n\nTo make it behave like framework adapters (which do store the\nupdates in the URL), set `hasMemory: true{:ts}`, so subsequent updates\nbuild up on the previous state. This memory is per-adapter instance,\nand so is isolated between tests, but shared for components under the same\nadapter.\n\n<details>\n<summary className='cursor-pointer'>🧪 Internal/advanced options</summary>\n\n- `rateLimitFactor{:ts}`. By default, rate limiting is disabled when testing,\nas it can lead to unexpected behaviours. Setting this to 1 will enable rate\nlimiting with the same factor as in production.\n\n- `resetUrlUpdateQueueOnMount{:ts}`: clear the URL update queue before running the test.\nThis is `true{:ts}` by default to isolate tests, but you can set it to `false{:ts}` to keep the\nURL update queue between renders and match the production behaviour more closely.\n\n</details>\n\n\n## NuqsTestingAdapter\n\nThe `withNuqsTestingAdapter{:ts}` function is a wrapper component factory function\nwraps children with a `NuqsTestingAdapter{:ts}`, but you can also use\nit directly:\n\n```tsx\n// [!code word:NuqsTestingAdapter]\nimport { NuqsTestingAdapter } from 'nuqs/adapters/testing'\n\n<NuqsTestingAdapter>\n  <ComponentsUsingNuqs/>\n</NuqsTestingAdapter>\n```\n\nIt takes the same props as the arguments you can pass to `withNuqsTestingAdapter{:ts}`.\n\n## Testing custom parsers\n\nIf you create custom parsers with `createParser{:ts}`, you will likely want to\ntest them.\n\nParsers should:\n1. Define pure functions for `parse{:ts}`, `serialize{:ts}`, and `eq{:ts}`.\n2. Be bijective: `parse(serialize(x)) === x{:ts}` and `serialize(parse(x)) === x{:ts}`.\n\nTo help test bijectivity, you can use helpers defined in `nuqs/testing`:\n\n```ts\n// [!code word:isParserBijective]\nimport {\n  isParserBijective,\n  testParseThenSerialize,\n  testSerializeThenParse\n} from 'nuqs/testing'\n\nit('is bijective', () => {\n  // Passing tests return true\n  expect(isParserBijective(parseAsInteger, '42', 42)).toBe(true)\n  // Failing test throws an error\n  expect(() => isParserBijective(parseAsInteger, '42', 47)).toThrowError()\n\n  // You can also test either side separately:\n  expect(testParseThenSerialize(parseAsInteger, '42')).toBe(true)\n  expect(testSerializeThenParse(parseAsInteger, 42)).toBe(true)\n  // Those will also throw an error if the test fails,\n  // which makes it easier to isolate which side failed:\n  expect(() => testParseThenSerialize(parseAsInteger, 'not a number')).toThrowError()\n  expect(() => testSerializeThenParse(parseAsInteger, NaN)).toThrowError()\n})\n```\n\n<FeatureSupportMatrix introducedInVersion='2.4.0' hideFrameworks />\n"
  },
  {
    "path": "packages/docs/content/docs/tips-tricks.mdx",
    "content": "---\ntitle: Tips and tricks\ndescription: A collection of good practices and tips to help you get the most out of nuqs\n---\n\n## Reusing hooks\n\nIf you find yourself reusing the same hooks in multiple components,\nyou can create a custom hook to encapsulate the parser configuration.\n\n<Callout title=\"Tip\">\nAll query states bound to the same key will be synchronized across components.\n</Callout>\n\n```ts title=\"hooks/useCoordinates.ts\"\n'use client'\n\nimport { useQueryStates, parseAsFloat } from 'nuqs'\n\nexport function useCoordinates() {\n  return useQueryStates({\n    lat: parseAsFloat.withDefault(0),\n    lng: parseAsFloat.withDefault(0),\n  })\n}\n```\n\n```tsx title=\"components/Map.tsx\"\n'use client'\n\nimport { useCoordinates } from '../hooks/useCoordinates'\n\nfunction MapView() {\n  const [{ lat, lng }] = useCoordinates() // Read-only\n  return (\n    <div>\n      Latitude: {lat}\n      Longitude: {lng}\n    </div>\n  )\n}\n\nfunction MapControls() {\n  const [{ lat, lng }, setCoordinates] = useCoordinates()\n  return (\n    <div>\n      <input\n        type=\"number\"\n        value={lat}\n        onChange={(e) => setCoordinates({ lat: e.target.valueAsNumber })}\n      />\n      <input\n        type=\"number\"\n        value={lng}\n        onChange={(e) => setCoordinates({ lng: e.target.valueAsNumber })}\n      />\n    </div>\n  )\n}\n```\n"
  },
  {
    "path": "packages/docs/content/docs/troubleshooting.mdx",
    "content": "---\ntitle: Troubleshooting\ndescription: Common issues and solutions\n---\n\n<Callout title=\"Tip\">\n  Check out the list of [known issues and solutions](https://github.com/47ng/nuqs/issues/423).\n</Callout>\n\n## Pages router\n\nBecause the Next.js **pages router** is not available in an SSR context, this\nhook will always return `null{:ts}` (or the default value if supplied) on SSR/SSG.\n\nThis limitation doesn't apply to the app router.\n\n## Caveats\n\n### Different parsers on the same key\n\nHooks are synced together on a per-key basis, so if you use different parsers\non the same key, the last state update will be propagated to all other hooks\nusing that key. It can lead to unexpected states like this:\n\n```ts\nconst [int] = useQueryState('foo', parseAsInteger)\nconst [float, setFloat] = useQueryState('foo', parseAsFloat)\n\nsetFloat(1.234)\n\n// `int` is now 1.234, instead of 1\n```\n\nWe recommend you abstract a key/parser pair into a dedicated hook to avoid this,\nand derive any desired state from the value:\n\n```ts\nfunction useIntFloat() {\n  const [float, setFloat] = useQueryState('foo', parseAsFloat)\n  const int = Math.floor(float)\n  return [{int, float}, setFloat] as const\n}\n```\n\n## Client components need to be wrapped in a `<Suspense>` boundary\n\nYou may have encountered the following [error message](https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout)\nfrom Next.js:\n\n```\nMissing Suspense boundary with useSearchParams\n```\n\nComponents using hooks like `useQueryState` should be wrapped in `<Suspense>` when rendered within a page component. Adding the 'use client' directive to the page.tsx file is the immediate fix to get things working if you are defining components that use client-side features (like nuqs or React Hooks):\n\n```tsx\n'use client'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const [foo, setFoo] = useQueryState('foo')\n  // ...\n}\n```\n\nHowever, the steps below indicate the optimal approach to get better UX: separating server and client files (and moving client side code as low down the tree as possible to pre-render the outer shell).\n\nThe recommended approach is:\n\n1. Keep page.tsx as a server component (no `'use client'{:ts}` directive)\n2. Move client-side features into a separate client file.\n3. Wrap the client component in a `<Suspense>` boundary.\n\n"
  },
  {
    "path": "packages/docs/content/docs/utilities.mdx",
    "content": "---\ntitle: Utilities\ndescription: Utilities for working with query strings\n---\n\n## Serializer helper\n\n<FeatureSupportMatrix introducedInVersion='1.16.0' hideFrameworks />\n\nTo populate `<Link>{:tsx}` components with state values, you can use the `createSerializer{:ts}`\nhelper.\n\nPass it an object describing your search params, and it will give you a function\nto call with values, that generates a query string serialized as the hooks would do.\n\nExample:\n\n```ts\n// [!code word:createSerializer]\nimport {\n  createSerializer,\n  parseAsInteger,\n  parseAsIsoDateTime,\n  parseAsString,\n  parseAsStringLiteral\n} from 'nuqs/server' // can also be imported from 'nuqs' in client code\n\nconst searchParams = {\n  search: parseAsString,\n  limit: parseAsInteger,\n  from: parseAsIsoDateTime,\n  to: parseAsIsoDateTime,\n  sortBy: parseAsStringLiteral(['asc', 'desc'])\n}\n\n// Create a serializer function by passing the description of the search params to accept\nconst serialize = createSerializer(searchParams)\n\n// Then later, pass it some values (a subset) and render them to a query string\nserialize({\n  search: 'foo bar',\n  limit: 10,\n  from: new Date('2024-01-01'),\n  // here, we omit `to`, which won't be added\n  sortBy: null // null values are also not rendered\n})\n// ?search=foo+bar&limit=10&from=2024-01-01T00:00:00.000Z\n```\n\n### Base parameter\n\nThe returned `serialize{:ts}` function can take a base parameter over which to\nappend/amend the search params:\n\n```ts\nserialize('/path?baz=qux', { foo: 'bar' }) // /path?baz=qux&foo=bar\n\nconst search = new URLSearchParams('?baz=qux')\nserialize(search, { foo: 'bar' }) // ?baz=qux&foo=bar\n\nconst url = new URL('https://example.com/path?baz=qux')\nserialize(url, { foo: 'bar' }) // https://example.com/path?baz=qux&foo=bar\n\n// Passing null removes existing values\nserialize('?remove=me', { foo: 'bar', remove: null }) // ?foo=bar\n```\n\n### Shorter search params keys\n\nJust like with `useQueryStates{:ts}`, you can\ndefine a [`urlKeys{:ts}`](/docs/batching#shorter-search-params-keys)\nobject to map the variable names defined by the parsers to shorter keys in the URL:\n\n```ts\nconst serialize = createSerializer(\n  {\n    // 1. Use variable names that make sense for your domain/business logic\n    latitude: parseAsFloat,\n    longitude: parseAsFloat,\n    zoomLevel: parseAsInteger\n  },\n  {\n    // 2. Remap them to shorter keys in the URL\n    urlKeys: {\n      latitude: 'lat',\n      longitude: 'lng',\n      zoomLevel: 'z'\n    }\n  }\n)\n\n// 3. Use your variable names when calling the serializer,\n// and the shorter keys will be rendered in the URL:\nserialize({\n  latitude: 45.18,\n  longitude: 5.72,\n  zoomLevel: 12\n})\n// ?lat=45.18&lng=5.72&z=12\n```\n\n### Processing URLSearchParams\n\n<FeatureSupportMatrix introducedInVersion='2.6.0' />\n\nJust like in the [`<NuqsAdapter>{:tsx}`](/docs/options#processing-urlsearchparams),\nyou can specify a `processUrlSearchParams{:ts}` function\nto modify the search params before they are serialized.\n\nFor example, it can be useful for consistent canonical URLs (for SEO),\nby sorting search param keys alphabetically:\n\n```ts\n// [!code word:processUrlSearchParams]\nconst serialize = createSerializer(\n  {\n    a: parseAsInteger,\n    z: parseAsInteger\n  },\n  {\n    processUrlSearchParams: (search) => {\n      // Note: you can modify search in-place,\n      // or return a copy, as you wish.\n      search.sort()\n      return search\n    }\n  }\n)\n\nserialize('?foo=bar', {\n  a: 1,\n  z: 1\n})\n// ?a=1&foo=bar&z=1\n// merged, then sorted\n```\n\n## Parser type inference\n\n<FeatureSupportMatrix introducedInVersion='1.18.0' />\n\nTo access the underlying type returned by a parser, you can use the\n`inferParserType{:ts}` type helper:\n\n```ts\nimport { parseAsInteger, type inferParserType } from 'nuqs' // or 'nuqs/server'\n\nconst intNullable = parseAsInteger\nconst intNonNull = parseAsInteger.withDefault(0)\n\ninferParserType<typeof intNullable> // number | null\ninferParserType<typeof intNonNull> // number\n```\n\nFor an object describing parsers (that you'd pass to [`createLoader{:ts}`](/docs/server-side#loaders)\nor to [`useQueryStates{:ts}`](/docs/batching#usequerystates)), `inferParserType{:ts}` will\nreturn the type of the object with the parsers replaced by their inferred types:\n\n```ts\nimport {\n  parseAsBoolean,\n  parseAsInteger,\n  type inferParserType\n} from 'nuqs' // or 'nuqs/server'\n\nconst parsers = {\n  a: parseAsInteger,\n  b: parseAsBoolean.withDefault(false)\n}\n\ninferParserType<typeof parsers>\n// { a: number | null, b: boolean }\n```\n\n## Standard Schema\n\n<FeatureSupportMatrix introducedInVersion='2.5.0' />\n\nSearch param definitions can be turned into a [Standard Schema](https://standardschema.dev)\nfor validating external sources and passing on type inference to other tools.\n\n```ts\n// [!code word:validateSearchParams]\nimport {\n  createStandardSchemaV1,\n  parseAsInteger,\n  parseAsString,\n} from 'nuqs' // or 'nuqs/server'\n\n// 1. Define your search params as usual\nexport const searchParams = {\n  searchTerm: parseAsString.withDefault(''),\n  maxResults: parseAsInteger.withDefault(10)\n}\n\n// 2. Then create a Standard Schema compatible validator\nexport const validateSearchParams = createStandardSchemaV1(searchParams)\n\n// 3. Use it with other tools, like tRPC:\nrouter({\n  search: publicProcedure.input(validateSearchParams).query(...)\n})\n```\n\n### TanStack Router & validateSearch\n\nYou can pass the standard schema validator to\n[TanStack Router](https://tanstack.com/router/)'s `validateSearch{:ts}` for type-safe\nlinking to nuqs URL state, but in order to keep those\nvalues optional (as nuqs uses different defaults strategies\nthan TSR), you need to mark the output as `Partial{:ts}`,\nusing the `partialOutput{:ts}` option:\n\n```ts title=\"src/routes/search.tsx\"\n// [!code word:partialOutput]\nimport { createStandardSchemaV1 } from 'nuqs'\n\nconst validateSearch = createStandardSchemaV1(searchParams, {\n  partialOutput: true\n})\n\nexport const Route = createFileRoute('/search')({\n  validateSearch\n})\n```\n\n<Callout title=\"Note\" type=\"warn\">\n TanStack Router support is still experimental and comes with caveats,\n see the [adapter documentation](/docs/adapters#tanstack-router) for more\n information about what is supported.\n</Callout>\n"
  },
  {
    "path": "packages/docs/mdx-components.tsx",
    "content": "import { HumanContent, LLMContent } from '@/src/components/audience'\nimport { FeatureSupportMatrix } from '@/src/components/feature-support-matrix'\nimport { Callout } from 'fumadocs-ui/components/callout'\nimport { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock'\nimport { Tab, Tabs } from 'fumadocs-ui/components/tabs'\nimport defaultMdxComponents from 'fumadocs-ui/mdx'\nimport type { MDXComponents } from 'mdx/types'\nimport { Suspense } from 'react'\n\ndeclare module 'mdx/types.js' {\n  // Augment the MDX types to make it understand React.\n  namespace JSX {\n    type Element = React.JSX.Element\n    type ElementClass = React.JSX.ElementClass\n    type ElementType = React.JSX.ElementType\n    type IntrinsicElements = React.JSX.IntrinsicElements\n  }\n}\n\nconst components = {\n  ...defaultMdxComponents,\n  Callout,\n  FeatureSupportMatrix,\n  HumanContent,\n  LLMContent,\n  Suspense,\n  Tab,\n  Tabs,\n  pre: ({ ref: _ref, children, ...props }) => (\n    <CodeBlock {...props}>\n      <Pre>{children}</Pre>\n    </CodeBlock>\n  )\n} satisfies MDXComponents\n\ndeclare global {\n  type MDXProvidedComponents = typeof components\n}\n\nexport function useMDXComponents(): MDXProvidedComponents {\n  return components\n}\n"
  },
  {
    "path": "packages/docs/next.config.mjs",
    "content": "// @ts-check\n\nimport { withSentryConfig } from '@sentry/nextjs'\nimport { createMDX } from 'fumadocs-mdx/next'\n\nconst withFumadocsMDX = createMDX()\n\nconst enableSentry =\n  process.env.ENABLE_SENTRY === 'true' &&\n  Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN) &&\n  Boolean(process.env.SENTRY_AUTH_TOKEN) &&\n  ['production', 'preview'].includes(process.env.VERCEL_ENV ?? '')\n\n/** @type {import('next').NextConfig} */\nconst config = {\n  outputFileTracingIncludes: {\n    '/playground/pagination': [\n      './src/app/playground/(demos)/pagination/search-params.ts',\n      './src/app/playground/(demos)/pagination/page.tsx',\n      './src/app/playground/(demos)/pagination/pagination-controls.server.tsx',\n      './src/app/playground/(demos)/pagination/pagination-controls.client.tsx'\n    ]\n  },\n  reactCompiler: true,\n  reactStrictMode: true,\n  turbopack: {\n    debugIds: enableSentry\n  },\n  experimental: {\n    isolatedDevBuild: true\n  },\n  productionBrowserSourceMaps: enableSentry,\n  redirects: async () => {\n    return [\n      {\n        // Jump straight to the first page (no root index page)\n        source: '/docs',\n        destination: '/docs/installation',\n        permanent: false\n      },\n      {\n        source: '/docs/parsers/community',\n        destination: '/docs/parsers/community/tanstack-table',\n        permanent: false\n      },\n      // Cool URLs don't break\n      {\n        source: '/docs/parsers',\n        destination: '/docs/parsers/built-in',\n        permanent: true\n      },\n      {\n        source: '/docs/community-adapters/inertia',\n        destination: '/registry/adapter-inertia',\n        permanent: true\n      },\n      {\n        source: '/docs/community-adapters/waku',\n        destination: '/registry/adapter-waku',\n        permanent: true\n      },\n      {\n        source: '/docs/community-adapters/onejs',\n        destination: '/registry/adapter-onejs',\n        permanent: true\n      },\n      // Moved from err.47ng.com/NUQS-123\n      {\n        source: '/NUQS-:code(\\\\d{3})',\n        destination:\n          'https://github.com/47ng/nuqs/blob/next/errors/NUQS-:code.md',\n        permanent: false\n      }\n    ]\n  },\n  async rewrites() {\n    return [\n      {\n        source: '/docs/:path*.md',\n        destination: '/llms/:path*'\n      }\n    ]\n  },\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'pbs.twimg.com',\n        pathname: '/profile_images/**'\n      },\n      {\n        protocol: 'https',\n        hostname: 'avatars.githubusercontent.com',\n        pathname: '/u/**'\n      },\n      {\n        protocol: 'https',\n        hostname: 'i.redd.it',\n        pathname: '/snoovatar/avatars/**'\n      }\n    ]\n  }\n}\n\n/**\n * @type {import('@sentry/nextjs').SentryBuildOptions}\n */\nconst sentryConfig = {\n  // For all available options, see:\n  // https://github.com/getsentry/sentry-webpack-plugin#options\n\n  silent: true,\n  org: process.env.SENTRY_ORG,\n  project: process.env.SENTRY_PROJECT,\n  authToken: process.env.SENTRY_AUTH_TOKEN,\n\n  // Upload a larger set of source maps for prettier stack traces (increases build time)\n  widenClientFileUpload: true,\n\n  release: {\n    setCommits: process.env.VERCEL_GIT_COMMIT_SHA\n      ? {\n          // https://github.com/getsentry/sentry-javascript-bundler-plugins/issues/443#issuecomment-1815988709\n          repo: '47ng/nuqs',\n          commit: process.env.VERCEL_GIT_COMMIT_SHA\n        }\n      : { auto: true }\n  },\n\n  // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. (increases server load)\n  // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-\n  // side errors will fail.\n  // tunnelRoute: '/sentry',\n\n  // Automatically tree-shake Sentry logger statements to reduce bundle size\n  // disableLogger: true,\n\n  // Enables automatic instrumentation of Vercel Cron Monitors.\n  // See the following for more information:\n  // https://docs.sentry.io/product/crons/\n  // https://vercel.com/docs/cron-jobs\n  automaticVercelMonitors: true,\n\n  debug: false\n}\n\nif (enableSentry) {\n  console.info('Sentry is enabled for this build.')\n}\n\nexport default enableSentry\n  ? withSentryConfig(withFumadocsMDX(config), sentryConfig)\n  : withFumadocsMDX(config)\n"
  },
  {
    "path": "packages/docs/package.json",
    "content": "{\n  \"name\": \"docs\",\n  \"version\": \"0.0.0-internal\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"author\": {\n    \"name\": \"François Best\",\n    \"email\": \"contact@francoisbest.com\",\n    \"url\": \"https://francoisbest.com\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/47ng/nuqs.git\",\n    \"directory\": \"packages/docs\"\n  },\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"pnpm run build:registry && pnpm run build:next\",\n    \"build:next\": \"next build\",\n    \"build:registry\": \"./src/registry/assemble.ts && shadcn build\",\n    \"start\": \"next start\",\n    \"postinstall\": \"fumadocs-mdx\",\n    \"pretest\": \"fumadocs-mdx && next typegen\",\n    \"test\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@databuddy/sdk\": \"^2.3.29\",\n    \"@faker-js/faker\": \"^10.2.0\",\n    \"@headlessui/react\": \"2.2.9\",\n    \"@headlessui/tailwindcss\": \"^0.2.2\",\n    \"@icons-pack/react-simple-icons\": \"^13.8.0\",\n    \"@number-flow/react\": \"^0.5.11\",\n    \"@radix-ui/react-checkbox\": \"^1.3.3\",\n    \"@radix-ui/react-label\": \"^2.1.8\",\n    \"@radix-ui/react-popover\": \"^1.1.15\",\n    \"@radix-ui/react-select\": \"^2.2.6\",\n    \"@radix-ui/react-separator\": \"^1.1.8\",\n    \"@radix-ui/react-slider\": \"^1.3.6\",\n    \"@radix-ui/react-slot\": \"^1.2.4\",\n    \"@radix-ui/react-switch\": \"^1.2.6\",\n    \"@radix-ui/react-tabs\": \"^1.1.13\",\n    \"@radix-ui/react-toggle\": \"^1.1.10\",\n    \"@radix-ui/react-toggle-group\": \"^1.1.11\",\n    \"@radix-ui/react-tooltip\": \"^1.2.8\",\n    \"@sentry/nextjs\": \"^10.38.0\",\n    \"@tailwindcss/container-queries\": \"^0.1.1\",\n    \"@tailwindcss/postcss\": \"^4.1.18\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"dayjs\": \"^1.11.19\",\n    \"effect\": \"^3.19.15\",\n    \"fumadocs-core\": \"16.0.4\",\n    \"fumadocs-mdx\": \"13.0.2\",\n    \"fumadocs-ui\": \"16.0.4\",\n    \"lucide-react\": \"^0.563.0\",\n    \"mdast-util-mdx-jsx\": \"^3.2.0\",\n    \"mdast-util-to-markdown\": \"^2.1.2\",\n    \"next\": \"catalog:next\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"recharts\": \"3.8.0\",\n    \"remark-smartypants\": \"^3.0.2\",\n    \"res\": \"workspace:*\",\n    \"server-only\": \"^0.0.1\",\n    \"tailwind-merge\": \"^3.4.0\",\n    \"tailwindcss\": \"^4.1.18\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"zod\": \"^4.3.6\"\n  },\n  \"devDependencies\": {\n    \"@shikijs/transformers\": \"^3.22.0\",\n    \"@types/mdast\": \"^4.0.4\",\n    \"@types/mdx\": \"^2.0.13\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"hast-util-to-jsx-runtime\": \"^2.3.6\",\n    \"import-in-the-middle\": \"^3.0.0\",\n    \"postcss\": \"^8.5.6\",\n    \"prettier-plugin-tailwindcss\": \"^0.7.2\",\n    \"require-in-the-middle\": \"^8.0.1\",\n    \"shadcn\": \"^3.8.2\",\n    \"shiki\": \"^3.22.0\",\n    \"typescript\": \"^5.9.3\",\n    \"unified\": \"^11.0.5\"\n  },\n  \"postcss\": {\n    \"plugins\": {\n      \"@tailwindcss/postcss\": {}\n    }\n  },\n  \"prettier\": {\n    \"arrowParens\": \"avoid\",\n    \"semi\": false,\n    \"singleQuote\": true,\n    \"tabWidth\": 2,\n    \"trailingComma\": \"none\",\n    \"useTabs\": false,\n    \"plugins\": [\n      \"prettier-plugin-tailwindcss\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/docs/public/.well-known/atproto-did",
    "content": "did:plc:bw4kguuz764ponbbn2aaycxk\n"
  },
  {
    "path": "packages/docs/rehype-code.config.ts",
    "content": "import type { RehypeCodeOptions } from 'fumadocs-core/mdx-plugins'\n\nexport const rehypeCodeOptions: RehypeCodeOptions = {\n  themes: {\n    light: 'catppuccin-latte',\n    dark: 'catppuccin-mocha'\n  },\n  inline: 'tailing-curly-colon',\n  defaultColor: false\n}\n"
  },
  {
    "path": "packages/docs/sentry.edge.config.ts",
    "content": "// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).\n// The config you add here will be used whenever one of the edge features is loaded.\n// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.\n// https://docs.sentry.io/platforms/javascript/guides/nextjs/\n\nimport * as Sentry from '@sentry/nextjs'\n\nconst enabled =\n  process.env.ENABLE_SENTRY === 'true' &&\n  Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN) &&\n  Boolean(process.env.SENTRY_AUTH_TOKEN) &&\n  ['production', 'preview'].includes(process.env.VERCEL_ENV ?? '')\n\nSentry.init({\n  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,\n  enabled,\n\n  // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.\n  tracesSampleRate: 1,\n\n  // Setting this option to true will print useful information to the console while you're setting up Sentry.\n  debug: false\n})\n"
  },
  {
    "path": "packages/docs/sentry.server.config.ts",
    "content": "// This file configures the initialization of Sentry on the server.\n// The config you add here will be used whenever the server handles a request.\n// https://docs.sentry.io/platforms/javascript/guides/nextjs/\n\nimport * as Sentry from '@sentry/nextjs'\n\nconst enabled =\n  process.env.ENABLE_SENTRY === 'true' &&\n  Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN) &&\n  Boolean(process.env.SENTRY_AUTH_TOKEN) &&\n  ['production', 'preview'].includes(process.env.VERCEL_ENV ?? '')\n\nSentry.init({\n  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,\n  enabled,\n\n  // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.\n  tracesSampleRate: 1,\n\n  // Setting this option to true will print useful information to the console while you're setting up Sentry.\n  debug: false\n})\n"
  },
  {
    "path": "packages/docs/source.config.ts",
    "content": "import {\n  defineCollections,\n  defineConfig,\n  defineDocs,\n  frontmatterSchema\n} from 'fumadocs-mdx/config'\nimport remarkSmartypants from 'remark-smartypants'\nimport { z } from 'zod'\nimport { rehypeCodeOptions } from './rehype-code.config'\nimport { remarkAudience } from './src/lib/remark-audience'\n\nexport default defineConfig({\n  lastModifiedTime: 'git',\n  mdxOptions: {\n    remarkPlugins: [remarkSmartypants, remarkAudience],\n    rehypeCodeOptions\n  }\n})\n\nexport const { docs, meta } = defineDocs({\n  dir: 'content/docs',\n  docs: {\n    schema: frontmatterSchema.extend({\n      exposeTo: z\n        .array(z.enum(['user', 'llm']))\n        .min(1)\n        .default(['user', 'llm'])\n    }),\n    postprocess: {\n      includeProcessedMarkdown: true\n    }\n  }\n})\n\nexport const blog = defineCollections({\n  dir: 'content/blog',\n  schema: frontmatterSchema.extend({\n    author: z.string(),\n    date: z.string().date().or(z.date()).optional()\n  }),\n  type: 'doc'\n})\n"
  },
  {
    "path": "packages/docs/src/app/(llms)/llms/[...slug]/route.tsx",
    "content": "import { source } from '@/src/app/source'\nimport { getLLMText } from '@/src/lib/get-llm-text'\nimport { notFound } from 'next/navigation'\nimport { type NextRequest, NextResponse } from 'next/server'\n\nexport const revalidate = false\n\nexport async function GET(\n  _req: NextRequest,\n  { params }: RouteContext<'/llms/[...slug]'>\n) {\n  const slug = (await params).slug\n  const page = source.getPage(slug)\n  if (!page) notFound()\n\n  return new NextResponse(await getLLMText(page), {\n    headers: {\n      'Content-Type': 'text/markdown'\n    }\n  })\n}\n\nexport function generateStaticParams() {\n  return source.generateParams()\n}\n"
  },
  {
    "path": "packages/docs/src/app/(llms)/llms-full.txt/route.ts",
    "content": "import { fullSource } from '@/src/app/source'\nimport { getLLMText } from '@/src/lib/get-llm-text'\nimport { flattenTree } from 'fumadocs-core/page-tree'\n\nexport const revalidate = false\n\nexport async function GET() {\n  const orderedItems = flattenTree(fullSource.pageTree.children)\n  const allPages = fullSource.getPages()\n  const llmPages = allPages.filter(page => page.data.exposeTo.includes('llm'))\n\n  // Get pages in sidebar order first\n  const orderedPages = orderedItems\n    .map(item => llmPages.find(page => page.url === item.url))\n    .filter((page): page is NonNullable<typeof page> => page !== undefined)\n\n  // Add any llm-only pages that aren't in the sidebar (not in meta.json)\n  const orderedUrls = new Set(orderedPages.map(p => p.url))\n  const llmOnlyPages = llmPages.filter(page => !orderedUrls.has(page.url))\n\n  const pages = [...orderedPages, ...llmOnlyPages]\n\n  const scan = pages.map(getLLMText)\n  const scanned = await Promise.all(scan)\n\n  const header = `nuqs - Type-safe search params state management for React. Like useState, but stored in the URL query string.\n\n--\n\n`\n\n  return new Response(header + scanned.join('\\n---\\n\\n'))\n}\n"
  },
  {
    "path": "packages/docs/src/app/(llms)/llms.txt/route.ts",
    "content": "import { fullSource } from '@/src/app/source'\nimport { getBaseUrl } from '@/src/lib/url'\nimport { flattenTree } from 'fumadocs-core/page-tree'\n\nexport const revalidate = false\n\nexport async function GET() {\n  const baseUrl = getBaseUrl()\n  const orderedItems = flattenTree(fullSource.pageTree.children)\n  const allPages = fullSource.getPages()\n  const llmPages = allPages.filter(page => page.data.exposeTo.includes('llm'))\n\n  // Get pages in sidebar order first\n  const orderedPages = orderedItems\n    .map(item => llmPages.find(page => page.url === item.url))\n    .filter((page): page is NonNullable<typeof page> => page !== undefined)\n\n  // Add any llm-only pages that aren't in the sidebar\n  const orderedUrls = new Set(orderedPages.map(p => p.url))\n  const llmOnlyPages = llmPages.filter(page => !orderedUrls.has(page.url))\n\n  const pages = [...orderedPages, ...llmOnlyPages]\n\n  const lines = [\n    'nuqs - Type-safe search params state management for React. Like useState, but stored in the URL query string.',\n    '',\n    '# Documentation index',\n    '',\n    'This index lists all documentation pages available to LLMs.',\n    '',\n    '## Available pages',\n    '',\n    ...pages.map(\n      page =>\n        `- [${page.data.title}](${baseUrl}${page.url}.md): ${page.data.description ?? ''}`\n    ),\n    '',\n    '## Complete documentation',\n    '',\n    `For the complete documentation in a single file, fetch: ${baseUrl}/llms-full.txt`\n  ]\n\n  return new Response(lines.join('\\n'))\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/(confs)/nextjs-conf-25/page.tsx",
    "content": "import { LinkTree, type LinkTreeItemProps } from '@/src/components/link-tree'\nimport {\n  SiBluesky,\n  SiGithub,\n  SiTwitch,\n  SiX,\n  SiYoutube\n} from '@icons-pack/react-simple-icons'\nimport { Heart, Images, Library, Linkedin } from 'lucide-react'\nimport { NextJSConf2025Logo } from '../../../banners'\n\nconst links: LinkTreeItemProps[] = [\n  {\n    href: '/',\n    icon: <Library className=\"size-5\" />,\n    label: 'Documentation'\n  },\n  {\n    href: 'https://github.com/47ng/nuqs',\n    icon: <SiGithub className=\"size-5\" />,\n    detail: '(give nuqs a star! ⭐)',\n    label: 'GitHub'\n  },\n  {\n    href: 'https://github.com/franky47/nextjs-conf-25-demo',\n    icon: <SiGithub className=\"size-5\" />,\n    label: 'Demo app'\n  },\n  {\n    href: 'https://nuqs.dev/nextjs-conf-25-slides.pdf',\n    icon: <Images className=\"size-5\" />,\n    label: 'Slides'\n  },\n  {\n    href: 'https://github.com/sponsors/franky47',\n    icon: <Heart className=\"size-5 text-pink-600 dark:text-pink-400\" />,\n    label: 'Sponsor me'\n  },\n  {\n    href: 'https://bsky.app/profile/francoisbest.com',\n    icon: <SiBluesky className=\"size-5 text-sky-500\" />,\n    label: 'Bluesky',\n    detail: '@francoisbest.com'\n  },\n  {\n    href: 'https://bsky.app/profile/nuqs.dev',\n    icon: <SiBluesky className=\"size-5 text-sky-500\" />,\n    label: 'Bluesky',\n    detail: '@nuqs.dev'\n  },\n  {\n    href: 'https://x.com/nuqs47ng',\n    icon: <SiX className=\"size-5\" aria-label=\"X\" />,\n    label: 'Twitter',\n    detail: '@nuqs47ng'\n  },\n  {\n    href: 'https://www.youtube.com/@47ng-dev',\n    icon: <SiYoutube className=\"size-5 text-red-500\" />,\n    label: 'YouTube'\n  },\n  {\n    href: 'https://www.twitch.tv/francoisbest',\n    icon: <SiTwitch className=\"size-5 text-purple-500\" />,\n    label: 'Twitch'\n  },\n  {\n    href: 'https://www.linkedin.com/in/francoisbest/',\n    icon: <Linkedin className=\"size-5 text-[#0077B5]\" />,\n    label: 'LinkedIn'\n  }\n]\n\nexport default function Page() {\n  return (\n    <section className=\"container max-w-lg py-12\">\n      <div className=\"flex items-center justify-center gap-12\">\n        <NextJSConf2025Logo className=\"mb-8 w-48\" />\n      </div>\n      <p className=\"mb-2 text-center\">Thanks for attending my talk! 🫶</p>\n      <p className=\"mb-8 text-center text-balance\">\n        Here are some useful links to learn more about nuqs and to get in touch\n        with me:\n      </p>\n      <LinkTree items={links} />\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/(confs)/react-advanced-25/page.tsx",
    "content": "import { LinkTree, type LinkTreeItemProps } from '@/src/components/link-tree'\nimport {\n  SiBluesky,\n  SiGithub,\n  SiTwitch,\n  SiX,\n  SiYoutube\n} from '@icons-pack/react-simple-icons'\nimport { Heart, Images, Library, Linkedin } from 'lucide-react'\nimport { ReactAdvancedLondonLogo } from '../../../banners'\n\nconst links: LinkTreeItemProps[] = [\n  {\n    href: '/',\n    icon: <Library className=\"size-5\" />,\n    label: 'Documentation'\n  },\n  {\n    href: 'https://github.com/47ng/nuqs',\n    icon: <SiGithub className=\"size-5\" />,\n    detail: '(give nuqs a star! ⭐)',\n    label: 'GitHub'\n  },\n  {\n    href: 'https://github.com/franky47/react-advanced-london-2025-demo',\n    icon: <SiGithub className=\"size-5\" />,\n    label: 'Demo app'\n  },\n  {\n    href: 'https://nuqs.dev/react-advanced-london-2025-slides.pdf',\n    icon: <Images className=\"size-5\" />,\n    label: 'Slides'\n  },\n  {\n    href: 'https://github.com/sponsors/franky47',\n    icon: <Heart className=\"size-5 text-pink-600 dark:text-pink-400\" />,\n    label: 'Sponsor me'\n  },\n  {\n    href: 'https://bsky.app/profile/francoisbest.com',\n    icon: <SiBluesky className=\"size-5 text-sky-500\" />,\n    label: 'Bluesky',\n    detail: '@francoisbest.com'\n  },\n  {\n    href: 'https://bsky.app/profile/nuqs.dev',\n    icon: <SiBluesky className=\"size-5 text-sky-500\" />,\n    label: 'Bluesky',\n    detail: '@nuqs.dev'\n  },\n  {\n    href: 'https://x.com/nuqs47ng',\n    icon: <SiX className=\"size-5\" aria-label=\"X\" />,\n    label: 'Twitter',\n    detail: '@nuqs47ng'\n  },\n  {\n    href: 'https://www.youtube.com/@47ng-dev',\n    icon: <SiYoutube className=\"size-5 text-red-500\" />,\n    label: 'YouTube'\n  },\n  {\n    href: 'https://www.twitch.tv/francoisbest',\n    icon: <SiTwitch className=\"size-5 text-purple-500\" />,\n    label: 'Twitch'\n  },\n  {\n    href: 'https://www.linkedin.com/in/francoisbest/',\n    icon: <Linkedin className=\"size-5 text-[#0077B5]\" />,\n    label: 'LinkedIn'\n  }\n]\n\nexport default function Page() {\n  return (\n    <section className=\"container max-w-lg py-12\">\n      <div className=\"flex items-center justify-center gap-12\">\n        <ReactAdvancedLondonLogo className=\"mb-8 w-48\" />\n      </div>\n      <p className=\"mb-2 text-center\">Thanks for attending my talk! 🫶</p>\n      <p className=\"mb-8 text-center text-balance\">\n        Here are some useful links to learn more about nuqs and to get in touch\n        with me:\n      </p>\n      <LinkTree items={links} />\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/(confs)/react-paris/page.tsx",
    "content": "import { Logo47ng } from '@/src/components/47ng'\nimport { LinkTree, type LinkTreeItemProps } from '@/src/components/link-tree'\nimport { ReactParisLogo } from '@/src/components/react-paris'\nimport {\n  SiBluesky,\n  SiDiscord,\n  SiGithub,\n  SiTwitch,\n  SiX,\n  SiYoutube\n} from '@icons-pack/react-simple-icons'\nimport { Heart, Images, Library, Linkedin, Mail } from 'lucide-react'\n\nconst links: LinkTreeItemProps[] = [\n  {\n    href: 'https://discord.com/channels/723276276072317098/1352179917680283743',\n    icon: <SiDiscord className=\"size-5\" />,\n    label: 'Questions & Feedback 🙏'\n  },\n  {\n    href: '/',\n    icon: <Library className=\"size-5\" />,\n    label: 'Documentation'\n  },\n  {\n    href: 'https://github.com/47ng/nuqs',\n    icon: <SiGithub className=\"size-5\" />,\n    detail: '(give nuqs a star! ⭐)',\n    label: 'GitHub'\n  },\n  {\n    href: 'https://github.com/franky47/react-paris-25-demo',\n    icon: <SiGithub className=\"size-5\" />,\n    label: 'Demo app'\n  },\n  {\n    href: 'https://nuqs.dev/react-paris-25-slides.pdf',\n    icon: <Images className=\"size-5\" />,\n    label: 'Slides'\n  },\n  {\n    href: 'https://github.com/sponsors/franky47',\n    icon: <Heart className=\"size-5 text-pink-600 dark:text-pink-400\" />,\n    label: 'Sponsor me'\n  },\n  {\n    href: 'https://bsky.app/profile/francoisbest.com',\n    icon: <SiBluesky className=\"size-5 text-sky-500\" />,\n    label: 'Bluesky',\n    detail: '@francoisbest.com'\n  },\n  {\n    href: 'https://bsky.app/profile/nuqs.dev',\n    icon: <SiBluesky className=\"size-5 text-sky-500\" />,\n    label: 'Bluesky',\n    detail: '@nuqs.dev'\n  },\n  {\n    href: 'https://x.com/nuqs47ng',\n    icon: <SiX className=\"size-5\" aria-label=\"X\" />,\n    label: 'Twitter',\n    detail: '@nuqs47ng'\n  },\n  {\n    href: 'https://www.youtube.com/@47ng-dev',\n    icon: <SiYoutube className=\"size-5 text-red-500\" />,\n    label: 'YouTube'\n  },\n  {\n    href: 'https://www.twitch.tv/francoisbest',\n    icon: <SiTwitch className=\"size-5 text-purple-500\" />,\n    label: 'Twitch'\n  },\n  {\n    href: 'https://www.linkedin.com/in/francoisbest/',\n    icon: <Linkedin className=\"size-5 text-[#0077B5]\" />,\n    label: 'LinkedIn'\n  },\n  {\n    href: 'mailto:nuqs@47ng.com',\n    icon: <Mail className=\"size-5\" />,\n    label: 'Contact me'\n  }\n]\n\nexport default function Page() {\n  return (\n    <section className=\"container max-w-lg py-12\">\n      <div className=\"flex items-center justify-center gap-12\">\n        <Logo47ng className=\"size-16\" />\n        <ReactParisLogo className=\"-mx-4 mb-4 size-24 translate-y-1.5\" />\n      </div>\n      <p className=\"mb-2 text-center\">Thanks for attending my talk! 🫶</p>\n      <p className=\"mb-8 text-center text-balance\">\n        Here are some useful links to learn more about nuqs, and how to find me\n        on social media:\n      </p>\n      <LinkTree items={links} />\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/bundle-size.tsx",
    "content": "import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nfunction prettyBytes(size: number) {\n  const formatter = new Intl.NumberFormat('en-GB', {\n    style: 'unit',\n    unit: 'byte',\n    notation: 'compact',\n    unitDisplay: 'narrow',\n    maximumFractionDigits: 1\n  })\n  return formatter.format(size).replace('K', ' k')\n}\n\nexport async function BundleSize() {\n  const filePath = path.resolve(process.cwd(), '../../packages/nuqs/size.json')\n  try {\n    const json = await fs.readFile(filePath, 'utf8')\n    const [{ size }] = JSON.parse(json)\n    return prettyBytes(size)\n  } catch (error) {\n    console.error(error)\n    return 'less than 6 kB'\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/contributors.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { z } from 'zod'\n\nconst contributorSchema = z.object({\n  login: z.string(),\n  html_url: z.url(),\n  avatar_url: z.url(),\n  type: z.string(),\n  contributions: z.number()\n})\ntype Contributor = z.infer<typeof contributorSchema>\n\nasync function fetchContributors(): Promise<Contributor[]> {\n  const headers: Record<string, string> = {\n    Accept: 'application/vnd.github+json'\n  }\n  if (process.env.GITHUB_TOKEN) {\n    headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`\n  }\n\n  let allContributors: Contributor[] = []\n  let page = 1\n  const perPage = 100 // GitHub API max per page\n\n  while (true) {\n    const url = new URL('https://api.github.com/repos/47ng/nuqs/contributors')\n    url.searchParams.set('per_page', perPage.toString())\n    url.searchParams.set('page', page.toString())\n    // anon=false by default; we only want registered users\n\n    const res = await fetch(url.toString(), {\n      headers,\n      next: {\n        tags: ['contributors']\n      }\n    })\n\n    if (!res.ok) {\n      throw new Error(\n        `Failed to fetch contributors: ${res.status} ${res.statusText}`\n      )\n    }\n\n    const data = await res.json()\n    const contributors = z.array(contributorSchema).parse(data)\n\n    // If we get fewer contributors than perPage, we've reached the end\n    if (contributors.length < perPage) {\n      allContributors = allContributors.concat(contributors)\n      break\n    }\n\n    allContributors = allContributors.concat(contributors)\n    page++\n  }\n\n  // Known bot account IDs - easily editable list\n  const knownBotIds = new Set([\n    'dependabot[bot]',\n    'dependabot-preview[bot]',\n    'renovate[bot]',\n    'renovate-bot',\n    'depfu[bot]',\n    'greenkeeper[bot]',\n    'mergify[bot]',\n    'mergify-bot',\n    'github-actions[bot]',\n    'github-actions-bot',\n    'codecov[bot]',\n    'codecov-io[bot]',\n    'snyk-bot',\n    'snyk[bot]',\n    'semantic-release[bot]',\n    'semantic-release-bot',\n    'release-drafter[bot]',\n    'release-drafter-bot',\n    'stale[bot]',\n    'stale-bot',\n    'app[bot]',\n    'app-bot',\n    'web-flow[bot]',\n    'web-flow-bot'\n  ])\n\n  // Filter bots (type Bot, or known bot accounts, or login ending with [bot])\n  const isHuman = (c: Contributor) => {\n    const loginLower = c.login.toLowerCase()\n\n    // Check if it's explicitly a Bot type\n    if (c.type === 'Bot') return false\n\n    // Check against known bot IDs (exact match)\n    if (knownBotIds.has(loginLower)) return false\n\n    // Check if login ends with [bot] (common pattern for GitHub bots)\n    if (loginLower.endsWith('[bot]')) return false\n\n    return true\n  }\n\n  const humans = allContributors.filter(isHuman)\n  humans.sort((a, b) => b.contributions - a.contributions)\n  return humans\n}\n\nexport async function ContributorsSection() {\n  let contributors: Contributor[] = []\n  try {\n    contributors = await fetchContributors()\n  } catch (error) {\n    console.error(error)\n    return <section className=\"text-red-500\">{String(error)}</section>\n  }\n\n  if (contributors.length === 0) return null\n\n  return (\n    <section className=\"container mb-24\">\n      <h2 className=\"mb-12 text-center text-3xl font-bold tracking-tighter md:text-4xl xl:text-5xl dark:text-white\">\n        Contributors\n      </h2>\n      <ul\n        className={cn(\n          'flex flex-wrap justify-center gap-x-3 gap-y-4 md:gap-x-4'\n        )}\n      >\n        {contributors.map(c => (\n          <li key={c.login} className=\"flex flex-col items-center\">\n            <a\n              href={c.html_url}\n              className=\"h-16 w-16 rounded-full transition-transform hover:scale-110 md:h-20 md:w-20\"\n              title={`${c.login} (${c.contributions} contributions)`}\n            >\n              <img\n                src={`${c.avatar_url}&s=200`}\n                alt={c.login}\n                className=\"mx-auto h-16 w-16 rounded-full md:h-20 md:w-20\"\n                loading=\"lazy\"\n                width={80}\n                height={80}\n              />\n            </a>\n          </li>\n        ))}\n      </ul>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/demo.client.tsx",
    "content": "'use client'\n\n// [!code word:useQueryState]\nimport { parseAsInteger, useQueryState } from 'nuqs'\n\nexport function Demo() {\n  const [hello, setHello] = useQueryState('hello', { defaultValue: '' })\n  const [count, setCount] = useQueryState(\n    'count',\n    parseAsInteger.withDefault(0)\n  )\n  return (\n    <>\n      <button\n        className=\"peer bg-primary text-primary-foreground ring-offset-background hover:bg-primary/90 focus-visible:ring-ring inline-flex h-10 items-center justify-center rounded-md px-4 py-2 text-sm font-medium whitespace-nowrap tabular-nums transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50\"\n        onClick={() => setCount(c => c + 1)}\n        data-interacted={count > 0}\n      >\n        Count: {count}\n      </button>\n      <input\n        value={hello}\n        placeholder=\"Enter your name\"\n        autoComplete=\"off\"\n        className=\"peer border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n        onChange={e => setHello(e.target.value || null)}\n        data-interacted={Boolean(hello)}\n      />\n      <p className=\"max-w-full break-words line-clamp-2 sm:truncate\">\n        Hello, {hello || 'anonymous visitor'}!\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/demo.tsx",
    "content": "import { CodeBlock } from '@/src/components/code-block'\nimport fs from 'node:fs/promises'\nimport { format } from 'prettier'\nimport { Suspense } from 'react'\nimport { Demo } from './demo.client'\n\nexport async function LandingDemo() {\n  const demoFilePath =\n    process.cwd() + '/src/app/(pages)/_landing/demo.client.tsx'\n  const demoFile = await fs.readFile(demoFilePath, 'utf8')\n  // Poor man's AST manipulation\n  const demoCode = demoFile\n    .replace(/className=\".+\"/g, '') // Strip styling\n    .replace('autoComplete=\"off\"', '') // Strip irrelevant attributes\n    .split('\\n')\n    .filter(line => !line.includes('data-interacted='))\n    .join('\\n')\n  const formattedCode = await format(demoCode, {\n    parser: 'typescript'\n  })\n  return (\n    <>\n      <Suspense\n        fallback={\n          <div className=\"mb-4 h-[136px] animate-pulse rounded bg-zinc-50 sm:h-10 dark:bg-zinc-900\" />\n        }\n      >\n        <div className=\"mb-4 flex flex-col gap-4 sm:flex-row sm:items-center\">\n          <Demo />\n          <LookAtTheURL />\n        </div>\n      </Suspense>\n      <CodeBlock code={formattedCode.trim()} />\n    </>\n  )\n}\n\nfunction LookAtTheURL() {\n  return (\n    <>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 315 72\"\n        className=\"pointer-events-none absolute -top-20 right-0 left-0 mx-auto w-64 opacity-0 transition-opacity select-none peer-data-[interacted=true]:opacity-100 sm:right-auto sm:left-16 sm:mx-0 xl:-top-6 xl:-left-72 xl:mx-0\"\n      >\n        <g\n          // Arrow\n          strokeLinecap=\"round\"\n          className=\"opacity-50\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M72 50c-3 2-11 10-18 11s-17 1-23-3c-6-3-10-8-14-16-3-8-6-27-7-32m62 40c-3 2-11 10-18 11s-17 1-23-3c-6-3-10-8-14-16-3-8-6-27-7-32\"\n            strokeDasharray=\"3 4\"\n          />\n          <path d=\"m18 24-8-14m8 14-8-14\" />\n          <path d=\"m7 26 3-16M7 26l3-16\" />\n        </g>\n        <g\n          // Text\n          fill=\"currentColor\"\n        >\n          <path d=\"M92.0004 52.8517C88.734 52.7597 82.1643 54.5445 81.7051 49.7045C82.2244 45.0641 85.4917 40.8334 85.6709 36.2531C88.7787 33.3884 87.7595 39.0805 86.774 40.928C86.3567 43.8113 83.3661 47.1269 84.7779 49.8577C88.4384 52.2298 94.423 47.6524 96.833 52.0638C96.4027 54.4093 93.547 52.441 92.0004 52.8517ZM117.319 42.4251C121.456 41.3467 128.248 46.2608 124.393 50.3041C121.381 53.8324 112.228 53.7254 112.285 48.5752C112.701 44.9113 113.309 44.1938 117.319 42.4251ZM114.78 49.2536C116.64 52.6099 123.789 50.5272 123.106 46.7586C120.459 43.0037 114.448 44.5458 114.78 49.2536ZM130.242 48.991C128.75 45.9256 129.87 52.7896 127.642 52.8254C125.068 52.6705 126.33 49.5625 127.064 48.3081C127.085 44.1724 126.515 39.6086 127.09 35.7016C130.43 36.2969 128.554 41.9138 129.428 44.1847C133.999 42.5861 138.029 37.796 143.216 38.6956C144.149 40.8298 139.015 40.5746 137.674 42.0836C136.19 43.8113 132.218 44.4222 131.896 46.7323C132.456 49.3266 134.917 50.1748 136.755 50.6193C137.976 53.5907 133.902 53.8457 132.723 51.562C131.662 50.9593 130.985 49.9078 130.242 48.991ZM163.276 48.9647C161.393 52.672 154.431 55.1847 153.112 49.7001C152.747 44.9879 159.247 40.9377 162.934 43.817C165.425 46.0797 166.257 50.3012 169.763 51.7749C168.103 55.5856 164.612 50.4709 163.276 48.9647ZM155.292 48.9647C157.156 53.2225 163.043 47.4569 161.7 44.7888C158.889 44.7212 155.907 46.0123 155.292 48.9647ZM202.362 51.7224C201.897 47.9314 203.726 45.2453 198.948 47.0737C196.851 49.0364 195.457 44.9335 198.496 44.2086C203.112 44.0097 202.146 43.8417 202.021 39.2734C204.898 37.5277 203.55 42.607 205.015 43.0817C207.081 43.0775 211.41 41.1662 211.844 43.9221C209.755 45.2156 206.69 44.7017 204.516 45.9181C203.452 48.3021 207.19 52.0507 203.702 53.0356C202.901 52.9748 202.697 52.2594 202.362 51.7224Z\" />\n          <path d=\"M219.387 46.4958C216.243 47.5337 215.763 51.6177 213.136 52.9829C210.232 51.3014 213.086 47.2739 211.981 44.526C212.728 42.4511 210.406 37.8319 212.664 37.0409C215.072 39.9266 213.619 44.6874 214.476 47.8352C216.245 45.9692 219.923 41.2182 222.04 45.3927C222.971 47.7964 224.237 50.837 223.616 53.298C220.569 53.1977 221.485 47.6717 219.387 46.4958ZM224.801 49.7525C225.362 44.9719 231.592 39.1128 235.884 43.528C238.895 48.1222 231.048 51.5073 228.084 47.9403C224.979 51.1567 230.798 55.1352 234.098 53.8496C236.583 53.8398 239.758 51.8355 241.925 53.2193C241.666 56.1568 237.044 55.1351 234.939 56.0557C230.704 57.0988 224.571 54.8558 224.801 49.7525ZM228.846 46.522C230.481 47.0176 234.393 47.5295 233.941 44.9462C232.085 43.4403 229.932 44.9215 228.846 46.522ZM252.201 44.6134C252.839 42.613 252.13 38.5144 254.854 38.1526C257.128 40.4324 253.211 43.793 254.778 46.6577C255.164 50.6854 260.015 51.0103 261.672 47.6776C263.699 45.0722 266.022 41.6893 264.954 38.3015C267.499 35.7948 267.86 43.4427 266.214 45.075C263.98 49.0668 262.226 52.6309 256.97 52.7465C253.412 51.9424 252.051 47.8824 252.201 44.6134ZM268.252 46.6534C269.466 42.3169 266.531 32.8108 271.562 34.1782C275.729 36.0535 281.706 35.8095 284.378 40.3501C284.708 43.8413 280.066 44.8347 277.734 45.3139C279.385 47.767 284.776 49.3113 283.196 52.7728C280.366 51.6928 278.636 48.4618 275.816 46.9948C274.088 46.0576 270.254 42.9116 273.426 41.5057C275.735 42.5861 279.933 43.8114 281.542 41.0592C280.222 38.4011 276.188 37.7704 273.426 37.356C268.585 36.5019 272.258 40.924 270.747 43.6593C269.543 46.7024 272.066 49.3584 271.089 52.1424C267.718 55.093 268.533 48.1864 268.252 46.6534ZM295.491 52.8516C292.225 52.7596 285.655 54.5443 285.196 49.7044C285.716 45.0639 288.983 40.8333 289.162 36.253C292.27 33.3883 291.251 39.0804 290.265 40.9279C289.848 43.8111 286.857 47.1267 288.269 49.8575C291.929 52.2297 297.914 47.6523 300.324 52.0637C299.899 54.4127 297.027 52.4369 295.491 52.8516ZM305.094 30.6326C308.022 31.6789 305.307 35.9247 305.54 39.5097C305.401 43.1228 304.726 44.2941 305.139 47.8173C305.612 49.2803 304.085 50.8044 302.791 48.328C302.864 46.011 303.7 35.6977 305.094 30.6326Z\" />\n          <path d=\"M304.078 53.2732C304.762 53.2732 305.316 52.7405 305.316 52.0834C305.316 51.4263 304.762 50.8936 304.078 50.8936C303.395 50.8936 302.841 51.4263 302.841 52.0834C302.841 52.7405 303.395 53.2732 304.078 53.2732Z\" />\n          <path d=\"M174.385 51.97C173.92 48.1789 175.749 45.4927 170.971 47.3212C168.874 49.284 167.479 45.181 170.519 44.4561C175.134 44.2572 174.169 44.0891 174.044 39.5207C176.921 37.775 175.573 42.8544 177.038 43.3291C179.104 43.3249 183.433 41.4136 183.867 44.1695C181.778 45.4631 178.713 44.9492 176.539 46.1656C175.475 48.5497 179.213 52.2983 175.725 53.2832C174.924 53.2225 174.72 52.507 174.385 51.97Z\" />\n          <path d=\"M102.575 42.4747C106.713 41.3963 113.504 46.3105 109.649 50.354C106.638 53.8824 97.4838 53.7753 97.5413 48.625C97.9569 44.961 98.5655 44.2435 102.575 42.4747ZM100.036 49.3034C101.896 52.6598 109.046 50.5771 108.362 46.8083C105.716 43.0534 99.7041 44.5955 100.036 49.3034Z\" />\n        </g>\n      </svg>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/dependents.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { z } from 'zod'\n\nconst dependentSchema = z.object({\n  stars: z.number(),\n  owner: z.string(),\n  name: z.string(),\n  pkg: z.string(),\n  avatarURL: z.string(),\n  version: z.string().nullable(),\n  createdAt: z.string().transform(date => new Date(date))\n})\nexport type Dependent = z.infer<typeof dependentSchema>\n\nexport async function fetchDependents() {\n  const data = await fetch('https://dependents.47ng.com', {\n    next: {\n      revalidate: 86_400 // 24 hours\n    }\n  }).then(res => res.json())\n  return z.array(dependentSchema).parse(data)\n}\n\nexport function DependentsSection() {\n  return (\n    <section className=\"container space-y-16\">\n      <h2 className=\"text-center text-3xl font-bold tracking-tighter md:text-4xl xl:text-5xl dark:text-white\">\n        Used by\n      </h2>\n      <SponsoredUsedBy />\n      <DependentsLeaderboard />\n    </section>\n  )\n}\n\nfunction SponsoredUsedBy() {\n  return (\n    <p className=\"flex flex-wrap items-center justify-center gap-x-16 gap-y-8\">\n      <a href=\"https://vercel.com\">\n        <svg\n          aria-label=\"Vercel\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 284 65\"\n          className=\"inline h-8 fill-black dark:fill-white\"\n        >\n          <path d=\"M141.68 16.25c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm117.14-14.5c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm-39.03 3.5c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9v-46h9zM37.59.25l36.95 64H.64l36.95-64zm92.38 5l-27.71 48-27.71-48h10.39l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10v14.8h-9v-34h9v9.2c0-5.08 5.91-9.2 13.2-9.2z\" />\n        </svg>\n      </a>\n      <a href=\"https://unkey.com\">\n        <svg\n          aria-label=\"Unkey\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"93\"\n          height=\"40\"\n          viewBox=\"0 0 93 40\"\n        >\n          <path\n            d=\"M10.8 30.3C4.8 30.3 1.38 27.12 1.38 21.66V9.9H4.59V21.45C4.59 25.5 6.39 27.18 10.8 27.18C15.21 27.18 17.01 25.5 17.01 21.45V9.9H20.25V21.66C20.25 27.12 16.83 30.3 10.8 30.3ZM26.3611 30H23.1211V15.09H26.0911V19.71H26.3011C26.7511 17.19 28.7311 14.79 32.5111 14.79C36.6511 14.79 38.6911 17.58 38.6911 21.03V30H35.4511V21.9C35.4511 19.11 34.1911 17.7 31.1011 17.7C27.8311 17.7 26.3611 19.38 26.3611 22.62V30ZM44.8181 30H41.5781V9.9H44.8181V21H49.0781L53.5481 15.09H57.3281L51.7181 22.26L57.2981 30H53.4881L49.0781 23.91H44.8181V30ZM66.4219 30.3C61.5319 30.3 58.3219 27.54 58.3219 22.56C58.3219 17.91 61.5019 14.79 66.3619 14.79C70.9819 14.79 74.1319 17.34 74.1319 21.87C74.1319 22.41 74.1019 22.83 74.0119 23.28H61.3519C61.4719 26.16 62.8819 27.69 66.3319 27.69C69.4519 27.69 70.7419 26.67 70.7419 24.9V24.66H73.9819V24.93C73.9819 28.11 70.8619 30.3 66.4219 30.3ZM66.3019 17.34C63.0019 17.34 61.5619 18.81 61.3819 21.48H71.0719V21.42C71.0719 18.66 69.4819 17.34 66.3019 17.34ZM78.9586 35.1H76.8286V32.16H79.7386C81.0586 32.16 81.5986 31.8 82.0486 30.78L82.4086 30L75.0586 15.09H78.6886L82.4986 23.01L83.9686 26.58H84.2086L85.6186 22.98L89.1286 15.09H92.6986L84.9286 31.62C83.6986 34.29 82.0186 35.1 78.9586 35.1Z\"\n            className=\"fill-black dark:fill-white\"\n          />\n        </svg>\n      </a>\n      <a href=\"https://openstatus.dev\">\n        <svg\n          aria-label=\"OpenStatus\"\n          viewBox=\"0 0 171 30\"\n          className=\"inline h-6 fill-black dark:fill-white\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <path d=\"M12.48 23.416C10.304 23.416 8.32 22.904 6.528 21.88C4.75733 20.8347 3.36 19.4267 2.336 17.656C1.312 15.8853 0.8 13.944 0.8 11.832C0.8 9.72 1.312 7.77867 2.336 6.008C3.36 4.216 4.75733 2.79733 6.528 1.752C8.32 0.706665 10.304 0.183998 12.48 0.183998C14.656 0.183998 16.6293 0.706665 18.4 1.752C20.1707 2.79733 21.5573 4.216 22.56 6.008C23.584 7.77867 24.096 9.72 24.096 11.832C24.096 13.944 23.584 15.8853 22.56 17.656C21.5573 19.4267 20.1707 20.8347 18.4 21.88C16.6293 22.904 14.656 23.416 12.48 23.416ZM5.44 11.832C5.44 13.1973 5.728 14.4453 6.304 15.576C6.88 16.7067 7.70133 17.6027 8.768 18.264C9.83467 18.9253 11.072 19.256 12.48 19.256C13.888 19.256 15.1147 18.9253 16.16 18.264C17.2267 17.6027 18.0373 16.7067 18.592 15.576C19.168 14.4453 19.456 13.1973 19.456 11.832C19.456 10.4667 19.168 9.21867 18.592 8.088C18.0373 6.936 17.2267 6.02933 16.16 5.368C15.1147 4.68533 13.888 4.344 12.48 4.344C11.072 4.344 9.83467 4.68533 8.768 5.368C7.70133 6.02933 6.88 6.936 6.304 8.088C5.728 9.21867 5.44 10.4667 5.44 11.832ZM26.3425 6.36H30.5985V9.048C31.0892 8.06667 31.7505 7.32 32.5825 6.808C33.4145 6.27467 34.5238 6.008 35.9105 6.008C37.4038 6.008 38.7798 6.40267 40.0385 7.192C41.2972 7.98133 42.2892 9.048 43.0145 10.392C43.7612 11.7147 44.1345 13.1653 44.1345 14.744C44.1345 16.3227 43.7612 17.7733 43.0145 19.096C42.2892 20.4187 41.2972 21.4747 40.0385 22.264C38.7798 23.032 37.4038 23.416 35.9105 23.416C34.5665 23.416 33.4678 23.1707 32.6145 22.68C31.7825 22.168 31.1318 21.4533 30.6625 20.536V29.4H26.3425V6.36ZM30.5345 14.744C30.5345 15.6187 30.7265 16.4187 31.1105 17.144C31.4945 17.8693 32.0385 18.4453 32.7425 18.872C33.4678 19.2987 34.2892 19.512 35.2065 19.512C36.1025 19.512 36.8918 19.2987 37.5745 18.872C38.2785 18.424 38.8225 17.8373 39.2065 17.112C39.6118 16.3653 39.8145 15.5653 39.8145 14.712C39.8145 13.8373 39.6225 13.0373 39.2385 12.312C38.8545 11.5867 38.3105 11.0107 37.6065 10.584C36.9025 10.136 36.1025 9.912 35.2065 9.912C34.2892 9.912 33.4678 10.136 32.7425 10.584C32.0385 11.0107 31.4945 11.5973 31.1105 12.344C30.7265 13.0693 30.5345 13.8693 30.5345 14.744ZM53.873 23.416C52.1877 23.416 50.6943 23.032 49.393 22.264C48.0917 21.4747 47.0783 20.4187 46.353 19.096C45.6277 17.7733 45.265 16.312 45.265 14.712C45.265 13.1333 45.6383 11.672 46.385 10.328C47.1317 8.984 48.1557 7.91733 49.457 7.128C50.7797 6.33867 52.2517 5.944 53.873 5.944C55.4943 5.944 56.945 6.33867 58.225 7.128C59.5263 7.91733 60.529 8.984 61.233 10.328C61.9583 11.6507 62.321 13.112 62.321 14.712C62.321 15.2453 62.2783 15.7573 62.193 16.248H49.745C50.001 17.2507 50.481 18.0613 51.185 18.68C51.9103 19.2773 52.8063 19.576 53.873 19.576C54.769 19.576 55.569 19.3733 56.273 18.968C56.977 18.5413 57.5317 17.9973 57.937 17.336L61.297 19.864C60.6143 20.9307 59.6117 21.7947 58.289 22.456C56.9663 23.096 55.4943 23.416 53.873 23.416ZM57.969 13.016C57.713 12.056 57.2117 11.256 56.465 10.616C55.7183 9.976 54.833 9.656 53.809 9.656C52.8063 9.656 51.9317 9.96533 51.185 10.584C50.4597 11.2027 49.9797 12.0133 49.745 13.016H57.969ZM64.2175 6.36H68.4735V8.792C69.3482 6.91467 71.0442 5.976 73.5615 5.976C74.7562 5.976 75.8335 6.264 76.7935 6.84C77.7535 7.39467 78.5108 8.19467 79.0655 9.24C79.6202 10.264 79.8975 11.4587 79.8975 12.824V23H75.5775V13.88C75.5775 12.4933 75.2575 11.4587 74.6175 10.776C73.9775 10.0933 73.1028 9.752 71.9935 9.752C71.0122 9.752 70.1908 10.104 69.5295 10.808C68.8682 11.4907 68.5375 12.5147 68.5375 13.88V23H64.2175V6.36ZM89.9273 23.416C88.0713 23.416 86.4073 23.032 84.9353 22.264C83.4846 21.4747 82.2899 20.248 81.3513 18.584L85.2233 16.12C85.7779 17.2293 86.4819 18.0613 87.3353 18.616C88.2099 19.1493 89.1593 19.416 90.1833 19.416C91.2073 19.416 92.0179 19.1813 92.6153 18.712C93.2339 18.2427 93.5433 17.624 93.5433 16.856C93.5433 16.152 93.3193 15.576 92.8713 15.128C92.4233 14.68 91.8473 14.328 91.1433 14.072C90.4606 13.7947 89.5539 13.5067 88.4233 13.208C86.6313 12.7387 85.2339 11.9813 84.2313 10.936C83.2499 9.89067 82.7593 8.54667 82.7593 6.904C82.7593 5.58133 83.0899 4.41867 83.7513 3.416C84.4126 2.392 85.3406 1.60267 86.5353 1.048C87.7299 0.493333 89.1059 0.216 90.6633 0.216C92.3486 0.216 93.7779 0.568 94.9513 1.272C96.1459 1.976 97.1806 3.01067 98.0553 4.376L94.2153 6.744C93.6606 5.848 93.0953 5.208 92.5193 4.824C91.9433 4.41867 91.2499 4.216 90.4392 4.216C89.5219 4.216 88.7753 4.44 88.1993 4.888C87.6233 5.336 87.3353 5.93333 87.3353 6.68C87.3353 7.42667 87.6233 7.992 88.1993 8.376C88.7753 8.73867 89.6713 9.112 90.8873 9.496C92.3806 9.96533 93.5753 10.4027 94.4713 10.808C95.3673 11.2133 96.1993 11.896 96.9673 12.856C97.7353 13.7947 98.1193 15.0427 98.1193 16.6C98.1193 17.944 97.7886 19.1387 97.1273 20.184C96.4659 21.208 95.5166 22.008 94.2793 22.584C93.0419 23.1387 91.5913 23.416 89.9273 23.416ZM106.084 23C104.399 23 103.129 22.584 102.276 21.752C101.423 20.92 100.996 19.6187 100.996 17.848V10.04H98.66V6.36H100.996V3L105.316 2.552V6.36H108.836V10.04H105.316V17.624C105.316 18.648 105.764 19.16 106.66 19.16H108.452V23H106.084ZM117.427 23.416C115.933 23.416 114.557 23.032 113.299 22.264C112.04 21.4747 111.037 20.4187 110.291 19.096C109.565 17.7733 109.203 16.3227 109.203 14.744C109.203 13.1653 109.565 11.7147 110.291 10.392C111.037 9.048 112.04 7.98133 113.299 7.192C114.557 6.40267 115.933 6.008 117.427 6.008C118.792 6.008 119.891 6.264 120.723 6.776C121.576 7.288 122.237 8.024 122.707 8.984V6.36H126.995V23H122.803V20.28C122.312 21.2827 121.64 22.0613 120.787 22.616C119.955 23.1493 118.835 23.416 117.427 23.416ZM113.523 14.712C113.523 15.5653 113.715 16.3653 114.099 17.112C114.504 17.8373 115.048 18.424 115.731 18.872C116.435 19.2987 117.235 19.512 118.131 19.512C119.048 19.512 119.859 19.2987 120.563 18.872C121.288 18.4453 121.843 17.8693 122.227 17.144C122.611 16.4187 122.803 15.6187 122.803 14.744C122.803 13.8693 122.611 13.0693 122.227 12.344C121.843 11.5973 121.288 11.0107 120.563 10.584C119.859 10.136 119.048 9.912 118.131 9.912C117.235 9.912 116.435 10.136 115.731 10.584C115.027 11.0107 114.483 11.5867 114.099 12.312C113.715 13.0373 113.523 13.8373 113.523 14.712ZM135.865 23C134.18 23 132.911 22.584 132.057 21.752C131.204 20.92 130.777 19.6187 130.777 17.848V10.04H128.441V6.36H130.777V3L135.097 2.552V6.36H138.617V10.04H135.097V17.624C135.097 18.648 135.545 19.16 136.441 19.16H138.233V23H135.865ZM146.237 23.384C145.043 23.384 143.965 23.1067 143.005 22.552C142.045 21.976 141.288 21.176 140.733 20.152C140.179 19.1067 139.901 17.9013 139.901 16.536V6.36H144.221V15.48C144.221 18.232 145.331 19.608 147.549 19.608C148.531 19.608 149.352 19.2667 150.013 18.584C150.675 17.88 151.005 16.8453 151.005 15.48V6.36H155.325V23H151.133V20.504C150.728 21.4853 150.131 22.2107 149.341 22.68C148.552 23.1493 147.517 23.384 146.237 23.384ZM163.57 23.416C160.775 23.416 158.471 22.4453 156.658 20.504L159.57 17.848C160.786 19.2133 162.087 19.896 163.474 19.896C164.199 19.896 164.764 19.736 165.17 19.416C165.575 19.096 165.778 18.68 165.778 18.168C165.778 17.6987 165.575 17.336 165.17 17.08C164.786 16.8027 163.975 16.5147 162.738 16.216C160.711 15.7253 159.346 15.0533 158.642 14.2C157.959 13.3467 157.618 12.2907 157.618 11.032C157.618 9.53867 158.172 8.32267 159.282 7.384C160.391 6.424 161.906 5.944 163.826 5.944C165.212 5.944 166.375 6.15733 167.314 6.584C168.274 7.01067 169.17 7.75733 170.002 8.824L166.866 11.224C166.119 10.0293 165.138 9.432 163.922 9.432C163.303 9.432 162.802 9.56 162.418 9.816C162.034 10.0507 161.842 10.4133 161.842 10.904C161.842 11.2453 161.98 11.544 162.258 11.8C162.556 12.0347 163.164 12.2693 164.082 12.504C166.364 13.1013 167.922 13.8373 168.754 14.712C169.607 15.5867 170.034 16.696 170.034 18.04C170.034 19.0427 169.746 19.96 169.17 20.792C168.615 21.6027 167.847 22.2427 166.866 22.712C165.884 23.1813 164.786 23.416 163.57 23.416Z\" />\n        </svg>\n      </a>\n      <a href=\"https://midday.ai\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 102 30\"\n          className=\"inline h-8 fill-black md:h-10 dark:fill-white\"\n          fill=\"none\"\n          aria-label=\"Midday.ai\"\n        >\n          <path\n            fill=\"currentColor\"\n            fillRule=\"evenodd\"\n            d=\"M14.347 0a14.931 14.931 0 0 0-6.282 1.68l6.282 10.88V0Zm0 17.443L8.067 28.32a14.933 14.933 0 0 0 6.28 1.68V17.443ZM15.652 30V17.432l6.285 10.887A14.932 14.932 0 0 1 15.652 30Zm0-17.43V0c2.26.097 4.392.693 6.287 1.682l-6.287 10.889ZM2.336 23.068l10.884-6.284-6.284 10.884a15.093 15.093 0 0 1-4.6-4.6Zm25.33-16.132-10.88 6.282 6.282-10.88a15.094 15.094 0 0 1 4.598 4.598ZM2.335 6.934a15.094 15.094 0 0 1 4.6-4.6l6.284 10.884L2.335 6.934Zm-.654 1.13A14.931 14.931 0 0 0 0 14.35h12.568L1.681 8.064Zm0 13.873a14.932 14.932 0 0 1-1.68-6.282h12.562L1.682 21.938Zm15.754-7.587H30a14.93 14.93 0 0 0-1.68-6.285L17.435 14.35Zm10.884 7.586-10.878-6.28H30a14.932 14.932 0 0 1-1.68 6.28Zm-11.533-5.151 6.281 10.88a15.092 15.092 0 0 0 4.598-4.599l-10.88-6.281Z\"\n            clipRule=\"evenodd\"\n          />\n          <path\n            fill=\"currentColor\"\n            d=\"M92.34 11.912h1.637l2.995 8.223 2.884-8.223h1.619l-4 11.107c-.372 1.06-1.08 1.544-2.196 1.544h-1.172v-1.358h1.024c.502 0 .8-.186.986-.707l.353-.912h-.52l-3.61-9.674ZM82.744 14.814c.39-1.916 1.916-3.126 4.018-3.126 2.549 0 3.963 1.489 3.963 4.13v3.964c0 .446.186.632.614.632h.39v1.358h-.65c-1.005 0-1.88-.335-1.861-1.544-.428.93-1.544 1.767-3.107 1.767-1.954 0-3.535-1.041-3.535-2.79 0-2.028 1.544-2.55 3.702-2.977l2.921-.558c-.018-1.712-.818-2.53-2.437-2.53-1.265 0-2.102.65-2.4 1.804l-1.618-.13Zm1.432 4.39c0 .8.689 1.452 2.14 1.433 1.637 0 2.92-1.153 2.92-3.442v-.167l-2.362.41c-1.47.26-2.698.371-2.698 1.767ZM80.129 8.563v13.21h-1.377l-.056-1.452c-.558 1.042-1.618 1.675-3.144 1.675-2.847 0-4.168-2.419-4.168-5.154s1.321-5.153 4.168-5.153c1.451 0 2.493.558 3.051 1.562V8.563h1.526Zm-7.145 8.28c0 1.915.819 3.701 2.884 3.701 2.028 0 2.865-1.823 2.865-3.702 0-1.953-.837-3.758-2.865-3.758-2.065 0-2.884 1.786-2.884 3.758ZM68.936 8.563v13.21H67.56l-.056-1.452c-.558 1.042-1.619 1.675-3.144 1.675-2.847 0-4.168-2.419-4.168-5.154s1.321-5.153 4.168-5.153c1.45 0 2.493.558 3.05 1.562V8.563h1.526Zm-7.144 8.28c0 1.915.819 3.701 2.884 3.701 2.028 0 2.865-1.823 2.865-3.702 0-1.953-.837-3.758-2.865-3.758-2.065 0-2.884 1.786-2.884 3.758ZM56.212 11.912h1.525v9.86h-1.525v-9.86Zm-.037-1.544V8.6h1.6v1.768h-1.6ZM40.224 11.912h1.395l.056 1.674c.446-1.21 1.47-1.898 2.846-1.898 1.414 0 2.438.763 2.865 2.084.428-1.34 1.47-2.084 3.014-2.084 1.973 0 3.126 1.377 3.126 3.74v6.344H52v-5.897c0-1.805-.707-2.828-1.916-2.828-1.544 0-2.437 1.041-2.437 2.846v5.88H46.12v-5.899c0-1.767-.725-2.827-1.916-2.827-1.526 0-2.456 1.079-2.456 2.827v5.898h-1.525v-9.86Z\"\n          />\n        </svg>\n      </a>\n      <a href=\"https://openpanel.dev\" className=\"flex items-center gap-2\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 278 278\"\n          fill=\"none\"\n          className=\"inline h-8 fill-black md:h-10 dark:fill-white\"\n          role=\"presentation\"\n        >\n          <rect width=\"278\" height=\"278\" rx=\"20\" fill=\"#2664EB\" />\n          <path\n            d=\"M148.959 203H128.873C128.291 203 128 202.698 128 202.095L128.349 74.7242C128.349 74.2414 128.582 74 129.048 74H163.456C174.402 74 183.048 77.4702 189.394 84.4105C195.798 91.2905 199 100.675 199 112.564C199 121.255 197.341 128.829 194.022 135.286C190.645 141.684 186.279 146.632 180.923 150.133C175.566 153.633 169.744 155.383 163.456 155.383H149.833V202.095C149.833 202.698 149.542 203 148.959 203ZM163.456 95.9979L149.833 96.179V132.933H163.456C167.241 132.933 170.53 131.062 173.325 127.32C176.119 123.518 177.517 118.599 177.517 112.564C177.517 107.736 176.265 103.783 173.761 100.705C171.258 97.567 167.823 95.9979 163.456 95.9979Z\"\n            fill=\"white\"\n            fillOpacity=\"0.9\"\n          />\n          <path\n            d=\"M114.47 203C108.074 203 102.177 201.36 96.7791 198.079C91.4395 194.798 87.1267 190.434 83.8408 184.986C80.6136 179.479 79 173.445 79 166.884L79.176 109.853C79.176 103.174 80.7896 97.1696 84.0169 91.8386C87.1854 86.4489 91.4688 82.143 96.8671 78.921C102.265 75.6403 108.133 74 114.47 74C121.042 74 126.939 75.611 132.161 78.8331C137.442 82.0552 141.667 86.3903 144.835 91.8386C148.063 97.2282 149.676 103.233 149.676 109.853L149.852 166.884C149.852 173.445 148.268 179.45 145.099 184.898C141.872 190.405 137.589 194.798 132.249 198.079C126.91 201.36 120.983 203 114.47 203ZM114.47 181.295C118.108 181.295 121.277 179.83 123.976 176.901C126.675 173.913 128.025 170.574 128.025 166.884L127.848 109.853C127.848 105.869 126.587 102.501 124.064 99.7473C121.541 96.9939 118.343 95.6172 114.47 95.6172C110.774 95.6172 107.605 96.9646 104.965 99.6594C102.324 102.354 101.004 105.752 101.004 109.853V166.884C101.004 170.809 102.324 174.206 104.965 177.077C107.605 179.889 110.774 181.295 114.47 181.295Z\"\n            fill=\"white\"\n            fillOpacity=\"0.9\"\n          />\n        </svg>\n        <span className=\"text-lg font-medium\">openpanel.dev</span>\n      </a>\n      <a href=\"https://www.liminity.se/\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 703 70\"\n          className=\"inline h-6 fill-black dark:fill-white\"\n          aria-label=\"Liminity\"\n        >\n          <path d=\"m176.3 56.1h27.7v6.2h-34.5v-55.4h6.8z\"></path>\n          <path d=\"m249.6 62.3h-6.8v-55.4h6.8z\"></path>\n          <path d=\"m352.4 62.3h-6.7v-40.9l-20.8 40.9h-4.8l-20.8-40.9v40.9h-6.8v-55.4h6.8l23.2 45.3 23.1-45.3h6.8z\"></path>\n          <path d=\"m402.1 62.3h-6.8v-55.4h6.8z\"></path>\n          <path d=\"m486.8 62.3h-6.4l-28.6-42.6v42.6h-6.8v-55.4h6.3l28.8 42.7v-42.7h6.7z\"></path>\n          <path d=\"m536.5 62.3h-6.8v-55.4h6.8z\"></path>\n          <path d=\"m621.9 13h-20.6v49.3h-6.8v-49.3h-20.5v-6.1h47.9z\"></path>\n          <path d=\"m681.8 40.1v22.2h-6.8v-22.2l-21-33.2h8l16.5 25.8 16.2-25.8h8z\"></path>\n          <path d=\"m94.1 0.7l-59.3 59.3-25-25 25.6-25.6 21.7 21.5 4.5-4.5-26.2-26.1-34.7 34.7 34.1 34.1 59.3-59.3 24.7 24.7-23.5 25.5-23-23-4.6 4.5 27.8 27.7 32.2-34.9z\"></path>\n        </svg>\n      </a>\n      <a href=\"https://databuddy.cc?utm_source=nuqs\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 400 67\"\n          className=\"inline h-7.5 fill-black dark:fill-white\"\n          aria-label=\"Databuddy\"\n        >\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M22.2645 22.2051H33.3966V33.3079H22.2645V22.2051ZM33.3966 33.3079H44.5286V44.4107H33.3966V33.3079Z\"\n          />\n          <path d=\"M66.7929 11.1035H55.6609V55.515H66.7929V11.1035Z\" />\n          <path\n            fillRule=\"evenodd\"\n            clipRule=\"evenodd\"\n            d=\"M0 0H55.6608V11.1028H11.1321V55.5143H55.6608V66.617H0V0Z\"\n          />\n          <path d=\"M90.7033 16.1753C102.674 16.1753 109.348 22.9373 109.348 34.9822C109.348 46.9739 102.78 53.6832 91.0211 53.6832H78.3088V16.1753H90.7033ZM84.9828 47.8193H90.7033C98.6484 47.8193 102.515 43.593 102.515 34.9822C102.515 26.2126 98.6484 22.0392 90.7033 22.0392H84.9828V47.8193Z\" />\n          <path d=\"M113.084 53.6832L126.644 16.1753H134.589L148.149 53.6832H141.263L138.138 44.5969H123.095L119.97 53.6832H113.084ZM125.108 38.8386H136.125L130.616 22.7788L125.108 38.8386Z\" />\n          <path d=\"M176.819 16.1753V22.0392H165.431V53.6832H158.757V22.0392H147.316V16.1753H176.819Z\" />\n          <path d=\"M176.058 53.6832L189.617 16.1753H197.562L211.122 53.6832H204.237L201.111 44.5969H186.068L182.943 53.6832H176.058ZM188.081 38.8386H199.098L193.59 22.7788L188.081 38.8386Z\" />\n          <path d=\"M217.447 53.6832V16.1753H230.847C240.541 16.1753 245.359 19.5035 245.359 26.5825C245.359 30.9672 242.657 33.7142 238.104 34.2425C243.717 34.8237 246.948 37.9933 246.948 43.0647C246.948 49.9324 242.022 53.6832 232.966 53.6832H217.447ZM224.119 47.9249H233.071C237.468 47.9249 240.117 46.1288 240.117 42.6422C240.117 39.1554 237.468 37.2536 233.071 37.2536H224.119V47.9249ZM224.119 31.918H231.217C235.826 31.918 238.528 30.2276 238.528 26.9523C238.528 23.5184 235.985 21.8807 231.217 21.8807H224.119V31.918Z\" />\n          <path d=\"M284.945 40.3178C284.945 49.0343 279.171 54.5286 269.955 54.5286C260.738 54.5286 255.019 49.0343 255.019 40.3178V16.1226H261.692V40.3178C261.692 45.7062 264.711 48.6647 269.955 48.6647C275.251 48.6647 278.27 45.7062 278.27 40.3178V16.1226H284.945V40.3178Z\" />\n          <path d=\"M306.778 16.1753C318.75 16.1753 325.425 22.9373 325.425 34.9822C325.425 46.9739 318.856 53.6832 307.096 53.6832H294.385V16.1753H306.778ZM301.059 47.8193H306.778C314.724 47.8193 318.591 43.593 318.591 34.9822C318.591 26.2126 314.724 22.0392 306.778 22.0392H301.059V47.8193Z\" />\n          <path d=\"M345.405 16.1753C357.375 16.1753 364.049 22.9373 364.049 34.9822C364.049 46.9739 357.481 53.6832 345.723 53.6832H333.009V16.1753H345.405ZM339.684 47.8193H345.405C353.349 47.8193 357.216 43.593 357.216 34.9822C357.216 26.2126 353.349 22.0392 345.405 22.0392H339.684V47.8193Z\" />\n          <path d=\"M367.32 16.1753H374.681L383.686 32.235L392.692 16.1753H400L387.024 38.7858V53.6832H380.349V38.7858L367.32 16.1753Z\" />\n        </svg>\n      </a>\n    </p>\n  )\n}\n\nasync function DependentsLeaderboard() {\n  let dependents: Dependent[] = []\n  try {\n    dependents = await fetchDependents()\n  } catch (error) {\n    console.error(error)\n    return <div className=\"text-red-500\">{String(error)}</div>\n  }\n  return (\n    <div className=\"flex flex-wrap justify-center gap-1.5\">\n      {dependents.map(dep => (\n        <a\n          key={dep.owner + dep.name}\n          href={`https://github.com/${dep.owner}/${dep.name}`}\n          className=\"relative h-8 w-8 rounded-full\"\n        >\n          <img\n            src={upscaleGitHubAvatar(dep.avatarURL, 64)}\n            alt={dep.owner + '/' + dep.name}\n            className=\"rounded-full\"\n            loading=\"lazy\"\n            width={64}\n            height={64}\n          />\n          <span\n            className={cn(\n              'border-background absolute right-0 bottom-0 h-2.5 w-2.5 rounded-full border-2',\n              dep.pkg === 'nuqs' ? 'bg-green-500' : 'bg-zinc-500'\n            )}\n            aria-label={`Using ${dep.pkg}`}\n          />\n        </a>\n      ))}\n    </div>\n  )\n}\n\nfunction upscaleGitHubAvatar(originalURL: string, size: number) {\n  const url = new URL(originalURL)\n  url.searchParams.set('s', size.toFixed())\n  return url.toString()\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/features.tsx",
    "content": "import {\n  BatteryFull,\n  BookCheck,\n  Feather,\n  History,\n  Hourglass,\n  Link,\n  Rainbow,\n  SatelliteDish,\n  Server,\n  Shuffle,\n  Sparkles,\n  TestTube2\n} from 'lucide-react'\nimport React from 'react'\nimport { BundleSize } from './bundle-size'\nimport { GitHubActionsStatus } from './gha-status'\n\nexport function FeaturesSection(props: React.ComponentProps<'section'>) {\n  return (\n    <section\n      className=\"container relative grid grid-cols-1 gap-x-12 gap-y-16 px-4 py-24 md:grid-cols-2 xl:grid-cols-3 xl:gap-y-24\"\n      {...props}\n    >\n      <h2 className=\"sr-only\">Features</h2>\n      <Feature\n        icon={\n          <svg\n            fill=\"none\"\n            width=\"1.2em\"\n            height=\"1.2em\"\n            viewBox=\"0 0 128 128\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <rect fill=\"currentColor\" height=\"128\" rx=\"6\" width=\"128\" />\n            <path\n              clipRule=\"evenodd\"\n              d=\"m74.2622 99.468v14.026c2.2724 1.168 4.9598 2.045 8.0625 2.629 3.1027.585 6.3728.877 9.8105.877 3.3503 0 6.533-.321 9.5478-.964 3.016-.643 5.659-1.702 7.932-3.178 2.272-1.476 4.071-3.404 5.397-5.786 1.325-2.381 1.988-5.325 1.988-8.8313 0-2.5421-.379-4.7701-1.136-6.6841-.758-1.9139-1.85-3.6159-3.278-5.1062-1.427-1.4902-3.139-2.827-5.134-4.0104-1.996-1.1834-4.246-2.3011-6.752-3.353-1.8352-.7597-3.4812-1.4975-4.9378-2.2134-1.4567-.7159-2.6948-1.4464-3.7144-2.1915-1.0197-.7452-1.8063-1.5341-2.3598-2.3669-.5535-.8327-.8303-1.7751-.8303-2.827 0-.9643.2476-1.8336.7429-2.6079s1.1945-1.4391 2.0976-1.9943c.9031-.5551 2.0101-.9861 3.3211-1.2929 1.311-.3069 2.7676-.4603 4.3699-.4603 1.1658 0 2.3958.0877 3.6928.263 1.296.1753 2.6.4456 3.911.8109 1.311.3652 2.585.8254 3.824 1.3806 1.238.5552 2.381 1.198 3.43 1.9285v-13.1051c-2.127-.8182-4.45-1.4245-6.97-1.819s-5.411-.5917-8.6744-.5917c-3.3211 0-6.4674.3579-9.439 1.0738-2.9715.7159-5.5862 1.8336-7.844 3.353-2.2578 1.5195-4.0422 3.4553-5.3531 5.8075-1.311 2.3522-1.9665 5.1646-1.9665 8.4373 0 4.1785 1.2017 7.7433 3.6052 10.6945 2.4035 2.9513 6.0523 5.4496 10.9466 7.495 1.9228.7889 3.7145 1.5633 5.375 2.323 1.6606.7597 3.0954 1.5486 4.3044 2.3668s2.1628 1.7094 2.8618 2.6736c.7.9643 1.049 2.06 1.049 3.2873 0 .9062-.218 1.7462-.655 2.5202s-1.1 1.446-1.9885 2.016c-.8886.57-1.9956 1.016-3.3212 1.337-1.3255.321-2.8768.482-4.6539.482-3.0299 0-6.0305-.533-9.0021-1.6-2.9715-1.066-5.7245-2.666-8.2591-4.799zm-23.5596-34.9136h18.2974v-11.5544h-51v11.5544h18.2079v51.4456h14.4947z\"\n              className=\"fill-background\"\n              fillRule=\"evenodd\"\n            />\n          </svg>\n        }\n        title=\"Type-safe\"\n        description=\"End-to-end type safety between Server and Client components.\"\n      />\n      <Feature\n        icon={<Shuffle size={32} />}\n        title=\"Universal\"\n        description=\"Supports Next.js (app & pages routers), React SPA, Remix, React Router, TanStack Router, and more.\"\n        isNew\n      />\n      <Feature\n        icon={<BookCheck size={32} />}\n        title=\"Simple\"\n        description={\n          <>\n            A familiar <code>React.useState</code>-like API, that syncs with the\n            URL.\n          </>\n        }\n      />\n      <Feature\n        icon={<BatteryFull size={32} />}\n        title=\"Batteries included\"\n        description=\"Built-in parsers & serializers for common state types.\"\n      />\n      <Feature\n        icon={<History size={32} />}\n        title=\"History controls\"\n        description=\"Replace or append to navigation history and use the Back button to navigate state updates.\"\n      />\n      <Feature\n        icon={<Link size={32} />}\n        title=\"Related queries\"\n        description={\n          <>\n            <code>useQueryStates</code> hook to manage multiple keys at once.\n          </>\n        }\n      />\n      <Feature\n        icon={<SatelliteDish size={32} />}\n        title=\"Client-first\"\n        description=\"Shallow updates by default, opt-in to notify the server to re-render RSCs (with throttle control).\"\n      />\n      <Feature\n        icon={<Server size={32} />}\n        title=\"Server cache\"\n        description=\"Type-safe search params access in nested React Server Components. No prop drilling needed.\"\n      />\n      <Feature\n        icon={<Hourglass size={32} />}\n        title=\"Transition\"\n        description=\"Support for useTransition to get loading states on server updates.\"\n      />\n      <Feature\n        icon={<Rainbow size={32} />}\n        title=\"Customizable\"\n        description=\"Make your own parser and serializer.\"\n      />\n      <Feature\n        icon={<Feather size={32} />}\n        title=\"Tiny\"\n        description={\n          <>\n            Only <BundleSize /> gzipped.\n          </>\n        }\n      />\n      <Feature\n        icon={<TestTube2 size={32} />}\n        title={\n          <span className=\"flex items-center\">\n            Tested & testable\n            <GitHubActionsStatus className=\"ml-4 inline-flex\" />\n          </span>\n        }\n        description=\"Tested against every Next.js release. Use the provided test adapter to test your components in isolation.\"\n      />\n    </section>\n  )\n}\n\n// --\n\ntype FeatureProps = {\n  title: React.ReactNode\n  description: React.ReactNode\n  icon: React.ReactNode\n  isNew?: boolean\n}\n\nexport function Feature({ title, description, icon, isNew }: FeatureProps) {\n  // https://v0.dev/t/xXdcvuFkW1d\n  const DescriptionContainer = typeof description === 'string' ? 'p' : 'div'\n  return (\n    <>\n      <div className=\"space-y-4 xl:space-y-8\">\n        <div className=\"flex items-center gap-2\">\n          <span\n            className=\"text-3xl text-zinc-500\"\n            aria-hidden\n            role=\"presentation\"\n          >\n            {icon}\n          </span>\n          <h3 className=\"text-2xl font-bold tracking-tighter dark:text-white md:text-3xl xl:text-4xl\">\n            {title}\n            {isNew && (\n              <Sparkles\n                className=\"ml-2 inline-block -translate-y-3 text-amber-500 dark:text-amber-300\"\n                aria-label=\"New feature\"\n              />\n            )}\n          </h3>\n        </div>\n        <DescriptionContainer className=\"text-zinc-500 dark:text-zinc-300 md:text-lg/relaxed xl:text-xl/relaxed\">\n          {description}\n        </DescriptionContainer>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/gha-status.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport React from 'react'\nimport { z } from 'zod'\n\nexport async function GitHubActionsStatus({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  const statuses = await getGitHubActionsStatus()\n  if (statuses.length === 0) {\n    return null\n  }\n  return (\n    <div\n      className={cn(\n        'flex items-center space-x-[-12px] md:space-x-[-14px]',\n        className\n      )}\n      aria-label=\"Last 5 GitHub Actions status\"\n      {...props}\n    >\n      {statuses.map(status => {\n        const color = {\n          SUCCESS: 'bg-green-500',\n          FAILURE: 'bg-red-500',\n          CANCELLED: 'bg-zinc-500',\n          TIMED_OUT: 'bg-zinc-500',\n          ACTION_REQUIRED: 'bg-purple-500',\n          NEUTRAL: 'bg-zinc-500'\n        }[status.checkSuite.conclusion]\n        return (\n          <a key={status.id} href={status.url} className=\"rounded-full p-1\">\n            <div\n              aria-label={status.checkSuite.conclusion}\n              className={cn(\n                'border-background h-4 w-4 rounded-full border-2 bg-current md:h-5 md:w-5',\n                color\n              )}\n            />\n          </a>\n        )\n      })}\n    </div>\n  )\n}\n\nconst ghaStatusSchema = z.object({\n  id: z.string(),\n  url: z.url(),\n  createdAt: z.iso.datetime(),\n  checkSuite: z.object({\n    status: z.literal('COMPLETED'),\n    conclusion: z.enum([\n      'SUCCESS',\n      'FAILURE',\n      'CANCELLED',\n      'TIMED_OUT',\n      'ACTION_REQUIRED',\n      'NEUTRAL'\n    ])\n  })\n})\n\nasync function getGitHubActionsStatus() {\n  // Fetch a few more than needed to filter out non-completed runs\n  const query = `query {\n    node(id: \"W_kwDOD6wJuM4EeKz5\") {\n      ... on Workflow {\n        runs(first: 8, orderBy: {field: CREATED_AT, direction: DESC}) {\n          nodes {\n            id\n            url\n            createdAt\n            checkSuite {\n              status\n              conclusion\n            }\n          }\n        }\n      }\n    }\n  }`.replace(/\\s+/g, ' ') // Minify\n  let debugInfo: any = undefined\n  try {\n    const json = await fetch(\n      `https://api.github.com/graphql?fn=getGitHubActionsStatus`,\n      {\n        method: 'POST',\n        headers: {\n          Authorization: `bearer ${process.env.GITHUB_TOKEN}`\n        },\n        body: JSON.stringify({ query }),\n        next: {\n          tags: ['github-actions-status']\n        }\n      }\n    ).then(res => res.json())\n    debugInfo = json\n\n    // Filter for completed runs only\n    return z\n      .array(z.unknown())\n      .parse(json.data.node.runs.nodes)\n      .reduce<z.infer<typeof ghaStatusSchema>[]>((runs, run) => {\n        const result = ghaStatusSchema.safeParse(run)\n        if (result.success) {\n          runs.push(result.data)\n        }\n        return runs\n      }, [])\n      .slice(0, 5) // Take only the last 5 completed runs\n      .reverse() // Order from oldest to newest\n  } catch (error) {\n    console.error(error, JSON.stringify(debugInfo))\n    return []\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/hero.tsx",
    "content": "import { NuqsWordmark } from '@/src/components/logo'\nimport { buttonVariants } from '@/src/components/ui/button'\nimport { cn } from '@/src/lib/utils'\nimport { ChevronDown, Github, Library } from 'lucide-react'\nimport Link from 'next/link'\nimport { LandingDemo } from './demo'\nimport { WorksWith } from './works-with'\n\nexport function HeroSection() {\n  return (\n    <section className=\"relative container mb-12 grid grid-cols-1 items-center justify-center gap-8 xl:h-[max(650px,min(800px,calc(75vh)))] xl:grid-cols-2 xl:flex-row\">\n      <aside className=\"my-16 flex flex-col items-center self-center xl:my-24 xl:-mr-10 xl:ml-10 xl:flex-1 xl:items-start\">\n        <h1 className=\"text-6xl md:text-8xl\">\n          <NuqsWordmark />\n        </h1>\n        <p className=\"my-8 text-center text-2xl md:text-4xl xl:text-left\">\n          Type-safe search params\n          <br />\n          state manager for React\n        </p>\n        <nav className=\"flex flex-wrap gap-4\">\n          <Link\n            href=\"/docs\"\n            className={cn(\n              buttonVariants({\n                size: 'lg'\n              }),\n              'text-md w-full rounded-full sm:w-auto'\n            )}\n          >\n            <Library className=\"mr-2 inline-block\" size={20} />\n            Documentation\n          </Link>\n          <a\n            href=\"https://github.com/47ng/nuqs\"\n            className={cn(\n              buttonVariants({\n                size: 'lg',\n                variant: 'secondary'\n              }),\n              'text-md w-full rounded-full sm:w-auto'\n            )}\n          >\n            <Github className=\"mr-2 -ml-1 inline-block\" size={20} />\n            GitHub\n          </a>\n        </nav>\n        <WorksWith className=\"mt-10\" />\n      </aside>\n      <aside className=\"relative my-4 xl:my-auto xl:flex-1 xl:pt-4\">\n        <LandingDemo />\n      </aside>\n      <div\n        className=\"absolute right-0 -bottom-12 left-0 hidden h-12 items-center justify-center xl:flex\"\n        aria-hidden\n      >\n        <ChevronDown />\n      </div>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/page-footer.tsx",
    "content": "import { NuqsWordmark } from '@/src/components/logo'\nimport {\n  SiBluesky,\n  SiGithub,\n  SiTwitch,\n  SiX,\n  SiYoutube\n} from '@icons-pack/react-simple-icons'\nimport Link from 'next/link'\n\nexport function PageFooter() {\n  return (\n    <footer className=\"mt-auto w-full border-t bg-zinc-50/50 py-12 dark:bg-zinc-900/20\">\n      <nav className=\"container px-4 md:px-6\">\n        <div className=\"grid gap-12 lg:grid-cols-3\">\n          <div className=\"space-y-4\">\n            <NuqsWordmark className=\"text-4xl\" />\n            <p className=\"text-sm text-zinc-500\">\n              Made by{' '}\n              <a\n                href=\"https://francoisbest.com\"\n                className=\"font-semibold hover:underline\"\n              >\n                François Best\n              </a>{' '}\n              and{' '}\n              <a\n                href=\"https://github.com/47ng/nuqs/graphs/contributors\"\n                className=\"font-semibold hover:underline\"\n              >\n                contributors\n              </a>\n              <br />\n              <a\n                href=\"https://github.com/47ng/nuqs/blob/next/LICENSE\"\n                className=\"hover:underline\"\n              >\n                MIT License\n              </a>{' '}\n              © 2020\n            </p>\n          </div>\n          <div className=\"space-y-3\">\n            <h3 className=\"text-xl font-semibold\">Quick Links</h3>\n            <ul className=\"space-y-2\">\n              <li>\n                <Link href=\"/docs\" className=\"hover:underline\" prefetch={false}>\n                  Documentation\n                </Link>\n              </li>\n              <li>\n                <Link href=\"/blog\" className=\"hover:underline\" prefetch={false}>\n                  Blog\n                </Link>\n              </li>\n              <li>\n                <Link\n                  href=\"/registry\"\n                  className=\"hover:underline\"\n                  prefetch={false}\n                >\n                  Shadcn Registry\n                </Link>\n              </li>\n              <li>\n                <Link\n                  href=\"/playground\"\n                  className=\"hover:underline\"\n                  prefetch={false}\n                >\n                  Playground\n                </Link>\n              </li>\n              <li>\n                <Link\n                  href=\"/stats\"\n                  className=\"hover:underline\"\n                  prefetch={false}\n                >\n                  Project Stats\n                </Link>\n              </li>\n            </ul>\n          </div>\n          <div className=\"space-y-3\">\n            <h3 className=\"text-xl font-semibold\">Social</h3>\n            <ul className=\"space-y-1\">\n              <li>\n                <a\n                  href=\"https://github.com/47ng/nuqs\"\n                  className=\"inline-flex items-center gap-1 hover:underline\"\n                >\n                  <SiGithub role=\"presentation\" className=\"mr-2 size-5\" />\n                  <span>GitHub</span>\n                </a>\n              </li>\n              <li>\n                <a\n                  href=\"https://x.com/nuqs47ng\"\n                  className=\"inline-flex items-center gap-1 hover:underline\"\n                >\n                  <SiX role=\"presentation\" className=\"mr-2 size-5\" />\n                  <span>X / Twitter</span>\n                </a>\n              </li>\n              <li>\n                <a\n                  href=\"https://bsky.app/profile/nuqs.dev\"\n                  className=\"inline-flex items-center gap-1 hover:underline\"\n                >\n                  <SiBluesky role=\"presentation\" className=\"mr-2 size-5\" />\n                  <span>Bluesky</span>\n                </a>\n              </li>\n              <li>\n                <a\n                  href=\"https://www.youtube.com/@47ng-dev\"\n                  className=\"inline-flex items-center gap-1 hover:underline\"\n                >\n                  <SiYoutube role=\"presentation\" className=\"mr-2 size-5\" />\n                  <span>YouTube</span>\n                </a>\n              </li>\n              <li>\n                <a\n                  href=\"https://www.twitch.tv/francoisbest\"\n                  className=\"inline-flex items-center gap-1 hover:underline\"\n                >\n                  <SiTwitch role=\"presentation\" className=\"mr-2 size-5\" />\n                  <span>Twitch</span>\n                </a>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </nav>\n    </footer>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/quotes/quotes-section.tsx",
    "content": "import { Quote } from '@/src/components/quote'\n\nexport function QuotesSection() {\n  return (\n    <section className=\"container my-24 grid grid-cols-1 gap-8 md:grid-cols-2 xl:grid-cols-3\">\n      <Quote\n        text={\n          <>\n            <p>\n              I started building my own hook helpers for handling URL states and\n              had to deal with some performance drawbacks.\n            </p>\n            <p>\n              Eventually stumbled on this little beauty and it has everything I\n              wanted and more.\n            </p>\n          </>\n        }\n        author={{\n          name: 'N8',\n          avatar: { service: 'x', handle: 'nathanbrachotte' }\n          // // 'https://pbs.twimg.com/profile_images/1589918605977722882/Iu7GZSZ9_400x400.jpg'\n        }}\n        url=\"https://x.com/nathanbrachotte/status/1747244520163397659\"\n      />\n      <Quote\n        author={{\n          name: 'Marc Seitz',\n          avatar: { service: 'x', handle: 'mfts0' }\n          // // 'https://pbs.twimg.com/profile_images/1176854646343852032/iYnUXJ-m_400x400.jpg'\n        }}\n        text=\"It’s a database ORM for your URL\"\n        url=\"https://x.com/mfts0/status/1814577051703066783\"\n      />\n      <Quote\n        author={{\n          name: 'Pontus Abrahamsson',\n          avatar: { service: 'x', handle: 'pontusab' }\n          // 'https://pbs.twimg.com/profile_images/1755611130368770048/JwLEqyeo_400x400.jpg'\n        }}\n        text={<>We use nuqs pretty much everywhere 🖤</>}\n        url=\"https://x.com/pontusab/status/1774434057469780028\"\n      />\n      <Quote\n        text=\"nuqs is fkn dope\"\n        author={{\n          name: 'kitze',\n          avatar: { service: 'x', handle: 'thekitze' }\n          // 'https://pbs.twimg.com/profile_images/1975336245992607744/ug-G1qXh_400x400.jpg'\n        }}\n        url=\"https://x.com/thekitze/status/1909576710179471466\"\n      />\n      <Quote\n        author={{\n          name: 'Darathor17',\n          avatar: { service: 'github', handle: 'Darathor17' } // 'https://avatars.githubusercontent.com/u/24258247?v=4'}\n        }}\n        text=\"thx a lot for this awesome library! I was syncing with URL like nextjs recommends .. performance are awful and I was about to move back to useState.\"\n        url=\"https://github.com/47ng/nuqs/discussions/444\"\n      />\n      <Quote\n        author={{\n          name: 'ahmet',\n          avatar: { service: 'x', handle: 'bruvimtired' }\n          // 'https://pbs.twimg.com/profile_images/1991136440135106560/KtTAASs0_400x400.jpg'\n        }}\n        text=\"nuqs has to be one of the best libraries out there. lifting state to the url is so easy now and far better for ux.\"\n        url=\"https://x.com/bruvimtired/status/1944437102562759144\"\n      />\n      <Quote\n        author={{\n          name: 'Aryan',\n          avatar: { service: 'x', handle: 'AryaAmour08' }\n          // 'https://pbs.twimg.com/profile_images/1880190860853444608/Ah13Xi4S_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>\n              Just used the nuqs library for the first time today… and wow -\n              syncing URL query params in Next.js has never felt this elegant.\n            </p>\n            <p>\n              Parsing, defaults, clearOnDefault — it's pretty wild\n              <br />\n              Where has this been all my life?\n            </p>\n          </>\n        }\n        url=\"https://x.com/AryaAmour08/status/1946565369537446127\"\n      />\n      <Quote\n        author={{\n          name: 'Ru Chern Chong',\n          avatar: {\n            service: 'http',\n            href: 'https://pbs.twimg.com/profile_images/1864759220048138240/cX7JIFtb_400x400.jpg'\n          }\n        }}\n        text=\"Sometimes, there is no need to complicate managing state. nuqs is really powerful.\"\n        url=\"https://x.com/ruchernchong/status/1946618077581619392\"\n      />\n      <Quote\n        author={{\n          name: 'Bharat Kara',\n          avatar: { service: 'x', handle: 'KaraBharat' }\n          // 'https://pbs.twimg.com/profile_images/1947143666616717312/r3BIu8OZ_400x400.jpg'\n        }}\n        text=\"nuqs simplifies your URL logic like magic. Seriously.\"\n        url=\"https://x.com/KaraBharat/status/1947984051840983521\"\n      />\n      <Quote\n        author={{\n          name: 'Ido Evergreen',\n          avatar: { service: 'x', handle: 'IdoEvergreen' }\n          // 'https://pbs.twimg.com/profile_images/1991962658023612416/AAao07da_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>\n              It made me realize URL should be part of the design convo not just\n              a place to dump state.\n            </p>\n            <p>\n              Since I started using nuqs more heavily in production, the way I\n              see and treat URL completely changed.\n            </p>\n          </>\n        }\n        url=\"https://x.com/IdoEvergreen/status/1948014681207030054\"\n      />\n      <Quote\n        author={{\n          name: 'Rhys Sullivan',\n          avatar: { service: 'x', handle: 'RhysSullivan' }\n          // 'https://pbs.twimg.com/profile_images/1303727365265203200/0cgHOP3y_400x400.jpg'\n        }}\n        text=\"The goat library\"\n        url=\"https://x.com/RhysSullivan/status/1971327979369398579\"\n      />\n      <Quote\n        author={{\n          name: 'Virgile Rietsch',\n          avatar: { service: 'x', handle: 'virgilerietsch' }\n          // 'https://pbs.twimg.com/profile_images/1967572887629398016/CGM5YVvp_400x400.jpg'\n        }}\n        text=\"best library ever\"\n        url=\"https://x.com/virgilerietsch/status/1971320475180823012\"\n      />\n      <Quote\n        author={{\n          name: 'Dominik Koch',\n          avatar: { service: 'x', handle: 'dominikkoch' }\n          // 'https://pbs.twimg.com/profile_images/1933961142457581568/i2Y0u0lV_400x400.jpg'\n        }}\n        text=\"i love nuqs\"\n        url=\"https://x.com/DominikDoesDev/status/1973696846079135968\"\n      />\n      <Quote\n        author={{\n          name: 'Josh tried coding',\n          avatar: { service: 'x', handle: 'joshtriedcoding' }\n          // 'https://pbs.twimg.com/profile_images/1899476552464646146/Vooiz1-9_400x400.jpg'\n        }}\n        text=\"nuqs is one of my favorite pieces of software recently. don't mean to glaze but man, it's a type-safe, way better version of react state\"\n        url=\"https://x.com/joshtriedcoding/status/1981561126254235959\"\n      />\n      <Quote\n        author={{\n          name: 'Pavel Svitek',\n          avatar: { service: 'x', handle: 'pavelsvitek_' }\n          // 'https://pbs.twimg.com/profile_images/1632440743804428290/EUnmpz5l_400x400.jpg'\n        }}\n        text=\"Just did first custom parser today, pretty simple .. great API design 👌\"\n        url=\"https://x.com/pavelsvitek_/status/1976329834981925328\"\n      />\n\n      <Quote\n        author={{\n          name: 'OrcDev',\n          avatar: { service: 'x', handle: 'orcdev' }\n          // 'https://pbs.twimg.com/profile_images/1756766826736893952/6Gvg6jha_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>Keep it up guys! You're doing a great job!</p>\n            <p>\n              I'm using nuqs on real projects in production already, it's\n              amazing how easy it is to control URL params.\n            </p>\n          </>\n        }\n        url=\"https://x.com/orcdev/status/1849368178717290945\"\n      />\n      <Quote\n        author={{\n          name: 'Brandon McConnell',\n          avatar: { service: 'x', handle: 'branmcconnell' }\n          // 'https://pbs.twimg.com/profile_images/1980738441986859008/Cby9Dgd2_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>\n              nuqs is the perfect solution to the epidemically annoying problem\n              of query param state management\n            </p>\n\n            <p>I wish I had this 10 years ago</p>\n          </>\n        }\n        url=\"https://x.com/branmcconnell/status/1976717520653459678\"\n      />\n      <Quote\n        author={{\n          name: 'Iza',\n          avatar: { service: 'x', handle: 'izadoesdev' }\n          // 'https://pbs.twimg.com/profile_images/1995822557115637760/HEiF8JFH_400x400.jpg'\n        }}\n        text=\"goated library\"\n        url=\"https://x.com/izadoesdev/status/1977276660090388523\"\n      />\n      <Quote\n        author={{\n          name: 'Code With Antonio',\n          avatar: { service: 'x', handle: 'codewithantonio' }\n          // 'https://pbs.twimg.com/profile_images/1677359164580929544/jngFF04Y_400x400.jpg'\n        }}\n        text=\"i am so happy every time i get to use nuqs!\"\n        url=\"https://x.com/YTCodeAntonio/status/1978044756157481313\"\n      />\n      <Quote\n        author={{\n          name: 'arth',\n          avatar: { service: 'x', handle: 'arthty' }\n          // 'https://pbs.twimg.com/profile_images/1997561168064544768/IdismN0M_400x400.jpg'\n        }}\n        text=\"also, nuqs is amazing\"\n        url=\"https://x.com/arthty/status/1980237430256234768\"\n      />\n      <Quote\n        author={{\n          name: 'Mr T.',\n          avatar: { service: 'x', handle: 'DorianTho5' }\n          // 'https://pbs.twimg.com/profile_images/2000156881772216324/h9lf-PZ6_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>one of the most useful pieces of library ever made</p>\n            <p>\n              thx for making our life easier as devs and ux way better for our\n              users\n            </p>\n          </>\n        }\n        url=\"https://x.com/DorianTho5/status/1981419755723739213\"\n      />\n      <Quote\n        author={{\n          name: 'Hayden Bleasel',\n          avatar: { service: 'x', handle: 'haydenbleasel' }\n          // 'https://pbs.twimg.com/profile_images/1964093609801895936/B3_Cmkma_400x400.jpg'\n        }}\n        text=\"deez nuqs\"\n        url=\"https://x.com/haydenbleasel/status/1981572915591258189\"\n      />\n      <Quote\n        author={{\n          name: 'Christopher Burns',\n          avatar: { service: 'x', handle: 'BurnedChris' }\n          // 'https://pbs.twimg.com/profile_images/1988259821020221440/qRyYd6iE_400x400.jpg'\n        }}\n        text=\"Really glad to finally met Francois from nuqs! I love using it!\"\n        url=\"https://x.com/BurnedChris/status/1981071113154417018\"\n      />\n      <Quote\n        author={{\n          name: 'Chánh Đại',\n          avatar: { service: 'x', handle: 'iamncdai' }\n          // 'https://pbs.twimg.com/profile_images/1905665979662958595/Y0_Ifuk5_400x400.jpg'\n        }}\n        text=\"I integrated nuqs into an internal project, and it helped simplify filter-related query logic and reduce development time.\"\n        url=\"https://x.com/iamncdai/status/1981606278444249232\"\n      />\n      <Quote\n        author={{\n          name: 'Abhishek Chauhan',\n          avatar: { service: 'x', handle: 'abhishekashwinc' }\n          // 'https://pbs.twimg.com/profile_images/1999736604102123520/p4VZVIcH_400x400.jpg'\n        }}\n        text=\"Big fan of nuqs!\"\n        url=\"https://x.com/abhishekashwinc/status/1981613111472996827\"\n      />\n      <Quote\n        author={{\n          name: 'Michael',\n          avatar: { service: 'x', handle: 'michael_chomsky' }\n          // 'https://pbs.twimg.com/profile_images/1995587948914638854/iGEDdcOq_400x400.jpg'\n        }}\n        text=\"meeting the guy behind nuqs was insane. it's one of those tools literally every web dev should know about\"\n        url=\"https://x.com/michael_chomsky/status/1981633873529471064\"\n      />\n      <Quote\n        author={{\n          name: 'James Perkins',\n          avatar: { service: 'x', handle: 'jamesperkins' }\n          // 'https://pbs.twimg.com/profile_images/1854308174817579008/Lon8OO2h_400x400.jpg'\n        }}\n        text=\"nuqs is so goated\"\n        url=\"https://x.com/jamesperkins/status/1981744427132424690\"\n      />\n      <Quote\n        author={{\n          name: 'dmytro',\n          avatar: { service: 'x', handle: 'pqoqubbw' }\n          // 'https://pbs.twimg.com/profile_images/2002890026905169920/T6FpaVHH_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>thank you for your hard work 🫶</p>\n            <p>nuqs is awesome</p>\n          </>\n        }\n        url=\"https://x.com/pqoqubbw/status/1981753810654494892\"\n      />\n      <Quote\n        author={{\n          name: 'Suraj Jha',\n          avatar: { service: 'x', handle: 'surajtwt_' }\n          // 'https://pbs.twimg.com/profile_images/1984373283136106496/OkLU-izR_400x400.jpg'\n        }}\n        text=\"Currently Using nuqs to add some pagination and searching feature into my nextjs app and i am loving it\"\n        url=\"https://x.com/surajtwt_/status/1982684604307034362\"\n      />\n      <Quote\n        author={{\n          name: 'shibbi',\n          avatar: { service: 'x', handle: 'shibbicodes' }\n          // 'https://pbs.twimg.com/profile_images/1911788743721164800/TBqun0ZP_400x400.jpg'\n        }}\n        text=\"We started using nuqs and achieved world peace internally.\"\n        url=\"https://x.com/shibbicodes/status/2002396495832817803\"\n      />\n      <Quote\n        author={{\n          name: 'Ajay Patel',\n          avatar: { service: 'x', handle: 'ajaypatel_aj' }\n          // 'https://pbs.twimg.com/profile_images/1957717329397141507/7ctDgOuc_400x400.jpg'\n        }}\n        text={\n          <>\n            <p>\n              Big thanks to nuqs for making URL state management actually\n              enjoyable! 🙌\n            </p>\n            <br />\n            <ul>\n              <li>useState but synced with the URL? ✅</li>\n              <li>Type-safe? ✅</li>\n              <li>Works everywhere (Next.js, Remix, React Router)? ✅</li>\n              <li>Only 6kb? ✅</li>\n            </ul>\n            <br />\n            <p>Happy to support such a well-crafted library 😇</p>\n          </>\n        }\n        url=\"https://x.com/ajaypatel_aj/status/2004082719047778362\"\n      />\n      <Quote\n        author={{\n          name: 'anarki supreme',\n          avatar: { service: 'x', handle: 'basedanarki' }\n          // 'https://pbs.twimg.com/profile_images/1994299964730781696/lh8gQd0V_400x400.jpg'\n        }}\n        text=\"I LOVE NUQS I LOVE PARAMS THAT JUST WORK AHAHA WOOHOOO❤️❤️❤️❤️\"\n        url=\"https://x.com/basedanarki/status/2001970260426318003\"\n      />\n      <Quote\n        author={{\n          name: 'Matt',\n          avatar: { service: 'x', handle: 'uixmat' }\n        }}\n        text=\"this is a huge time saver 😍 nuqs is literally the first thing i add to a project after some basic ui like tabs & toggles\"\n        url=\"https://x.com/uixmat/status/1987809486329860602\"\n      />\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/sponsors.tsx",
    "content": "import { Button } from '@/src/components/ui/button'\nimport { cn } from '@/src/lib/utils'\nimport { Heart } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { z } from 'zod'\n\nconst sponsorSchema = z.object({\n  name: z.string().nullish(),\n  handle: z.string(),\n  url: z.string().url(),\n  img: z.string(),\n  title: z.custom<ReactNode>().optional()\n})\ntype Sponsors = z.infer<typeof sponsorSchema>[]\n\nconst SPONSORS: Sponsors = [\n  {\n    handle: 'vercel',\n    name: 'Vercel',\n    url: 'https://vercel.com/',\n    img: 'https://avatars.githubusercontent.com/u/14985020?s=200&v=4'\n  },\n  {\n    handle: 'getsentry',\n    name: 'Sentry',\n    url: 'https://sentry.io/?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs',\n    img: '/sponsors/sentry.svg'\n  },\n  {\n    handle: 'syntaxfm',\n    name: 'Syntax.fm',\n    url: 'https://syntax.fm/?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs',\n    img: 'https://avatars.githubusercontent.com/u/130389858?s=200&v=4'\n  },\n  {\n    handle: '1771-Technologies',\n    name: '1771 Technologies',\n    url: 'https://1771technologies.com/?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs',\n    img: 'https://avatars.githubusercontent.com/u/148620833?s=200&v=4'\n  },\n  {\n    handle: 'unkey.com',\n    name: 'Unkey',\n    url: 'https://unkey.com',\n    img: 'https://avatars.githubusercontent.com/u/138932600?s=200&v=4'\n  },\n  {\n    handle: 'openstatus.dev',\n    name: 'OpenStatus',\n    url: 'https://openstatus.dev',\n    img: 'https://avatars.githubusercontent.com/u/136892265?s=200&v=4'\n  },\n  {\n    handle: 'databuddy.cc',\n    name: 'Databuddy',\n    url: 'https://databuddy.cc?utm_source=nuqs',\n    img: 'https://avatars.githubusercontent.com/u/190393139?v=4'\n  },\n  {\n    handle: 'code-store-platform',\n    name: 'code.store',\n    url: 'https://code.store',\n    img: 'https://avatars.githubusercontent.com/u/57156815?s=200&v=4'\n  },\n  {\n    handle: 'pqoqubbw',\n    name: 'dmytro',\n    url: 'https://pqoqubbw.dev/',\n    img: 'https://avatars.githubusercontent.com/u/71014515?s=200&v=4'\n  },\n  {\n    handle: 'ryanmagoon',\n    name: 'Ryan Magoon',\n    url: 'https://x.com/Ryan_Magoon',\n    img: 'https://avatars.githubusercontent.com/u/5327290?s=200&v=4'\n  },\n  {\n    handle: 'pontusab',\n    name: 'Pontus Abrahamsson',\n    url: 'https://x.com/pontusab',\n    img: 'https://avatars.githubusercontent.com/u/655158?s=200&v=4',\n    title: (\n      <>\n        Founder of{' '}\n        <a href=\"https://midday.ai\" className=\"hover:underline\">\n          Midday.ai\n        </a>\n      </>\n    )\n  },\n  {\n    handle: 'CarlLindesvard',\n    name: 'Carl Lindesvärd',\n    url: 'https://x.com/CarlLindesvard',\n    img: 'https://pbs.twimg.com/profile_images/1751607056316944384/8E4F88FL_400x400.jpg',\n    title: (\n      <>\n        Founder of{' '}\n        <a href=\"https://openpanel.dev\" className=\"hover:underline\">\n          OpenPanel\n        </a>\n      </>\n    )\n  },\n  {\n    handle: 'rwieruch',\n    name: 'Robin Wieruch',\n    url: 'https://www.robinwieruch.de/',\n    img: 'https://avatars.githubusercontent.com/u/2479967?s=200&v=4',\n    title: (\n      <>\n        Author of{' '}\n        <a href=\"https://www.road-to-next.com/\" className=\"hover:underline\">\n          The Road to Next\n        </a>\n      </>\n    )\n  },\n  {\n    handle: 'aurorascharff',\n    name: 'Aurora Scharff',\n    url: 'https://aurorascharff.no/',\n    img: 'https://avatars.githubusercontent.com/u/66901228?s=200&v=4',\n    title: 'Queen of RSCs 👸'\n  },\n  {\n    handle: 'YoannFleuryDev',\n    name: 'Yoann Fleury',\n    url: 'https://www.yoannfleury.dev/',\n    img: 'https://pbs.twimg.com/profile_images/1594632934245498880/CJTKNRCO_400x400.jpg',\n    title: 'Front end developer'\n  },\n  {\n    handle: 'dominikkoch',\n    name: 'Dominik Koch',\n    url: 'https://dominikkoch.dev',\n    img: 'https://avatars.githubusercontent.com/u/68947960?s=200&v=4',\n    title: (\n      <>\n        Founder of{' '}\n        <a href=\"https://usenotra.com\" className=\"hover:underline\">\n          Notra\n        </a>\n      </>\n    )\n  },\n  {\n    handle: 'lpbonomi',\n    name: 'Luis Pedro Bonomi',\n    url: 'https://github.com/lpbonomi',\n    img: 'https://avatars.githubusercontent.com/u/38361000?s=200&v=4'\n  },\n  {\n    handle: 'RhysSullivan',\n    name: 'Rhys Sullivan',\n    url: 'https://rhys.dev',\n    img: 'https://avatars.githubusercontent.com/u/39114868?s=200&v=4'\n  },\n  {\n    handle: 'brandonmcconnell',\n    name: 'Brandon McConnell',\n    url: 'https://github.com/brandonmcconnell',\n    img: 'https://avatars.githubusercontent.com/u/5913254?s=200&v=4'\n  },\n  {\n    handle: 'ruchernchong',\n    name: 'Ru Chern Chong',\n    url: 'https://github.com/ruchernchong',\n    img: 'https://avatars.githubusercontent.com/u/10343662?s=200&v=4'\n  },\n  {\n    handle: 'DavidHDev',\n    name: 'David Haz',\n    url: 'https://github.com/DavidHDev',\n    img: 'https://avatars.githubusercontent.com/u/48634587?s=200&v=4'\n  },\n  {\n    handle: 'basedanarki',\n    name: 'anarki',\n    url: 'https://github.com/basedanarki',\n    img: 'https://avatars.githubusercontent.com/u/161698650?s=200&v=4'\n  },\n  {\n    handle: 'haydenbleasel',\n    name: 'Hayden Bleasel',\n    url: 'https://www.haydenbleasel.com/',\n    img: 'https://avatars.githubusercontent.com/u/4142719?s=200&v=4'\n  }\n]\n\nexport function SponsorsSection() {\n  return (\n    <section className=\"mb-24\">\n      <h2 className=\"mb-12 text-center text-3xl font-bold tracking-tighter md:text-4xl xl:text-5xl dark:text-white\">\n        Sponsors\n      </h2>\n      <div className=\"mb-12 flex flex-wrap items-center justify-center gap-8\">\n        <a\n          href=\"https://nextjsweekly.com?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"block p-2\"\n        >\n          <span\n            role=\"presentation\"\n            className=\"bg-foreground mx-auto block h-[25.5px] w-[270px] [mask-image:url('https://nextjsweekly.com/logo.svg')] [mask-size:100%] [mask-position:center] [mask-repeat:no-repeat]\"\n          />\n          <span className=\"sr-only\">Next.js Weekly</span>\n        </a>\n        <a\n          href=\"https://shadcnstudio.com/?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"flex items-center gap-3 p-2\"\n        >\n          <svg\n            viewBox=\"0 0 328 329\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"size-8\"\n            aria-hidden\n          >\n            <rect\n              y=\"0.5\"\n              width=\"328\"\n              height=\"328\"\n              rx=\"164\"\n              fill=\"currentColor\"\n            />\n            <path\n              d=\"M165.018 72.3008V132.771C165.018 152.653 148.9 168.771 129.018 168.771H70.2288\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <path\n              d=\"M166.627 265.241L166.627 204.771C166.627 184.889 182.744 168.771 202.627 168.771L261.416 168.771\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"238.136\"\n              y1=\"98.8184\"\n              x2=\"196.76\"\n              y2=\"139.707\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"135.688\"\n              y1=\"200.957\"\n              x2=\"94.3128\"\n              y2=\"241.845\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"133.689\"\n              y1=\"137.524\"\n              x2=\"92.5566\"\n              y2=\"96.3914\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"237.679\"\n              y1=\"241.803\"\n              x2=\"196.547\"\n              y2=\"200.671\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n          </svg>\n          <span className=\"mb-px text-3xl font-semibold\">shadcn/studio</span>\n        </a>\n      </div>\n      <ul className=\"container flex flex-wrap justify-center gap-x-4 gap-y-12 md:gap-x-6 lg:gap-x-8\">\n        {SPONSORS.map(sponsor => (\n          <li\n            key={sponsor.handle}\n            className=\"flex w-1/2 flex-col items-center md:w-1/3 lg:w-1/6\"\n          >\n            <a\n              href={sponsor.url}\n              className=\"flex h-32 w-32 items-center justify-center rounded-full\"\n            >\n              <img\n                src={sponsor.img}\n                alt={sponsor.name ?? sponsor.handle}\n                className=\"size-32 rounded-full\"\n                width={128}\n                height={128}\n              />\n            </a>\n            <a\n              href={sponsor.url}\n              className=\"mt-2 inline-block font-semibold hover:underline\"\n            >\n              {sponsor.name ?? sponsor.handle}\n            </a>\n            {Boolean(sponsor.title) && (\n              <span className=\"mt-1 inline-block text-sm text-zinc-500\">\n                {sponsor.title}\n              </span>\n            )}\n          </li>\n        ))}\n      </ul>\n      <div className=\"mt-16 flex justify-center\">\n        <Button className=\"text-md mx-auto font-semibold\" asChild size=\"lg\">\n          <a href=\"https://github.com/sponsors/franky47\">\n            <Heart className=\"mr-2 stroke-pink-500\" size={18} /> Sponsor my work\n          </a>\n        </Button>\n      </div>\n    </section>\n  )\n}\n\n// --\n\nexport function InlineSponsorsList({\n  className,\n  ...props\n}: ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'flex flex-wrap items-center justify-center gap-2',\n        // 'container grid grid-cols-2 gap-y-12 md:grid-cols-3 lg:grid-cols-6',\n        className\n      )}\n      {...props}\n    >\n      {SPONSORS.map(sponsor => (\n        <InlineSponsor key={sponsor.handle} {...sponsor} />\n      ))}\n      <InlineSponsor\n        handle=\"ajaypatelaj\"\n        name=\"Ajay Patel\"\n        url=\"https://shadcnstudio.com/?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs\"\n        img=\"https://avatars.githubusercontent.com/u/749684?s=200&v=4\"\n      />\n    </ul>\n  )\n}\n\nfunction InlineSponsor({ url, img, handle, name }: Sponsors[number]) {\n  return (\n    <li className=\"flex flex-col items-center\">\n      <a\n        href={url}\n        className=\"size-12 rounded-full transition-transform hover:scale-125\"\n      >\n        <img\n          src={img}\n          alt={name ?? handle}\n          className=\"mx-auto size-12 rounded-full\"\n          title={name ?? handle}\n          width={48}\n          height={48}\n        />\n      </a>\n    </li>\n  )\n}\n\n// --\n\nexport function AsideSponsors() {\n  return (\n    <aside className=\"mt-8\">\n      <a\n        href=\"https://github.com/sponsors/franky47\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className=\"text-muted-foreground group mb-2 inline-flex items-center gap-2 text-xs\"\n      >\n        <Heart\n          className=\"size-4 fill-transparent stroke-current\"\n          aria-label=\"Sponsor my work on GitHub to add your company here\"\n        />\n        <h3 className=\"group-hover:underline group-active:underline\">\n          Sponsored by\n        </h3>\n      </a>\n      <ul className=\"space-y-2\">\n        <li>\n          <NextJSWeeklyAsideSponsor />\n        </li>\n        <li>\n          <ShadcnStudioAsideSponsor />\n        </li>\n      </ul>\n    </aside>\n  )\n}\n\nexport function NextJSWeeklyAsideSponsor() {\n  return (\n    <a\n      href=\"https://nextjsweekly.com?utm_source=nuqs&utm_medium=sponsor&utm_campaign=nuqs\"\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      className=\"group\"\n    >\n      <section className=\"text-muted-foreground space-y-4 rounded-md border border-dashed px-4 py-6 text-center transition-colors group-hover:text-current group-active:text-current\">\n        <svg\n          viewBox=\"0 0 200 19\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          className=\"mx-auto w-3/4\"\n        >\n          <title>Next.js Weekly</title>\n          <g\n            transform=\"translate(-20.000000, -20.000000)\"\n            fill=\"currentColor\"\n            fillRule=\"nonzero\"\n            stroke=\"none\"\n            strokeWidth=\"1\"\n          >\n            <path d=\"M23.3514572,20 L31.1718922,31.5115414 L31.3273949,31.5115414 L31.3273949,20 L35.1385934,20 L35.1385934,38.3038438 L31.8295335,38.3038438 L23.9595199,26.7851804 L23.8252848,26.7851804 L23.8252848,38.3038438 L20,38.3038438 L20,20 L23.3514572,20 Z M51.3188892,20 L51.3188892,23.1888449 L42.9471514,23.1888449 L42.9471514,27.5503076 L50.6896969,27.5503076 L50.6896969,30.7391525 L42.9471514,30.7391525 L42.9471514,35.1149989 L51.3542433,35.1149989 L51.3542433,38.3038438 L39.1218665,38.3038438 L39.1218665,20 L51.3188892,20 Z M58.7808891,20 L62.4295415,26.2419526 L62.5709579,26.2419526 L66.24074,20 L70.5609821,20 L65.0385628,29.1519219 L70.6881739,38.3038438 L66.2831373,38.3038438 L62.5709579,32.0547692 L62.4295415,32.0547692 L58.7173622,38.3038438 L54.3334551,38.3038438 L59.9971526,29.1519219 L54.4395174,20 L58.7808891,20 Z M87.9857056,20 L87.9857056,23.1888449 L82.4421568,23.1888449 L82.4421568,38.3038438 L78.6594073,38.3038438 L78.6594073,23.1888449 L73.1158585,23.1888449 L73.1158585,20 L87.9857056,20 Z M101.536026,30.035155 C102.111007,30.035155 102.613126,30.1372959 103.042381,30.3415778 C103.471637,30.5458596 103.806435,30.8301539 104.046776,31.1944607 C104.287117,31.5587674 104.410577,31.9800271 104.417158,32.4582398 L104.417158,32.4582398 L102.846432,32.4582398 L102.827355,32.3269509 C102.776986,32.0729665 102.653333,31.8705437 102.456396,31.7196823 C102.226636,31.5436775 101.915807,31.4556751 101.523908,31.4556751 C101.256574,31.4556751 101.030469,31.494827 100.845593,31.573131 C100.660717,31.6514349 100.520105,31.7584251 100.423756,31.8941015 C100.327407,32.0297779 100.278484,32.184309 100.276986,32.3576949 C100.273952,32.5017398 100.304088,32.6275847 100.367395,32.7352296 C100.430701,32.8428745 100.519356,32.9353272 100.633359,33.0125876 C100.747363,33.0898481 100.878764,33.1562335 101.027563,33.211744 C101.176362,33.2672545 101.334737,33.3146522 101.502688,33.3539372 L101.502688,33.3539372 L102.198766,33.5267911 L102.447027,33.590679 C102.690087,33.6595275 102.917541,33.7432574 103.129391,33.8418688 C103.411857,33.9733507 103.656996,34.1348181 103.864806,34.326271 C104.072617,34.5177238 104.233898,34.7430907 104.34865,35.0023715 C104.463402,35.2616523 104.521546,35.5586426 104.523083,35.8933424 C104.521546,36.3846501 104.401494,36.810493 104.162927,37.1708713 C103.924359,37.5312495 103.581246,37.8101728 103.133588,38.0076412 C102.685929,38.2051096 102.146335,38.3038437 101.514806,38.3038437 C100.888832,38.3038437 100.344706,38.2045878 99.8824279,38.0060759 C99.4201495,37.8075641 99.0600129,37.512538 98.8020181,37.1209978 C98.5440233,36.7294575 98.4082086,36.2437562 98.3945739,35.6638936 L98.3945739,35.6638936 L99.980432,35.6638936 L99.9979238,35.8200771 C100.030445,36.021154 100.099763,36.1935594 100.205877,36.3372933 C100.33852,36.5169606 100.515938,36.651839 100.738132,36.7419285 C100.960326,36.8320179 101.213168,36.8770626 101.496659,36.8770626 C101.774593,36.8770626 102.015959,36.8354144 102.220754,36.752118 C102.42555,36.6688216 102.584694,36.5527979 102.698185,36.404047 C102.811676,36.2552961 102.86917,36.0829538 102.870667,35.88702 C102.86917,35.7063091 102.81735,35.5545402 102.715208,35.4317133 C102.613067,35.3088863 102.464386,35.2041264 102.269165,35.1174335 C102.073945,35.0307406 101.835614,34.9520376 101.554173,34.8813247 L101.554173,34.8813247 L100.709788,34.6644593 L100.471106,34.5982091 C99.9292563,34.4334136 99.4929359,34.197137 99.1621448,33.8893791 C98.784098,33.5376558 98.5958429,33.0648039 98.5973798,32.4708233 C98.5958429,31.9842216 98.7220622,31.5585014 98.9760375,31.1936627 C99.2300128,30.828824 99.5788102,30.5445297 100.02243,30.3407798 C100.466049,30.1370299 100.970581,30.035155 101.536026,30.035155 Z M117.000416,20 L120.019864,32.7197694 L120.176476,32.7197694 L123.502194,20 L127.10553,20 L130.431109,32.7412751 L130.58786,32.7412751 L133.607307,20 L137.815813,20 L132.596003,38.3038438 L128.843148,38.3038438 L125.375005,26.3346784 L125.23258,26.3346784 L121.764575,38.3038438 L118.01172,38.3038438 L112.791772,20 L117.000416,20 Z M152.993585,20 L152.993585,23.1888449 L144.56211,23.1888449 L144.56211,27.5503076 L152.359903,27.5503076 L152.359903,30.7391525 L144.56211,30.7391525 L144.56211,35.1149989 L153.029192,35.1149989 L153.029192,38.3038438 L140.709529,38.3038438 L140.709529,20 L152.993585,20 Z M169.182597,20 L169.182597,23.1888449 L160.751121,23.1888449 L160.751121,27.5503076 L168.548915,27.5503076 L168.548915,30.7391525 L160.751121,30.7391525 L160.751121,35.1149989 L169.218203,35.1149989 L169.218203,38.3038438 L156.898541,38.3038438 L156.898541,20 L169.182597,20 Z M176.940133,20 L176.940133,28.072309 L177.175052,28.072309 L183.740948,20 L188.35545,20 L181.597334,28.1938021 L188.440849,38.3038438 L183.826348,38.3038438 L178.834364,30.7820242 L176.940133,33.0986324 L176.940133,38.3038438 L173.087552,38.3038438 L173.087552,20 L176.940133,20 Z M195.058986,20 L195.058986,35.1149989 L202.870966,35.1149989 L202.870966,38.3038438 L191.206405,38.3038438 L191.206405,20 L195.058986,20 Z M207.188803,20 L211.347654,27.8793166 L211.518592,27.8793166 L215.677443,20 L220,20 L213.348704,31.833149 L213.348704,38.3038438 L209.517542,38.3038438 L209.517542,31.833149 L202.866246,20 L207.188803,20 Z M96.5857574,30.1451529 L96.5857574,35.755047 L96.5783288,35.9735177 C96.5498982,36.402007 96.4400262,36.7797841 96.248713,37.106849 C96.0255142,37.4884248 95.7158081,37.7827961 95.3195948,37.989963 C94.9233815,38.1971298 94.4637532,38.3007132 93.9407098,38.3007132 C93.4731115,38.3007132 93.0499145,38.214675 92.6711189,38.0425987 C92.2923233,37.8705223 91.9917005,37.6088885 91.7692504,37.2576972 C91.5468003,36.9065059 91.4363437,36.4632405 91.4378648,35.9279009 L91.4378648,35.9279009 L93.069076,35.9279009 L93.0794978,36.08126 C93.0969426,36.228874 93.1354564,36.359253 93.1950391,36.4723967 C93.2744827,36.6232551 93.384693,36.7383683 93.52567,36.8177362 C93.666647,36.8971041 93.8322235,36.9367881 94.0223996,36.9367881 C94.2241613,36.9367881 94.3950281,36.8919991 94.5350002,36.8024212 C94.6749724,36.7128433 94.7815277,36.5801849 94.8546663,36.404446 C94.9278049,36.2287071 94.9651426,36.0122408 94.9666795,35.755047 L94.9666795,35.755047 L94.9666795,30.1451529 L96.5857574,30.1451529 Z M88.8936526,36.4150345 C89.1347419,36.4150345 89.3459513,36.5064437 89.5272806,36.6892621 C89.7086099,36.8720804 89.800043,37.0918205 89.8015798,37.3484823 C89.800043,37.5223592 89.7570307,37.6806654 89.672543,37.8234008 C89.5880554,37.9661362 89.477717,38.0797966 89.3415279,38.1643821 C89.2053388,38.2489676 89.056047,38.2912603 88.8936526,38.2912603 C88.6434603,38.2912603 88.4292068,38.1994726 88.2508921,38.0158972 C88.0725773,37.8323218 87.9841884,37.6098502 87.9857253,37.3484823 C87.9841884,37.0918205 88.0725773,36.8720804 88.2508921,36.6892621 C88.4292068,36.5064437 88.6434603,36.4150345 88.8936526,36.4150345 Z\" />\n          </g>\n        </svg>\n        <p className=\"text-sm\">Stay up to date on Next.js</p>\n        <p className=\"text-muted-foreground text-xs\">\n          A weekly newsletter to keep up with what's happening in the ecosystem.\n        </p>\n      </section>\n    </a>\n  )\n}\n\nexport function ShadcnStudioAsideSponsor() {\n  return (\n    <a\n      href=\"https://shadcnstudio.com/?utm_source=nuqs&utm_medium=banner&utm_campaign=github\"\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      className=\"group\"\n    >\n      <section className=\"text-muted-foreground space-y-4 rounded-md border border-dashed px-4 py-6 transition-colors group-hover:text-current group-active:text-current\">\n        <header className=\"mx-auto flex items-center justify-center gap-2\">\n          <svg\n            viewBox=\"0 0 328 329\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"size-8\"\n            aria-hidden\n          >\n            <rect\n              y=\"0.5\"\n              width=\"328\"\n              height=\"328\"\n              rx=\"164\"\n              fill=\"currentColor\"\n            />\n            <path\n              d=\"M165.018 72.3008V132.771C165.018 152.653 148.9 168.771 129.018 168.771H70.2288\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <path\n              d=\"M166.627 265.241L166.627 204.771C166.627 184.889 182.744 168.771 202.627 168.771L261.416 168.771\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"238.136\"\n              y1=\"98.8184\"\n              x2=\"196.76\"\n              y2=\"139.707\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"135.688\"\n              y1=\"200.957\"\n              x2=\"94.3128\"\n              y2=\"241.845\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"133.689\"\n              y1=\"137.524\"\n              x2=\"92.5566\"\n              y2=\"96.3914\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n            <line\n              x1=\"237.679\"\n              y1=\"241.803\"\n              x2=\"196.547\"\n              y2=\"200.671\"\n              strokeWidth=\"20\"\n              className=\"stroke-background\"\n            />\n          </svg>\n          <div className=\"flex flex-col\">\n            <span className=\"text-sm leading-tight font-medium\">\n              shadcnstudio.com\n            </span>\n            <span className=\"text-muted-foreground text-xs\">\n              shadcn blocks & templates\n            </span>\n          </div>\n        </header>\n        <p className=\"text-muted-foreground text-center text-xs\">\n          Accelerate your project development with ready-to-use, and fully\n          customizable shadcn ui Components, Blocks, UI Kits, Boilerplates,\n          Templates and Themes with AI Tools 🪄.\n        </p>\n      </section>\n    </a>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/_landing/works-with.tsx",
    "content": "import {\n  NextJS,\n  ReactRouter,\n  ReactSPA,\n  Remix,\n  TanStackRouter,\n  Vitest\n} from '@/src/components/frameworks'\nimport { cn } from '@/src/lib/utils'\nimport { ComponentProps } from 'react'\n\nexport function WorksWith({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex items-center gap-6 text-3xl', className)}\n      {...props}\n    >\n      <p className=\"sr-only\">Works with</p>\n      {/* <Vite /> */}\n      <ReactSPA />\n      <ReactRouter />\n      <NextJS />\n      <Remix />\n      <TanStackRouter />\n      <Vitest />\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/[slug]/_components/author.tsx",
    "content": "export function Author() {\n  return (\n    <a\n      href=\"https://bsky.app/profile/francoisbest.com\"\n      className=\"flex items-center gap-3 rounded-lg px-2 py-1 hover:bg-foreground/5\"\n      aria-description=\"Author\"\n    >\n      <img\n        src=\"/avatar-128.jpeg\"\n        alt=\"François Best\"\n        role=\"presentation\"\n        className=\"size-9 rounded-full\"\n      />\n      <div>\n        <p className=\"font-semibold\">François Best</p>\n        <p className=\"text-xs text-fd-muted-foreground\">@francoisbest.com</p>\n      </div>\n    </a>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/[slug]/opengraph-image.tsx",
    "content": "import { blog } from '@/src/app/source'\nimport { generateOpengraphImage, size } from '@/src/components/og-image'\nimport { notFound } from 'next/navigation'\nimport { ImageResponse } from 'next/og'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\n// Image metadata\nexport { contentType, size } from '@/src/components/og-image'\nexport const dynamic = 'force-static'\n\nexport async function generateStaticParams(): Promise<{ slug: string }[]> {\n  const pages = blog.getPages()\n  const slugs = new Set(pages.flatMap(page => page.slugs))\n  return Array.from(slugs).map(slug => ({ slug }))\n}\n\n// Image generation\nexport default async function Image({ params }: PageProps<'/blog/[slug]'>) {\n  const { slug } = await params\n  const page = blog.getPage([slug])\n  if (!page) notFound()\n  const customImage = await getCustomImage(slug)\n  if (customImage) {\n    return new ImageResponse(\n      (\n        <img\n          src={customImage}\n          alt=\"Open Graph Image\"\n          style={{\n            position: 'absolute',\n            inset: 0\n          }}\n        />\n      ),\n      size\n    )\n  }\n  // Fallback to generated image\n  return generateOpengraphImage({\n    title: page.data.title,\n    description: page.data.description\n  })\n}\n\n// --\n\nasync function getCustomImage(slug: string) {\n  const filePath = join(process.cwd(), 'content/blog/' + slug + '.og.png')\n  try {\n    const imageBuffer = await readFile(filePath)\n    return 'data:image/png;base64,' + imageBuffer.toString('base64')\n  } catch {\n    return null\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/[slug]/page.tsx",
    "content": "import { blog } from '@/src/app/source'\n// import { createMetadata } from '@/utils/metadata'\nimport { PageFooter } from '@/src/app/(pages)/_landing/page-footer'\nimport { Description } from '@/src/components/typography'\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator\n} from '@/src/components/ui/breadcrumb'\nimport { SiBluesky, SiGithub } from '@icons-pack/react-simple-icons'\nimport defaultMdxComponents from 'fumadocs-ui/mdx'\nimport type { Metadata } from 'next'\nimport Link from 'next/link'\nimport { notFound } from 'next/navigation'\nimport { Author } from './_components/author'\n\nexport default async function Page(props: {\n  params: Promise<{ slug: string }>\n}): Promise<React.ReactElement> {\n  const params = await props.params\n  const page = blog.getPage([params.slug])\n\n  if (!page) notFound()\n  const blueskyShareIntent = encodeURIComponent(\n    `\"${page.data.title}\" by @francoisbest.com on the @nuqs.dev blog • https://nuqs.dev/blog/${params.slug}`\n  )\n\n  return (\n    <>\n      <Breadcrumb className=\"container mt-8 max-w-[900px] md:px-8\">\n        <BreadcrumbList>\n          <BreadcrumbItem>\n            <BreadcrumbLink asChild>\n              <Link href=\"/\">Home</Link>\n            </BreadcrumbLink>\n          </BreadcrumbItem>\n          <BreadcrumbSeparator />\n          <BreadcrumbItem>\n            <BreadcrumbLink asChild>\n              <Link href=\"/blog\">Blog</Link>\n            </BreadcrumbLink>\n          </BreadcrumbItem>\n          <BreadcrumbSeparator />\n          <BreadcrumbItem>\n            <BreadcrumbPage>{page.data.title}</BreadcrumbPage>\n          </BreadcrumbItem>\n        </BreadcrumbList>\n      </Breadcrumb>\n      <div className=\"container max-w-[900px] py-12 md:px-8\">\n        <h1 className=\"text-foreground mb-4 text-4xl font-bold sm:text-5xl\">\n          {page.data.title}\n        </h1>\n        <Description>{page.data.description}</Description>\n      </div>\n      <div className=\"container flex max-w-[900px] flex-col gap-4 px-4 text-sm sm:flex-row sm:items-center sm:justify-between lg:px-8\">\n        <Author />\n        {page.data.date && (\n          <p className=\"text-fd-muted-foreground text-sm font-medium\">\n            {new Date(page.data.date).toLocaleDateString('en-GB', {\n              dateStyle: 'long'\n            })}\n          </p>\n        )}\n      </div>\n      <div className=\"container max-w-[900px] px-0 lg:px-8\">\n        <hr className=\"my-4\" />\n      </div>\n      <article className=\"container max-w-[900px] px-0 pt-8 pb-24 lg:px-4\">\n        <div className=\"prose min-w-0 flex-1 p-4\">\n          <page.data.body components={defaultMdxComponents} />\n        </div>\n      </article>\n      <nav\n        aria-label=\"Share this post\"\n        className=\"container flex max-w-[900px] items-center justify-center gap-4 px-0 pb-24 lg:px-4\"\n      >\n        <a\n          href={`https://github.com/47ng/nuqs/edit/next/packages/docs/content/blog/${params.slug}.mdx`}\n          className=\"flex items-center gap-1.5 hover:underline\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          <SiGithub role=\"presentation\" className=\"size-4\" />\n          Edit on GitHub\n        </a>\n        <span aria-hidden>•</span>\n        <a\n          href={`https://bsky.app/intent/compose?text=${blueskyShareIntent}`}\n          className=\"flex items-center gap-1.5 hover:underline\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          <SiBluesky role=\"presentation\" className=\"size-4\" />\n          Comment on Bluesky\n        </a>\n      </nav>\n      <PageFooter />\n    </>\n  )\n}\n\nexport async function generateMetadata(props: {\n  params: Promise<{ slug: string }>\n}): Promise<Metadata> {\n  const { slug } = await props.params\n  const page = blog.getPage([slug])\n\n  if (!page) notFound()\n\n  return {\n    title: page.data.title,\n    description:\n      page.data.description ??\n      'Type-safe search params state manager for React frameworks',\n    alternates: {\n      types: {\n        'application/rss+xml': [\n          {\n            url: '/blog/rss.xml',\n            title: 'nuqs blog RSS feed'\n          }\n        ]\n      }\n    }\n  }\n}\n\nexport async function generateStaticParams(): Promise<{ slug: string }[]> {\n  return blog.getPages().map(page => ({\n    slug: page.slugs[0]\n  }))\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/[slug]/twitter-image.tsx",
    "content": "import Image from './opengraph-image'\nexport default Image\nexport { contentType, size } from './opengraph-image'\n\nexport const dynamic = 'force-static'\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/_lib/source.ts",
    "content": "import { blog } from '@/src/app/source'\n\nexport type BlogPost = ReturnType<typeof blog.getPages>[number]\n\nexport function getBlogPosts() {\n  return blog.getPages().sort((a, b) => {\n    return (\n      new Date(b.data.date ?? 0).getTime() -\n      new Date(a.data.date ?? 0).getTime()\n    )\n  })\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/page.tsx",
    "content": "import { blog } from '@/src/app/source'\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator\n} from '@/src/components/ui/breadcrumb'\nimport { Card } from 'fumadocs-ui/components/card'\nimport { DocsDescription, DocsTitle } from 'fumadocs-ui/page'\nimport { RssIcon } from 'lucide-react'\nimport type { Metadata } from 'next'\nimport Link from 'next/link'\nimport { ComponentProps } from 'react'\nimport { PageFooter } from '../_landing/page-footer'\nimport { getBlogPosts } from './_lib/source'\n\nexport const dynamic = 'force-static'\n\nexport const metadata = {\n  title: 'Blog',\n  description: 'URL state management with nuqs',\n  alternates: {\n    types: {\n      'application/rss+xml': [\n        {\n          url: '/blog/rss.xml',\n          title: 'nuqs blog RSS feed'\n        }\n      ]\n    }\n  }\n} satisfies Metadata\n\nexport default function BlogIndexPage() {\n  const posts = getBlogPosts()\n  return (\n    <>\n      <Breadcrumb className=\"container mt-8 max-w-[900px] md:px-8\">\n        <BreadcrumbList>\n          <BreadcrumbItem>\n            <BreadcrumbLink asChild>\n              <Link href=\"/\">Home</Link>\n            </BreadcrumbLink>\n          </BreadcrumbItem>\n          <BreadcrumbSeparator />\n          <BreadcrumbItem>\n            <BreadcrumbPage>Blog</BreadcrumbPage>\n          </BreadcrumbItem>\n        </BreadcrumbList>\n      </Breadcrumb>\n      <div className=\"container max-w-[900px] py-12 md:px-8\">\n        <nav className=\"mb-4 flex items-center justify-between\">\n          <DocsTitle>Blog</DocsTitle>\n          <RssFeedLink />\n        </nav>\n        <DocsDescription>URL state management with nuqs</DocsDescription>\n      </div>\n      <ul className=\"container max-w-[900px] space-y-4 pb-24 lg:px-4\">\n        {posts.map(post => (\n          <BlogPostLink post={post} key={post.url} />\n        ))}\n      </ul>\n      <PageFooter />\n    </>\n  )\n}\n\ntype BlogPostLinkProps = ComponentProps<'li'> & {\n  post: ReturnType<typeof blog.getPages>[number]\n}\n\nfunction BlogPostLink({ post, ...props }: BlogPostLinkProps) {\n  return (\n    <li {...props}>\n      <Card\n        title={post.data.title}\n        href={post.url}\n        description={\n          <span className=\"flex flex-wrap gap-2\">\n            <span>{post.data.description}</span>\n            {post.data.date ? (\n              <span className=\"ml-auto\">\n                <time dateTime={new Date(post.data.date).toISOString()}>\n                  <span className=\"sr-only\">Published on </span>\n                  {new Date(post.data.date).toLocaleDateString('en-GB', {\n                    dateStyle: 'long'\n                  })}\n                </time>\n              </span>\n            ) : (\n              <span className=\"ml-auto\">\n                <em>Draft</em>\n              </span>\n            )}\n          </span>\n        }\n      />\n    </li>\n  )\n}\n\n// --\n\nfunction RssFeedLink() {\n  return (\n    <a\n      href=\"/blog/rss.xml\"\n      className=\"text-muted-foreground flex items-center gap-1 text-sm hover:underline\"\n    >\n      <RssIcon\n        className=\"size-4 text-orange-600 dark:text-orange-400\"\n        role=\"presentation\"\n      />\n      RSS\n    </a>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/blog/rss.xml/route.ts",
    "content": "import { getBlogPosts } from '../_lib/source'\n\nexport const dynamic = 'force-static'\n\nexport async function GET() {\n  const rssXml = generateRssXml()\n  return new Response(rssXml, {\n    headers: {\n      'Content-Type': 'application/rss+xml'\n    }\n  })\n}\n\nfunction generateRssXml() {\n  const baseUrl = 'https://nuqs.dev'\n  const posts = getBlogPosts().filter(post => Boolean(post.data.date))\n  const items = posts\n    .map(post => {\n      return `<item>\n      <title>${post.data.title}</title>\n      <link>${baseUrl}${post.url}</link>\n      <guid>${baseUrl}${post.url}</guid>\n      <description>${post.data.description}</description>\n      <pubDate>${new Date(post.data.date!).toUTCString()}</pubDate>\n    </item>`\n    })\n    .join('')\n  return `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n  <channel>\n    <title>nuqs</title>\n    <link>${baseUrl}/blog</link>\n    <description>URL state management with nuqs</description>\n    <atom:link href=\"http://${baseUrl}/blog/rss.xml\" rel=\"self\" type=\"application/rss+xml\" />\n  ${items}\n  </channel>\n</rss>\n`\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/layout.tsx",
    "content": "import { getSharedLayoutProps } from '@/src/components/shared-layout'\nimport { HomeLayout } from 'fumadocs-ui/layouts/home'\n\nexport default function PageLayout({\n  children\n}: {\n  children: React.ReactNode\n}) {\n  return <HomeLayout {...getSharedLayoutProps()}>{children}</HomeLayout>\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/page.tsx",
    "content": "import type { Metadata } from 'next'\nimport { metadata as rootMetadata } from '../layout'\nimport { ContributorsSection } from './_landing/contributors'\nimport { DependentsSection } from './_landing/dependents'\nimport { FeaturesSection } from './_landing/features'\nimport { HeroSection } from './_landing/hero'\nimport { PageFooter } from './_landing/page-footer'\nimport { QuotesSection } from './_landing/quotes/quotes-section'\nimport { SponsorsSection } from './_landing/sponsors'\n\nexport const metadata: Metadata = {\n  title: {\n    absolute: 'nuqs | Type-safe search params state management for React'\n  },\n  alternates: {\n    canonical: 'https://nuqs.dev',\n    types: rootMetadata.alternates.types\n  }\n}\n\nexport default function HomePage() {\n  return (\n    <main>\n      {/* Note: top-level banner goes here */}\n      <HeroSection />\n      <FeaturesSection />\n      <SponsorsSection />\n      <ContributorsSection />\n      <DependentsSection />\n      <QuotesSection />\n      <PageFooter />\n    </main>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/downloads.client.tsx",
    "content": "'use client'\n\nimport {\n  ChartContainer,\n  ChartLegend,\n  ChartLegendContent,\n  ChartTooltip,\n  ChartTooltipContent\n} from '@/src/components/ui/chart'\nimport type { ReactNode } from 'react'\nimport { CartesianGrid, Line, LineChart, XAxis, YAxis } from 'recharts'\nimport { formatDate, formatStatNumber } from '../lib/format'\nimport type { Datum, MultiDatum } from '../lib/npm'\nimport { PartialLine } from './partial-line'\nimport { Widget, WidgetProps } from './widget'\n\nfunction EstimatedDot(props: any) {\n  const { cx, cy, payload } = props\n  if (!payload?.estimated) return null\n  return (\n    <circle\n      cx={cx}\n      cy={cy}\n      r={3}\n      fill=\"none\"\n      stroke={props.stroke}\n      strokeWidth={1.5}\n      strokeDasharray=\"2 2\"\n    />\n  )\n}\n\ntype DownloadsGraphProps = WidgetProps & {\n  data: (Datum | MultiDatum)[]\n  partialLast: boolean\n  trend: ReactNode\n}\n\nexport function DownloadsGraph({\n  data,\n  partialLast,\n  trend,\n  ...props\n}: DownloadsGraphProps) {\n  return (\n    <Widget {...props}>\n      <ChartContainer\n        className=\"relative h-85.5 w-full pr-1\"\n        config={{\n          nuqs: {\n            label: 'nuqs'\n          },\n          'next-usequerystate': {\n            label: 'next-usequerystate'\n          }\n        }}\n        domChildren={<div className=\"absolute top-1.25 left-1\">{trend}</div>}\n      >\n        <LineChart\n          // accessibilityLayer // note: Causes a bug with Recharts 2.15.4 where a click on the chart moves the cursor to the first data point.\n          // Bug is fixed in Recharts v3 (but v3 breaks the <Customized> component for the partial line dashes)\n          data={data}\n          margin={{ top: 5, right: 0, bottom: 5, left: 5 }}\n        >\n          <YAxis\n            width={40}\n            fillOpacity={0.75}\n            axisLine={false}\n            tickLine={false}\n            tickCount={5}\n            tickFormatter={value => formatStatNumber(value).toUpperCase()}\n            allowDataOverflow\n          />\n          <ChartLegend\n            align=\"right\"\n            verticalAlign=\"top\"\n            content={<ChartLegendContent />}\n          />\n          <CartesianGrid vertical={false} />\n          <XAxis\n            padding={{ left: 20, right: 20 }}\n            dataKey=\"date\"\n            axisLine={false}\n            tickLine={false}\n            minTickGap={40}\n            tickMargin={10}\n            fillOpacity={0.75}\n            tickFormatter={value =>\n              value.startsWith(\"'\")\n                ? value\n                : formatDate(value, '', { day: '2-digit', month: 'short' })\n            }\n          />\n          <ChartTooltip\n            content={\n              <ChartTooltipContent\n                valueFormatter={value =>\n                  formatStatNumber(value as number).toUpperCase()\n                }\n                labelClassName=\"border-border border-b px-3 py-2\"\n                labelFormatter={(label, payload) => {\n                  const estimated = payload?.[0]?.payload?.estimated\n                  const formattedLabel = String(label).startsWith(\"'\")\n                    ? label\n                    : formatDate(String(label), '', {\n                        weekday: 'short',\n                        day: 'numeric',\n                        month: 'long'\n                      })\n                  return (\n                    <span>\n                      {formattedLabel}\n                      {estimated && (\n                        <span className=\"text-muted-foreground ml-1 text-xs font-normal\">\n                          (estimated)\n                        </span>\n                      )}\n                    </span>\n                  )\n                }}\n              />\n            }\n            isAnimationActive={false}\n            position={{ y: 20 }}\n          />\n          <PartialLine\n            // showDebug\n            partialLast={partialLast}\n            data={(data as MultiDatum[]).map(d => d.nuqs)}\n            dataKey=\"nuqs\"\n            padding={{ left: 20, right: 20 }}\n            isAnimationActive={false}\n            type=\"monotone\"\n            stroke=\"var(--color-red-500)\"\n            className=\"stroke-red-500 dark:stroke-red-400\"\n            dot={<EstimatedDot />}\n            strokeWidth={2}\n          />\n          <Line\n            dataKey=\"next-usequerystate\"\n            isAnimationActive={false}\n            type=\"monotone\"\n            stroke=\"var(--color-zinc-500)\"\n            strokeOpacity={0.5}\n            dot={<EstimatedDot />}\n            strokeWidth={2}\n          />\n        </LineChart>\n      </ChartContainer>\n    </Widget>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/downloads.tsx",
    "content": "import { Badge } from '@/src/components/ui/badge'\nimport { cn } from '@/src/lib/utils'\nimport { Download, Minus, TrendingDown, TrendingUp } from 'lucide-react'\nimport { formatStatNumber } from '../lib/format'\nimport {\n  combineStats,\n  fetchNpmPackage,\n  getIsoWeekday,\n  getPartialPreviousWeekDownloads\n} from '../lib/npm'\nimport { DownloadsGraph } from './downloads.client'\nimport { GraphSkeleton } from './graph.skeleton'\nimport { WidgetSkeleton } from './widget.skeleton'\n\nexport async function NPMStats() {\n  const [nuqs, nextUseQueryState] = await Promise.all([\n    fetchNpmPackage('nuqs'),\n    fetchNpmPackage('next-usequerystate')\n  ])\n  const both = combineStats(nuqs, nextUseQueryState)\n  return (\n    <>\n      <dl className=\"flex items-center gap-3 text-3xl font-bold lg:text-4xl\">\n        <Download className=\"size-7 lg:size-9\" />\n        <dt className=\"sr-only\">combined</dt>\n        <dd title={`All time, combined: ${both.allTime}`}>\n          {formatStatNumber(both.allTime)}\n        </dd>\n        <span className=\"font-light text-zinc-500\" aria-hidden>\n          |\n        </span>\n        <dt className=\"sr-only\">nuqs</dt>\n        <dd className=\"text-red-500\" title={`All time, nuqs: ${nuqs.allTime}`}>\n          {formatStatNumber(nuqs.allTime)}\n        </dd>\n        <dt className=\"sr-only\">next-usequerystate</dt>\n        <dd\n          className=\"text-zinc-500/50\"\n          title={`All time, next-usequerystate: ${nextUseQueryState.allTime}`}\n        >\n          {formatStatNumber(nextUseQueryState.allTime)}\n        </dd>\n      </dl>\n    </>\n  )\n}\n\nexport const NPMStatsSkeleton = () => (\n  <div className=\"bg-muted h-9 w-64 animate-pulse rounded-md lg:h-12\" />\n)\n\nexport async function NPMDownloads() {\n  const [nuqs, nextUseQueryState] = await Promise.all([\n    fetchNpmPackage('nuqs'),\n    fetchNpmPackage('next-usequerystate')\n  ])\n  const both = combineStats(nuqs, nextUseQueryState)\n  const lastDate = both.last30Days.at(-1)?.date\n  const lastDateWeekday = getIsoWeekday(lastDate ?? '')\n  // Fortunately the epoch did not land on a Sunday (it was a Thursday).\n  const isLastDateSunday = lastDateWeekday === 7\n  return (\n    <>\n      <DownloadsGraph\n        data={both.last90Days}\n        partialLast={!isLastDateSunday}\n        trend={\n          <TrendBadge\n            label={`nuqs downloads this week vs ${isLastDateSunday ? 'last week' : `the first ${lastDateWeekday} days of last week`}`}\n            // Compare the N days of the current week (possibly pending)\n            // to the first N days of the previous week.\n            oldValue={getPartialPreviousWeekDownloads(nuqs.last30Days)}\n            newValue={nuqs.last90Days.at(-1)?.downloads ?? 0}\n            estimated={nuqs.last90Days.at(-1)?.estimated}\n          />\n        }\n        title={\n          <>\n            <Download size={20} /> Last 90 days\n            <dl className=\"mr-1 ml-auto flex gap-2\">\n              <dt className=\"sr-only\">combined</dt>\n              <dd>\n                {formatStatNumber(\n                  both.last90Days.reduce(\n                    (sum, { nuqs, ['next-usequerystate']: n_uqs }) =>\n                      sum + nuqs + n_uqs,\n                    0\n                  )\n                )}\n              </dd>\n              <span className=\"font-light text-zinc-500\" aria-hidden>\n                |\n              </span>\n              <dt className=\"sr-only\">nuqs</dt>\n              <dd className=\"text-red-500\">\n                {formatStatNumber(\n                  nuqs.last90Days.reduce(\n                    (sum, { downloads }) => sum + downloads,\n                    0\n                  )\n                )}\n              </dd>\n              <dt className=\"sr-only\">next-usequerystate</dt>\n              <dd className=\"text-zinc-500/50\">\n                {formatStatNumber(\n                  nextUseQueryState.last90Days.reduce(\n                    (sum, { downloads }) => sum + downloads,\n                    0\n                  )\n                )}\n              </dd>\n            </dl>\n          </>\n        }\n      />\n      <DownloadsGraph\n        data={both.last30Days}\n        partialLast={false}\n        trend={\n          <TrendBadge\n            label=\"nuqs downloads compared to 7 days ago\"\n            oldValue={nuqs.last30Days.at(-8)?.downloads ?? 0}\n            newValue={nuqs.last30Days.at(-1)?.downloads ?? 0}\n          />\n        }\n        title={\n          <>\n            <Download size={20} /> Last 30 days\n            <dl className=\"mr-1 ml-auto flex gap-2\">\n              <dt className=\"sr-only\">combined</dt>\n              <dd>\n                {formatStatNumber(\n                  both.last30Days.reduce(\n                    (sum, { nuqs, ['next-usequerystate']: n_uqs }) =>\n                      sum + nuqs + n_uqs,\n                    0\n                  )\n                )}\n              </dd>\n              <span className=\"font-light text-zinc-500\" aria-hidden>\n                |\n              </span>\n              <dt className=\"sr-only\">nuqs</dt>\n              <dd className=\"text-red-500\">\n                {formatStatNumber(\n                  nuqs.last30Days.reduce(\n                    (sum, { downloads }) => sum + downloads,\n                    0\n                  )\n                )}\n              </dd>\n              <dt className=\"sr-only\">next-usequerystate</dt>\n              <dd className=\"text-zinc-500/50\">\n                {formatStatNumber(\n                  nextUseQueryState.last30Days.reduce(\n                    (sum, { downloads }) => sum + downloads,\n                    0\n                  )\n                )}\n              </dd>\n            </dl>\n          </>\n        }\n      />\n    </>\n  )\n}\n\nexport function NPMDownloadsSkeleton() {\n  return (\n    <>\n      <WidgetSkeleton\n        title={\n          <div className=\"flex w-full items-center gap-2\">\n            <Download size={20} /> Last 90 days\n            <div className=\"bg-muted ml-auto h-6 w-40 animate-pulse rounded-md\" />\n          </div>\n        }\n      >\n        <div className=\"flex w-full justify-end py-2\">\n          <div className=\"bg-muted h-4 w-52 animate-pulse rounded-md\" />\n        </div>\n        <GraphSkeleton className=\"h-69\" />\n      </WidgetSkeleton>\n      <WidgetSkeleton\n        title={\n          <div className=\"flex w-full items-center gap-2\">\n            <Download size={20} /> Last 30 days\n            <div className=\"bg-muted ml-auto h-6 w-40 animate-pulse rounded-md\" />\n          </div>\n        }\n      >\n        <div className=\"flex w-full justify-end py-2\">\n          <div className=\"bg-muted h-4 w-52 animate-pulse rounded-md\" />\n        </div>\n        <div className=\"flex h-69 w-full animate-pulse flex-col justify-between pt-1 pr-1 pl-10 opacity-50\">\n          <hr />\n          <hr />\n          <hr />\n          <hr />\n          <hr />\n        </div>\n      </WidgetSkeleton>\n    </>\n  )\n}\n\n// --\n\ntype TrendBadgeProps = {\n  oldValue: number\n  newValue: number\n  label: string\n  estimated?: boolean\n}\n\nfunction TrendBadge({ oldValue, newValue, label, estimated }: TrendBadgeProps) {\n  const diff = newValue - oldValue\n  const pct = oldValue === 0 ? 100 : (diff / oldValue) * 100\n  const sign = diff === 0 ? '' : diff > 0 ? '+' : '-'\n  const title = estimated\n    ? `${sign}${Math.abs(diff)} ${label} (* includes estimated data)`\n    : `${sign}${Math.abs(diff)} ${label}`\n  return (\n    <Badge\n      variant=\"outline\"\n      className={cn(\n        'flex items-center gap-1.25 rounded-md border-none pl-1.5',\n        diff > 0 && 'bg-green-500/10 text-green-500',\n        diff < 0 && 'bg-red-500/10 text-red-500',\n        diff === 0 && 'bg-zinc-500/10 text-zinc-500'\n      )}\n      title={title}\n    >\n      {diff > 0 && <TrendingUp size={12} />}\n      {diff < 0 && <TrendingDown size={12} />}\n      {diff === 0 && <Minus size={12} />}\n      {diff > 0 ? '+' : '-'}\n      {formatStatNumber(Math.abs(diff)) || 'No change'}\n      {oldValue !== 0 && (\n        <span className=\"font-normal\">({pct.toFixed(1)}%){estimated && '*'}</span>\n      )}\n    </Badge>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/graph.skeleton.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { ComponentProps } from 'react'\n\nexport function GraphSkeleton({ className }: ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex h-69 w-full animate-pulse flex-col justify-between pt-1 pr-1 pl-10 opacity-50',\n        className\n      )}\n    >\n      <hr />\n      <hr />\n      <hr />\n      <hr />\n      <hr />\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/partial-line.tsx",
    "content": "'use client'\n\nimport { Line, LineProps, usePlotArea, useYAxisScale } from 'recharts'\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max)\n}\n\nfunction getMonotoneSlopes(points: Point[]) {\n  const count = points.length\n  if (count < 2) return []\n  const deltas = new Array<number>(count - 1)\n  for (let i = 0; i < count - 1; i++) {\n    const dx = points[i + 1].x - points[i].x\n    deltas[i] = dx === 0 ? 0 : (points[i + 1].y - points[i].y) / dx\n  }\n  const slopes = new Array<number>(count)\n  slopes[0] = deltas[0]\n  slopes[count - 1] = deltas[count - 2]\n  for (let i = 1; i < count - 1; i++) {\n    if (\n      deltas[i - 1] === 0 ||\n      deltas[i] === 0 ||\n      deltas[i - 1] * deltas[i] < 0\n    ) {\n      slopes[i] = 0\n    } else {\n      slopes[i] = (deltas[i - 1] + deltas[i]) / 2\n    }\n  }\n  for (let i = 0; i < count - 1; i++) {\n    const delta = deltas[i]\n    if (delta === 0) {\n      slopes[i] = 0\n      slopes[i + 1] = 0\n      continue\n    }\n    const a = slopes[i] / delta\n    const b = slopes[i + 1] / delta\n    if (a < 0 || b < 0) {\n      slopes[i] = 0\n      slopes[i + 1] = 0\n      continue\n    }\n    const magnitude = a * a + b * b\n    if (magnitude > 9) {\n      const scale = 3 / Math.sqrt(magnitude)\n      slopes[i] = scale * a * delta\n      slopes[i + 1] = scale * b * delta\n    }\n  }\n  return slopes\n}\n\nfunction getMonotoneSegmentPoint(\n  start: Point,\n  end: Point,\n  startSlope: number,\n  endSlope: number,\n  t: number\n) {\n  const dx = end.x - start.x\n  const t2 = t * t\n  const t3 = t2 * t\n  const h00 = 2 * t3 - 3 * t2 + 1\n  const h10 = t3 - 2 * t2 + t\n  const h01 = -2 * t3 + 3 * t2\n  const h11 = t3 - t2\n  const x = start.x + dx * t\n  const y =\n    h00 * start.y + h10 * dx * startSlope + h01 * end.y + h11 * dx * endSlope\n  return { x, y }\n}\n\nfunction getMonotoneSegmentLength(\n  start: Point,\n  end: Point,\n  startSlope: number,\n  endSlope: number\n) {\n  const dx = end.x - start.x\n  const dy = end.y - start.y\n  const chord = Math.sqrt(dx * dx + dy * dy)\n  if (chord === 0) return 0\n  const samples = clamp(Math.round(chord / 12), 4, 12)\n  let length = 0\n  let previous = start\n  for (let i = 1; i <= samples; i++) {\n    const point = getMonotoneSegmentPoint(\n      start,\n      end,\n      startSlope,\n      endSlope,\n      i / samples\n    )\n    const deltaX = point.x - previous.x\n    const deltaY = point.y - previous.y\n    length += Math.sqrt(deltaX * deltaX + deltaY * deltaY)\n    previous = point\n  }\n  return length\n}\n\nfunction getMonotoneLengths(points: Point[], slopes: number[]) {\n  if (points.length < 2) return { total: 0, last: 0 }\n  let total = 0\n  let last = 0\n  for (let i = 0; i < points.length - 1; i++) {\n    const segmentLength = getMonotoneSegmentLength(\n      points[i],\n      points[i + 1],\n      slopes[i],\n      slopes[i + 1]\n    )\n    total += segmentLength\n    if (i === points.length - 2) {\n      last = segmentLength\n    }\n  }\n  return { total, last }\n}\n\ntype UseStrokeDashArrayProps = {\n  data: number[]\n  partialLast: boolean\n  padding?: {\n    top?: number\n    right?: number\n    bottom?: number\n    left?: number\n  }\n}\n\ntype Point = { x: number; y: number }\n\nfunction getDashArray(solidLength: number, dashedLength: number) {\n  const targetDashPattern = [2, 2]\n  const patternSegmentLength =\n    (targetDashPattern?.[0] || 0) + (targetDashPattern?.[1] || 0) || 1\n  const repetitions = Math.ceil(dashedLength / patternSegmentLength)\n  const dashedPatternSegments = Array.from({ length: repetitions }, () =>\n    targetDashPattern.join(' ')\n  )\n\n  const finalDasharray = `${solidLength} ${dashedPatternSegments.join(' ')}`\n\n  return finalDasharray\n}\n\nfunction useStrokeDashArray({\n  data,\n  partialLast,\n  padding = {}\n}: UseStrokeDashArrayProps) {\n  const area = usePlotArea()\n  const yScale = useYAxisScale()\n  if (!partialLast || !area || !yScale) return undefined\n  const p = {\n    t: padding.top ?? 0,\n    r: padding.right ?? 0,\n    b: padding.bottom ?? 0,\n    l: padding.left ?? 0\n  }\n  const stepX = (area.width - (p.l + p.r)) / (data.length - 1)\n  const coordinates = data.map((d, i) => ({\n    x: area.x + p.l + i * stepX,\n    y: yScale(d) ?? 0\n  }))\n  const slopes = getMonotoneSlopes(coordinates)\n  const { total, last } = getMonotoneLengths(coordinates, slopes)\n  const solidLength = total - last\n  return getDashArray(solidLength, last)\n}\n\nfunction useDebugPoints({\n  data,\n  padding = {},\n  enabled\n}: Pick<UseStrokeDashArrayProps, 'data' | 'padding'> & { enabled: boolean }) {\n  const area = usePlotArea()\n  const yScale = useYAxisScale()\n  if (!enabled || !area || !yScale) return null\n  const p = {\n    t: padding.top ?? 0,\n    r: padding.right ?? 0,\n    b: padding.bottom ?? 0,\n    l: padding.left ?? 0\n  }\n  const stepX = (area.width - (p.l + p.r)) / (data.length - 1)\n  const points = data.map((d, i) => ({\n    x: area.x + p.l + i * stepX,\n    y: yScale(d) ?? 0\n  }))\n  const slopes = getMonotoneSlopes(points)\n  return { points, slopes, enabled }\n}\n\nfunction DebugOverlay({ points, slopes }: DebugPoints) {\n  const tangentScale = 10\n  return (\n    <g className=\"pointer-events-none\">\n      {points.map((point, index) => (\n        <circle\n          key={`point-${index}`}\n          cx={point.x}\n          cy={point.y}\n          r={2}\n          fill=\"var(--color-amber-500)\"\n        />\n      ))}\n      {points.map((point, index) => {\n        const slope = slopes[index] ?? 0\n        const dx = tangentScale\n        const dy = slope * dx\n        return (\n          <line\n            key={`tangent-${index}`}\n            x1={point.x - dx}\n            y1={point.y - dy}\n            x2={point.x + dx}\n            y2={point.y + dy}\n            stroke=\"var(--color-amber-500)\"\n            strokeWidth={1}\n            strokeOpacity={0.7}\n          />\n        )\n      })}\n    </g>\n  )\n}\n\ntype PartialLineProps = LineProps & {\n  data: number[]\n  partialLast: boolean\n  padding?: {\n    top?: number\n    right?: number\n    bottom?: number\n    left?: number\n  }\n  showDebug?: boolean\n}\n\ntype DebugPoints = {\n  points: Point[]\n  slopes: number[]\n  enabled: boolean\n}\n\nexport function PartialLine({\n  data,\n  partialLast,\n  showDebug,\n  ...props\n}: PartialLineProps) {\n  if (!partialLast && !showDebug) {\n    // Avoid computing dasharray & debug points if not needed\n    // We need to not pass data to this component, hence why it's spread out of props.\n    return <Line {...props} />\n  }\n  return (\n    <PartialLineImpl\n      data={data}\n      partialLast={partialLast}\n      showDebug={showDebug}\n      {...props}\n    />\n  )\n}\n\nfunction PartialLineImpl({\n  data,\n  partialLast,\n  padding = {},\n  showDebug = false,\n  ...props\n}: PartialLineProps) {\n  const strokeDasharray = useStrokeDashArray({\n    data,\n    partialLast,\n    padding\n  })\n  const debugPoints = useDebugPoints({ data, padding, enabled: showDebug })\n  return (\n    <>\n      <Line strokeDasharray={strokeDasharray} {...props} />\n      {showDebug && debugPoints ? <DebugOverlay {...debugPoints} /> : null}\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/stars.client.tsx",
    "content": "'use client'\n\nimport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent\n} from '@/src/components/ui/chart'\nimport { Tabs, TabsList, TabsTrigger } from '@/src/components/ui/tabs'\nimport { Star } from 'lucide-react'\nimport { parseAsStringLiteral, useQueryState } from 'nuqs'\nimport { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'\nimport { formatDate } from '../lib/format'\nimport { GitHubStarHistory } from '../lib/github'\nimport { Widget } from './widget'\n\ntype StarsGraphProps = {\n  data: GitHubStarHistory\n  stargazersTab: React.ReactNode\n}\n\nconst starTabs = ['earned', 'gazers'] as const\ntype StarTab = (typeof starTabs)[number]\n\nexport function StarsGraph({ data, stargazersTab }: StarsGraphProps) {\n  const [activeTab, setActiveTab] = useQueryState(\n    'stars',\n    parseAsStringLiteral(starTabs).withDefault('earned')\n  )\n\n  return (\n    <Widget\n      className=\"px-0 pb-0\"\n      title={\n        <>\n          <Star size={20} className=\"ml-2\" /> {data.count}\n          <Tabs\n            className=\"mr-2 ml-auto w-auto\"\n            value={activeTab}\n            onValueChange={value => setActiveTab(value as StarTab)}\n          >\n            <TabsList>\n              <TabsTrigger\n                className=\"data-[state=active]:text-amber-700 dark:data-[state=active]:text-amber-500\"\n                value=\"earned\"\n              >\n                Stars earned\n              </TabsTrigger>\n              <TabsTrigger\n                className=\"data-[state=active]:text-amber-700 dark:data-[state=active]:text-amber-500\"\n                value=\"gazers\"\n              >\n                Stargazers\n              </TabsTrigger>\n            </TabsList>\n          </Tabs>\n        </>\n      }\n    >\n      {activeTab === 'earned' && (\n        <ChartContainer className=\"mt-2 h-82 w-full px-2\">\n          <BarChart\n            // accessibilityLayer // note: Causes a bug with Recharts 2.15.4 where a click on the chart moves the cursor to the first data point.\n            data={data.bins.toReversed().map(b => ({ ...b, Stars: b.diff }))}\n          >\n            <YAxis\n              axisLine={false}\n              width={30}\n              tickLine={false}\n              fillOpacity={0.75}\n            />\n            <CartesianGrid vertical={false} />\n            <XAxis\n              dataKey=\"date\"\n              tickLine={false}\n              tickMargin={10}\n              axisLine={false}\n              fillOpacity={0.75}\n              tickFormatter={value =>\n                formatDate(value, '', { day: '2-digit', month: 'short' })\n              }\n            />\n            <ChartTooltip\n              content={<ChartTooltipContent />}\n              cursor={{ fillOpacity: 0.5 }}\n              isAnimationActive={false}\n              position={{ y: 20 }}\n            />\n            <Bar\n              isAnimationActive={false}\n              shape={<CustomGradientBar />}\n              dataKey=\"Stars\"\n              fill=\"var(--color-amber-500)\"\n            />\n          </BarChart>\n        </ChartContainer>\n      )}\n      {activeTab === 'gazers' && stargazersTab}\n    </Widget>\n  )\n}\n\nconst CustomGradientBar = (\n  props: React.SVGProps<SVGRectElement> & { dataKey?: string }\n) => {\n  const { fill, x, y, width, height, dataKey } = props\n  return (\n    <>\n      <rect\n        x={x}\n        y={y}\n        width={width}\n        height={height}\n        stroke=\"none\"\n        fill={`url(#gradient-bar-pattern-${dataKey})`}\n      />\n      <rect x={x} y={y} width={width} height={2} stroke=\"none\" fill={fill} />\n      <defs>\n        <linearGradient\n          id={`gradient-bar-pattern-${dataKey}`}\n          x1=\"0\"\n          y1=\"0\"\n          x2=\"0\"\n          y2=\"1\"\n        >\n          <stop offset=\"0%\" stopColor={fill} stopOpacity={0.2} />\n          <stop offset=\"100%\" stopColor={fill} stopOpacity={0} />\n        </linearGradient>\n      </defs>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/stars.gazers-list.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { formatDate, formatStatNumber } from '../lib/format'\nimport type { GitHubStarHistory } from '../lib/github'\n\ntype Stargazer = GitHubStarHistory['bins'][number]['stargarzers'][number]\n\ntype StargazersListProps = {\n  stars: GitHubStarHistory\n}\n\nexport default function StargazersList({ stars }: StargazersListProps) {\n  return (\n    <ul className=\"max-h-84 overflow-y-auto overscroll-contain\">\n      {stars.bins.map((bin, index) => (\n        <section key={bin.date} className=\"relative not-first:pt-4\">\n          <h3 className=\"text-muted-foreground bg-background sticky top-0 border-b px-3 py-2 text-xs leading-tight font-semibold uppercase\">\n            {formatDate(bin.date, '', {\n              weekday: 'long',\n              day: '2-digit',\n              month: 'long'\n            })}\n          </h3>\n          {bin.stargarzers.length === 0 && (\n            <p className=\"text-muted-foreground flex items-center gap-2 p-3 text-sm\">\n              {index === 0 ? (\n                <>\n                  <span className=\"text-lg\">⏳</span> No stargazers yet today\n                </>\n              ) : (\n                <>\n                  <span className=\"text-lg\">😢</span> No stargazers on that day\n                </>\n              )}\n            </p>\n          )}\n          {bin.stargarzers.map(stargazer => (\n            <Stargazer\n              data={stargazer}\n              key={stargazer.login + stargazer.avatarUrl}\n            />\n          ))}\n        </section>\n      ))}\n    </ul>\n  )\n}\n\ntype StargazerProps = {\n  data: Stargazer\n}\n\nfunction Stargazer({\n  data: { login, name, avatarUrl, company, followers }\n}: StargazerProps) {\n  return (\n    <li className=\"border-border/50 flex flex-wrap gap-2 px-3 py-2 text-sm not-last:border-b\">\n      <a\n        href={`https://github.com/${login}`}\n        className=\"group flex items-center gap-2\"\n      >\n        <img\n          src={avatarUrl}\n          alt={name ?? 'Unknown'}\n          className=\"h-5 w-5 rounded-full\"\n        />\n        <span className=\"text-foreground font-semibold empty:hidden\">\n          {name}\n        </span>\n        <span className=\"text-sm text-zinc-500 group-hover:underline\">\n          {login}\n        </span>\n      </a>\n      <span className=\"ml-auto flex items-center gap-2 text-zinc-500\">\n        <span className=\"font-semibold empty:hidden\">{company}</span>\n        <span\n          className={cn(\n            followers > 100 && 'text-blue-600 dark:text-blue-400/80',\n            followers > 500 && 'text-green-600 dark:text-green-400/70'\n          )}\n        >\n          {formatStatNumber(followers)} follower{followers > 1 && 's'}\n        </span>\n      </span>\n    </li>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/stars.tsx",
    "content": "import { Star } from 'lucide-react'\nimport { connection } from 'next/server'\nimport { getStarHistory } from '../lib/github'\nimport { GraphSkeleton } from './graph.skeleton'\nimport { StarsGraph } from './stars.client'\nimport StargazersList from './stars.gazers-list'\nimport { WidgetSkeleton } from './widget.skeleton'\n\nexport async function StarHistoryGraph() {\n  await connection()\n  const stars = await getStarHistory()\n  return (\n    <StarsGraph data={stars} stargazersTab={<StargazersList stars={stars} />} />\n  )\n}\n\nexport function StarHistoryGraphSkeleton() {\n  return (\n    <WidgetSkeleton\n      title={\n        <div className=\"flex w-full items-center gap-2 pb-1\">\n          <Star size={20} />\n          <div className=\"bg-muted h-5 w-16 animate-pulse rounded-md\" />\n          <div className=\"bg-muted ml-auto h-9 w-50 animate-pulse rounded-md\" />\n        </div>\n      }\n    >\n      <GraphSkeleton className=\"h-74.5 pt-2\" />\n    </WidgetSkeleton>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/versions.tsx",
    "content": "'use client'\n\nimport {\n  ChartContainer,\n  ChartLegend,\n  ChartLegendContent,\n  ChartTooltip,\n  ChartTooltipContent\n} from '@/src/components/ui/chart'\nimport { Checkbox } from '@/src/components/ui/checkbox'\nimport { Label } from '@/src/components/ui/label'\nimport { Tabs, TabsList, TabsTrigger } from '@/src/components/ui/tabs'\nimport { Boxes } from 'lucide-react'\nimport { inferParserType, useQueryStates } from 'nuqs'\nimport { CartesianGrid, Line, LineChart, XAxis, YAxis } from 'recharts'\nimport { formatDate, formatStatNumber } from '../lib/format'\nimport { pkgParser, searchParams } from '../searchParams'\nimport { Widget } from './widget'\n\ntype VersionProps = {\n  records: Array<\n    Record<\n      | `${number}.${number}.${number}`\n      | `${number}.${number}.${number}-beta.${number}`,\n      number\n    > & {\n      date: string\n    }\n  >\n  versions: string[]\n}\n\n// stroke-red-500 fill-red-500 bg-red-500 text-red-500\n// stroke-amber-500 fill-amber-500 bg-amber-500 text-amber-500\n// stroke-green-500 fill-green-500 bg-green-500 text-green-500\n// stroke-blue-500 fill-blue-500 bg-blue-500 text-blue-500\n// stroke-purple-500 fill-purple-500 bg-purple-500 text-purple-500\n\nconst lineColors = [\n  'green-500',\n  'amber-500',\n  'red-500',\n  'blue-500',\n  'purple-500'\n]\n\nexport function Versions({ records, versions }: VersionProps) {\n  const [{ pkg: activeTab, beta }, setSearchParams] = useQueryStates(\n    searchParams,\n    { shallow: false }\n  )\n  return (\n    <Widget\n      className=\"lg:col-span-2\"\n      title={\n        <>\n          <Boxes size={24} strokeWidth={1.5} />\n          Version adoption\n          <Label className=\"ml-auto flex items-center gap-2\">\n            <Checkbox\n              id=\"beta\"\n              checked={beta}\n              onCheckedChange={checked =>\n                setSearchParams({ beta: checked === true })\n              }\n            />\n            Beta\n          </Label>\n          <Tabs\n            className=\"ml-1 w-auto\"\n            value={activeTab}\n            onValueChange={value =>\n              setSearchParams({\n                pkg: value as inferParserType<typeof pkgParser>\n              })\n            }\n          >\n            <TabsList>\n              <TabsTrigger\n                value=\"nuqs\"\n                className=\"data-[state=active]:text-red-700 dark:data-[state=active]:text-red-400\"\n              >\n                nuqs\n              </TabsTrigger>\n              <TabsTrigger value=\"next-usequerystate\">\n                next-usequerystate\n              </TabsTrigger>\n              <TabsTrigger\n                value=\"both\"\n                className=\"data-[state=active]:text-green-700 dark:data-[state=active]:text-green-300\"\n              >\n                combined\n              </TabsTrigger>\n            </TabsList>\n          </Tabs>\n        </>\n      }\n    >\n      <ChartContainer\n        className=\"mt-1 h-82 w-full pr-1\"\n        config={versions.reduce(\n          (config, version) => ({ ...config, [version]: { label: version } }),\n          {}\n        )}\n      >\n        <LineChart\n          // accessibilityLayer // note: Causes a bug with Recharts 2.15.4 where a click on the chart moves the cursor to the first data point.\n          data={records}\n          margin={{ top: 5, right: 0, bottom: 5, left: 5 }}\n        >\n          <ChartLegend\n            align=\"right\"\n            verticalAlign=\"top\"\n            content={<ChartLegendContent />}\n          />\n          <YAxis\n            width={30}\n            fillOpacity={0.75}\n            axisLine={false}\n            tickLine={false}\n            tickFormatter={value => formatStatNumber(value)}\n            allowDataOverflow\n          />\n          <CartesianGrid vertical={false} />\n          <XAxis\n            dataKey=\"date\"\n            padding={{ left: 20, right: 20 }}\n            axisLine={false}\n            tickLine={false}\n            minTickGap={40}\n            tickMargin={10}\n            fillOpacity={0.75}\n            tickFormatter={value =>\n              formatDate(value, '', { day: '2-digit', month: 'short' })\n            }\n          />\n          <ChartTooltip\n            content={\n              <ChartTooltipContent\n                valueFormatter={value => formatStatNumber(value as number)}\n              />\n            }\n            isAnimationActive={false}\n            position={{ y: 20 }}\n          />\n          {versions.map((version, index) => (\n            <Line\n              isAnimationActive={false}\n              key={version}\n              dataKey={version}\n              type=\"monotone\"\n              stroke={`var(--color-${lineColors[index]})`}\n              dot={false}\n              strokeWidth={2}\n            />\n          ))}\n        </LineChart>\n      </ChartContainer>\n    </Widget>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/widget.skeleton.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { Widget, WidgetProps } from './widget'\n\nexport function WidgetSkeleton({ className, ...props }: WidgetProps) {\n  return <Widget className={cn('animate-pulse', className)} {...props} />\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/_components/widget.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport type { ComponentProps, ReactNode } from 'react'\n\nexport type WidgetProps = Omit<ComponentProps<'div'>, 'title'> & {\n  title?: ReactNode\n}\n\nexport function Widget({ title, children, className, ...props }: WidgetProps) {\n  return (\n    <div className={cn('h-96 rounded-xl border p-2', className)} {...props}>\n      {Boolean(title) && (\n        <div className=\"flex flex-wrap items-center gap-2 pl-1.5 text-lg font-bold text-inherit\">\n          {title}\n        </div>\n      )}\n      {children}\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/lib/format.ts",
    "content": "const LOCALE = 'en-GB'\n\n/**\n * Format a date-ish object to a locale-friendly string\n */\nexport function formatDate(\n  date?: Date | string | number,\n  defaultValue: string = '',\n  options: Intl.DateTimeFormatOptions = {\n    year: 'numeric',\n    month: 'long',\n    day: 'numeric'\n  }\n) {\n  if (!date) {\n    return defaultValue\n  }\n  // https://css-tricks.com/how-to-convert-a-date-string-into-a-human-readable-format/\n  return new Date(date).toLocaleDateString(LOCALE, options)\n}\n\nexport function formatTime(date: Date | string | number) {\n  return new Date(date).toLocaleTimeString(LOCALE, {\n    hour12: false,\n    hour: '2-digit',\n    minute: '2-digit'\n  })\n}\n\nconst numberFormat = Intl.NumberFormat(LOCALE)\n\nexport function formatNumber(value: number) {\n  return numberFormat.format(value)\n}\n\nexport function formatSEOKeyValues(dict: Record<string, string>) {\n  return Object.keys(dict).flatMap((key, index) => [\n    { name: `twitter:label${index + 1}`, content: key },\n    { name: `twitter:data${index + 1}`, content: dict[key] }\n  ])\n}\n\nexport function formatStatNumber(\n  number: number,\n  options: Intl.NumberFormatOptions = {}\n): string {\n  return number.toLocaleString(LOCALE, {\n    notation: 'compact',\n    unitDisplay: 'short',\n    ...options\n  })\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/lib/github.ts",
    "content": "import dayjs from 'dayjs'\nimport utc from 'dayjs/plugin/utc'\nimport 'server-only'\nimport { z } from 'zod'\n\ndayjs.extend(utc)\n\nexport type GitHubStarHistory = {\n  count: number\n  bins: Array<{\n    stars: number // Value at the end of the day\n    diff: number // Stars earned during the day\n    date: string\n    stargarzers: Array<{\n      login: string\n      name: string | null\n      avatarUrl: string\n      company: string | null\n      followers: number\n    }>\n  }>\n}\n\nconst starHistoryQuerySchema = z.object({\n  data: z.object({\n    repository: z.object({\n      stargazers: z.object({\n        totalCount: z.number(),\n        pageInfo: z.object({\n          hasNextPage: z.boolean(),\n          endCursor: z.string().nullish()\n        }),\n        edges: z.array(\n          z.object({\n            starredAt: z\n              .string()\n              .datetime()\n              .transform(d => new Date(d)),\n            node: z.object({\n              login: z.string(),\n              name: z.string().nullish(),\n              avatarUrl: z.string(),\n              company: z.string().nullish(),\n              followers: z.object({\n                totalCount: z.number()\n              })\n            })\n          })\n        )\n      })\n    })\n  })\n})\n\nexport async function getStarHistory(\n  slug = '47ng/nuqs'\n): Promise<GitHubStarHistory> {\n  const [owner, repo] = slug.split('/')\n\n  // Compute the 12-day window [today .. today-11d] in UTC\n  const todayStart = dayjs().utc().startOf('day')\n  const days = Array.from({ length: 12 }, (_, i) =>\n    todayStart.clone().subtract(i, 'day').format('YYYY-MM-DD')\n  )\n  const windowStart = todayStart.clone().subtract(11, 'day') // already startOf('day')\n\n  // Pre-initialize bins for consecutive days (including empty days), most recent first\n  const bins: GitHubStarHistory['bins'] = days.map(date => ({\n    stars: 0,\n    diff: 0,\n    date,\n    stargarzers: []\n  }))\n\n  // Paginate through stargazers 100 at a time until we reach older than the window start\n  let after: string | undefined\n  let hasNextPage = true\n  let totalCount = 0\n\n  while (hasNextPage) {\n    const afterClause = after ? `, after: \"${after}\"` : ''\n    const query = `query {\n  repository(owner: \"${owner}\", name: \"${repo}\") {\n    stargazers(first: 100, orderBy: {field: STARRED_AT, direction: DESC}${afterClause}) {\n      totalCount\n      pageInfo {\n        hasNextPage\n        endCursor\n      }\n      edges {\n        starredAt\n        node {\n          login\n          name\n          avatarUrl\n          company\n          followers {\n            totalCount\n          }\n        }\n      }\n    }\n  }\n}`.replace(/\\s+/g, ' ') // Minify\n    const res = await fetch(\n      `https://api.github.com/graphql?fn=getStarHistory`,\n      {\n        method: 'POST',\n        headers: {\n          'content-type': 'application/json',\n          Authorization: `bearer ${process.env.GITHUB_TOKEN}`\n        },\n        body: JSON.stringify({ query })\n      }\n    )\n    if (!res.ok) {\n      throw new Error(\n        `GitHub API error: ${res.status} ${res.statusText}\n${query}\n${await res.text()}`\n      )\n    }\n\n    const {\n      data: {\n        repository: {\n          stargazers: { totalCount: tc, pageInfo, edges }\n        }\n      }\n    } = starHistoryQuerySchema.parse(await res.json())\n\n    totalCount = tc\n\n    // Fill bins for edges within the 12-day window\n    for (const { starredAt, node } of edges) {\n      const starredAtUtc = dayjs.utc(starredAt)\n      if (starredAtUtc.isBefore(windowStart)) {\n        // We already went beyond the 12-day window; stop after this page\n        hasNextPage = false\n        continue\n      }\n      const dateStr = starredAtUtc.format('YYYY-MM-DD')\n      const idx = days.indexOf(dateStr)\n      if (idx !== -1) {\n        bins[idx].stars++\n        bins[idx].stargarzers.push({\n          login: node.login,\n          name: node.name ?? null,\n          avatarUrl: node.avatarUrl,\n          company: node.company ?? null,\n          followers: node.followers.totalCount\n        })\n      }\n    }\n\n    // Decide if we should continue pagination\n    if (!pageInfo.hasNextPage) {\n      hasNextPage = false\n    } else if (edges.length > 0) {\n      const oldestInPage = dayjs.utc(edges[edges.length - 1].starredAt)\n      if (oldestInPage.isBefore(windowStart)) {\n        hasNextPage = false\n      } else {\n        after = pageInfo.endCursor ?? undefined\n      }\n    } else {\n      hasNextPage = false\n    }\n  }\n\n  // Compute end-of-day star totals using totalCount\n  if (bins.length > 0) {\n    bins[0].diff = bins[0].stars\n    bins[0].stars = totalCount\n    for (let i = 1; i < bins.length; ++i) {\n      bins[i].diff = bins[i].stars\n      bins[i].stars = bins[i - 1].stars - bins[i - 1].diff\n    }\n  }\n\n  return {\n    count: totalCount,\n    bins\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/lib/npm.ts",
    "content": "import dayjs from 'dayjs'\nimport isoWeek from 'dayjs/plugin/isoWeek'\nimport minMax from 'dayjs/plugin/minMax'\nimport 'server-only'\nimport { z } from 'zod'\n\ndayjs.extend(isoWeek)\ndayjs.extend(minMax)\n\nexport type Datum = {\n  date: string\n  downloads: number\n  estimated?: boolean\n}\n\nexport type MultiDatum = {\n  date: string\n  nuqs: number\n  'next-usequerystate': number\n  estimated?: boolean\n}\n\nexport type NpmPackageStatsData = {\n  allTime: number\n  last30Days: Datum[]\n  last90Days: Datum[]\n}\n\n// const regexp = /https:\\/\\/npmjs\\.com\\/package\\/([\\w.-]+|@[\\w.-]+\\/[\\w.-]+)/gm\n\ntype RangeResponse = {\n  downloads: Array<{\n    downloads: number\n    day: string\n  }>\n}\n\nconst rangeResponseSchema = z.object({\n  downloads: z.array(\n    z.object({\n      downloads: z.number(),\n      day: z.string()\n    })\n  )\n})\n\nasync function getLastNDays(pkg: string, n: number): Promise<Datum[]> {\n  const start = dayjs().subtract(n, 'day').format('YYYY-MM-DD')\n  const end = dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD')\n  const url = `https://api.npmjs.org/downloads/range/${start}:${end}/${pkg}`\n  try {\n    const { downloads } = rangeResponseSchema.parse(await get(url))\n    const data = downloads.map(d => ({\n      date: d.day,\n      downloads: d.downloads\n    }))\n    if (data.at(-1)?.downloads === 0) {\n      data.pop() // Remove last day if it's zero (stats not available yet)\n    }\n    return data\n  } catch (cause) {\n    const error = new Error(`error: getLastNDays(${pkg}, ${n}) - url: ${url}`, {\n      cause\n    })\n    console.error(error)\n    return []\n  }\n}\n\n/**\n * Interpolate zero-download days using weekly rhythm-aware estimation.\n * Processes left-to-right so earlier interpolated values can feed later ones.\n */\nfunction interpolateZeroDays(data: Datum[]): Datum[] {\n  for (let i = 0; i < data.length; i++) {\n    if (data[i].downloads !== 0) continue\n\n    // D-7: same weekday last week\n    if (i - 7 < 0) continue\n    const base = data[i - 7].downloads\n    if (base === 0) continue\n\n    // Backward trend: (D-1 vs D-8) week-over-week\n    let backwardTrend: number | null = null\n    if (\n      i - 1 >= 0 &&\n      i - 8 >= 0 &&\n      data[i - 1].downloads > 0 &&\n      data[i - 8].downloads > 0\n    ) {\n      backwardTrend =\n        (data[i - 1].downloads - data[i - 8].downloads) /\n        data[i - 8].downloads\n    }\n\n    // Forward trend: (D+1 vs D-6) week-over-week\n    let forwardTrend: number | null = null\n    if (\n      i + 1 < data.length &&\n      i - 6 >= 0 &&\n      data[i + 1].downloads > 0 &&\n      data[i - 6].downloads > 0\n    ) {\n      forwardTrend =\n        (data[i + 1].downloads - data[i - 6].downloads) /\n        data[i - 6].downloads\n    }\n\n    // Average available trends\n    let trend: number\n    if (backwardTrend !== null && forwardTrend !== null) {\n      trend = (backwardTrend + forwardTrend) / 2\n    } else if (backwardTrend !== null) {\n      trend = backwardTrend\n    } else if (forwardTrend !== null) {\n      trend = forwardTrend\n    } else {\n      trend = 0\n    }\n\n    data[i] = {\n      date: data[i].date,\n      downloads: Math.max(0, Math.round(base * (1 + trend))),\n      estimated: true\n    }\n  }\n  return data\n}\n\nconst packageResponseSchema = z.object({\n  time: z.object({\n    created: z.string()\n  })\n})\n\nasync function getPackageCreationDate(pkg: string): Promise<dayjs.Dayjs> {\n  const npmStatsEpoch = dayjs('2015-01-10')\n  const url = `https://registry.npmjs.org/${pkg}`\n  try {\n    const { time } = packageResponseSchema.parse(await get(url))\n    return dayjs.max(npmStatsEpoch, dayjs(time.created))\n  } catch (cause) {\n    const error = new Error(\n      `error: getPackageCreationDate(${pkg}) - url: ${url}, falling back to npm stats epoch`,\n      { cause }\n    )\n    console.error(error)\n    return npmStatsEpoch\n  }\n}\n\nasync function getAllTime(pkg: string): Promise<number> {\n  let downloads: number = 0\n  let start = dayjs(await getPackageCreationDate(pkg))\n  let end = start.add(18, 'month')\n  const now = dayjs()\n  while (start.isBefore(now)) {\n    const url = `https://api.npmjs.org/downloads/range/${start.format(\n      'YYYY-MM-DD'\n    )}:${end.format('YYYY-MM-DD')}/${pkg}`\n    try {\n      const res = rangeResponseSchema.parse(await get(url))\n      downloads += res.downloads.reduce((sum, d) => sum + d.downloads, 0)\n      start = end\n      end = start.add(18, 'month')\n    } catch (cause) {\n      const error = new Error(`error: getAllTime(${pkg}) - url: ${url}`, {\n        cause\n      })\n      console.error(error)\n      break\n    }\n  }\n  return downloads\n}\n\nexport async function fetchNpmPackage(\n  pkg: string\n): Promise<NpmPackageStatsData> {\n  // Ensure we cover 90 days + a full first week\n  const startOfFirstWeek = dayjs().subtract(90, 'day').startOf('isoWeek')\n  const ninetyOrSoDays = dayjs().diff(startOfFirstWeek, 'day')\n  const [allTime, last30DaysRaw, last90DaysRaw] = await Promise.all([\n    getAllTime(pkg),\n    getLastNDays(pkg, 30),\n    getLastNDays(pkg, ninetyOrSoDays)\n  ])\n  const last30Days = interpolateZeroDays(last30DaysRaw)\n  const last90Days = interpolateZeroDays(last90DaysRaw)\n  return {\n    allTime,\n    last30Days,\n    last90Days: groupByWeek(last90Days)\n  }\n}\n\nasync function get(url: string): Promise<unknown> {\n  const res = await fetch(url, {\n    next: {\n      revalidate: 6 * 60 * 60, // 6 hours\n      tags: ['npm-stats']\n    }\n  })\n  return res.json()\n}\n\nfunction groupByWeek(data: Datum[]): Datum[] {\n  const weeks = new Map<string, { downloads: number; estimated: boolean }>()\n  for (const d of data) {\n    const date = dayjs(d.date)\n    const key = [\n      \"'\" + (date.isoWeekYear() - 2000),\n      date.isoWeek().toFixed().padStart(2, '0')\n    ].join('W')\n    const existing = weeks.get(key) ?? { downloads: 0, estimated: false }\n    weeks.set(key, {\n      downloads: existing.downloads + d.downloads,\n      estimated: existing.estimated || (d.estimated ?? false)\n    })\n  }\n  return Array.from(weeks.entries()).map(([date, { downloads, estimated }]) => ({\n    date,\n    downloads,\n    ...(estimated ? { estimated: true } : {})\n  }))\n}\n\nexport function combineStats(\n  nuqs: NpmPackageStatsData,\n  n_uqs: NpmPackageStatsData\n) {\n  return {\n    allTime: nuqs.allTime + n_uqs.allTime,\n    last30Days: nuqs.last30Days.map((d, i) => ({\n      date: d.date,\n      nuqs: d.downloads,\n      ['next-usequerystate']: n_uqs.last30Days[i].downloads,\n      ...(d.estimated ? { estimated: true } : {})\n    })),\n    last90Days: nuqs.last90Days.map((d, i) => ({\n      date: d.date,\n      nuqs: d.downloads,\n      ['next-usequerystate']: n_uqs.last90Days[i].downloads,\n      ...(d.estimated ? { estimated: true } : {})\n    }))\n  }\n}\n\n// Re-export to avoid importing dayjs everywhere\n// ISO weekday: 1 (Monday) - 7 (Sunday)\nexport function getIsoWeekday(date: string) {\n  const lastDay = dayjs(date)\n  return lastDay.isoWeekday()\n}\n\nexport function getPartialPreviousWeekDownloads(data: Datum[]) {\n  const lastDate = data.at(-1)?.date\n  if (!lastDate) return 0\n  const lastDay = dayjs(lastDate)\n  const startOfLastWeek = lastDay.startOf('isoWeek').subtract(7, 'day')\n  const numDaysInCurrentWeek = lastDay.isoWeekday()\n  const filtered = data.filter(d => {\n    const date = dayjs(d.date)\n    return (\n      date.isSame(startOfLastWeek) ||\n      (date.isAfter(startOfLastWeek) &&\n        date.isBefore(startOfLastWeek.add(numDaysInCurrentWeek, 'day')))\n    )\n  })\n  return filtered.reduce((sum, d) => sum + d.downloads, 0)\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/lib/svg.ts",
    "content": "// Source:\n// https://medium.com/@francoisromain/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74\n\nexport type Point = [number, number]\n\nexport type CommandFn = (point: Point, index: number, array: Point[]) => string\n\nconst FLOAT_DECIMALS = 4\n\nexport function svgPath(points: Point[], command: CommandFn) {\n  // build the d attributes by looping over the points\n  const d = points.reduce(\n    (acc, point, i, a) =>\n      i === 0 // if first point\n        ? `M ${point[0].toFixed(FLOAT_DECIMALS)},${point[1].toFixed(\n            FLOAT_DECIMALS\n          )}` // else\n        : `${acc} ${command([point[0], point[1]], i, a)}`,\n    ''\n  )\n  return d\n}\nexport const lineCommand = (point: Point) => `L ${point[0]} ${point[1]}`\n\nconst line = (pointA: Point, pointB: Point) => {\n  const lengthX = pointB[0] - pointA[0]\n  const lengthY = pointB[1] - pointA[1]\n  return {\n    length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),\n    angle: Math.atan2(lengthY, lengthX)\n  }\n}\n\nfunction controlPoint(\n  current: Point,\n  previous: Point,\n  next: Point,\n  reverse?: boolean\n) {\n  // When 'current' is the first or last point of the array\n  // 'previous' or 'next' don't exist.\n  // Replace with 'current'\n  const p = previous || current\n  const n = next || current // The smoothing ratio\n  const smoothing = 0.2 // Properties of the opposed-line\n  const o = line(p, n) // If is end-control-point, add PI to the angle to go backward\n  const angle = o.angle + (reverse ? Math.PI : 0)\n  const length = o.length * smoothing // The control point position is relative to the current point\n  const x = current[0] + Math.cos(angle) * length\n  const y = current[1] + Math.sin(angle) * length\n  return [x, y]\n}\n\nexport const bezierCommand: CommandFn = (point, i, a) => {\n  // start control point\n  const [cpsX, cpsY] = controlPoint(a[i - 1], a[i - 2], point) // end control point\n  const [cpeX, cpeY] = controlPoint(point, a[i - 1], a[i + 1], true)\n  const coordinates = (x: number, y: number) =>\n    [x.toFixed(FLOAT_DECIMALS), y.toFixed(FLOAT_DECIMALS)].join(',')\n  return [\n    'C',\n    coordinates(cpsX, cpsY),\n    coordinates(cpeX, cpeY),\n    coordinates(point[0], point[1])\n  ].join(' ')\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/lib/versions.ts",
    "content": "import 'server-only'\nimport { z } from 'zod'\n\nconst versionsData = `\n{\"date\":\"2025-09-01\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.1.0\":2,\"1.8.0-beta.8\":1,\"1.5.0\":11,\"1.8.1\":3,\"1.14.0\":90,\"1.9.0-beta.1\":1,\"1.9.2\":77,\"1.10.1\":6,\"1.13.2\":57,\"1.19.3\":3530,\"1.8.0\":1,\"1.7.3\":1991,\"1.8.1-beta.1\":1,\"1.8.4\":9,\"1.17.3\":1,\"1.14.0-beta.5\":1,\"1.7.1\":7,\"1.9.1\":6,\"1.8.2\":11,\"1.13.2-beta.1\":1,\"1.9.0\":1,\"1.19.0\":24,\"1.4.0-beta.5\":2,\"1.8.0-beta.15\":1,\"1.17.2\":251,\"1.7.2\":2988,\"1.12.2\":21,\"1.19.1\":133,\"1.17.7\":57,\"1.8.0-beta.5\":3,\"1.10.2\":2,\"1.8.0-beta.12\":1,\"1.17.0\":361,\"1.12.0\":20,\"1.15.1\":1,\"1.9.0-beta.2\":1,\"1.15.3\":6,\"1.16.1\":16,\"1.16.0\":58,\"1.17.8\":3479,\"1.17.3-beta.1\":1,\"1.17.1\":6771,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.19.2\":1,\"1.13.0\":727,\"1.7.0\":12,\"1.12.0-beta.1\":1,\"1.13.1\":97,\"1.3.0\":57,\"1.10.0\":10,\"1.20.0\":8059,\"1.17.6\":12,\"1.15.4\":49,\"1.17.0-beta.1\":1,\"1.8.3-beta.1\":1,\"1.10.2-beta.1\":1,\"1.8.0-beta.11\":1,\"1.6.0\":2,\"1.16.1-beta.1\":1,\"1.17.4\":618,\"1.2.1\":36,\"1.8.0-beta.4\":1,\"1.10.0-beta.1\":1,\"1.15.2\":83,\"1.14.0-beta.3\":1,\"1.16.0-beta.1\":1,\"1.8.0-beta.1\":1,\"1.13.1-beta.1\":1,\"1.11.0\":15,\"1.8.0-beta.13\":1}}\n{\"date\":\"2025-09-01\",\"package\":\"nuqs\",\"downloads\":{\"2.1.2\":4020,\"1.17.6\":1355,\"2.4.0-beta.4\":1,\"1.19.0\":677,\"2.5.2\":18932,\"1.15.4\":1169,\"1.17.2-beta.1\":12,\"2.5.0-beta.1\":81,\"2.3.0\":5532,\"2.5.0-beta.5\":131,\"1.16.0\":73,\"2.2.3\":14748,\"1.19.2\":613,\"1.19.0-beta.1\":1,\"2.3.2-beta.2\":1,\"2.5.0\":31099,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.0.2\":12,\"1.15.0\":3,\"2.0.4\":1457,\"2.1.0\":475,\"2.2.0\":548,\"2.5.0-beta.6\":151,\"2.3.1\":14953,\"1.18.0\":221,\"1.16.0-beta.1\":1,\"2.5.1\":106500,\"2.4.2-beta.3\":1,\"2.4.0\":11766,\"1.14.1\":466,\"1.19.3\":1854,\"2.5.0-beta.7\":53,\"1.14.0\":36,\"1.16.1\":122,\"1.15.2\":525,\"2.1.1\":3572,\"1.17.0\":1032,\"2.4.2\":8456,\"1.20.0\":63125,\"2.5.0-beta.3\":53,\"1.17.4\":6407,\"2.3.2\":8414,\"2.0.3\":43,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.2.1\":3828,\"1.17.5\":867,\"1.19.1\":8662,\"1.17.3\":1,\"2.5.0-beta.4\":11,\"1.17.7\":300,\"2.6.0-beta.1\":44,\"2.4.3\":410690,\"1.17.8\":3955,\"2.4.2-beta.2\":1,\"2.3.1-beta.3\":1,\"1.15.3\":1209,\"2.3.2-beta.1\":1,\"2.5.0-beta.2\":255,\"2.0.0\":66,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.2.2\":1550,\"2.4.1\":43803,\"1.17.2\":273,\"2.0.0-beta.1\":168,\"2.4.0-beta.2\":42,\"1.17.1\":7108,\"1.15.1\":16,\"2.0.1\":102}}\n{\"date\":\"2025-09-02\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.11.0\":13,\"1.13.1\":94,\"1.17.3\":1,\"1.16.1-beta.1\":1,\"1.17.8\":3463,\"1.9.2\":76,\"1.19.3\":3004,\"1.8.2\":12,\"1.7.1\":7,\"1.8.0-beta.13\":1,\"1.13.1-beta.1\":1,\"1.7.2\":3137,\"1.15.2\":90,\"1.15.4\":47,\"1.17.0-beta.1\":1,\"1.17.6\":14,\"1.17.4\":652,\"1.16.0\":59,\"1.16.1\":17,\"1.9.0-beta.2\":1,\"1.8.4\":9,\"1.12.2\":38,\"1.17.2\":275,\"1.10.0\":10,\"1.19.0\":21,\"1.9.0\":1,\"1.13.2\":65,\"1.7.3\":2119,\"1.8.1\":8,\"1.17.0\":290,\"1.12.0\":31,\"1.10.1\":2,\"1.19.1\":155,\"1.17.7\":56,\"1.20.0\":7848,\"1.14.0\":83,\"1.4.0-beta.5\":2,\"1.9.0-beta.1\":1,\"1.17.1\":6131,\"1.8.0-beta.12\":1,\"1.10.2\":1,\"1.8.0\":1,\"1.14.0-beta.5\":1,\"1.19.2\":1,\"1.15.3\":6,\"1.10.0-beta.1\":1,\"1.2.1\":33,\"1.8.3-beta.1\":1,\"1.15.1\":1,\"1.8.0-beta.4\":1,\"1.10.2-beta.1\":1,\"1.17.3-beta.1\":1,\"1.8.0-beta.11\":1,\"1.6.0\":2,\"1.8.0-beta.5\":2,\"1.8.1-beta.1\":1,\"1.3.0\":57,\"1.14.0-beta.3\":1,\"1.16.0-beta.1\":1,\"1.9.1\":6,\"1.17.5\":2,\"1.1.0\":2,\"1.13.2-beta.1\":1,\"1.5.0\":7,\"1.8.0-beta.8\":1,\"1.13.0\":778,\"1.7.0\":16,\"1.12.0-beta.1\":1,\"1.8.0-beta.15\":1}}\n{\"date\":\"2025-09-02\",\"package\":\"nuqs\",\"downloads\":{\"2.2.2\":1405,\"2.3.3-beta.1\":1,\"2.2.3\":13947,\"1.19.2\":561,\"1.19.0-beta.1\":2,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"2.4.0\":11429,\"1.16.0-beta.1\":2,\"2.5.0-beta.6\":166,\"1.18.0\":280,\"2.0.4\":1281,\"1.15.2\":581,\"2.5.0-beta.2\":253,\"2.3.2-beta.1\":2,\"1.17.2\":234,\"2.0.1\":112,\"2.1.1\":3765,\"1.17.0\":838,\"1.19.1\":8673,\"2.0.2\":18,\"1.15.0\":3,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.19.3\":1865,\"2.3.2-beta.4\":1,\"2.6.0-beta.1\":52,\"1.14.0-beta.4\":1,\"2.0.0\":70,\"2.4.2-beta.3\":2,\"2.5.1\":104610,\"1.14.0\":49,\"2.2.1\":3551,\"1.16.1\":119,\"1.16.1-beta.1\":1,\"2.5.0-beta.3\":54,\"1.17.4\":6168,\"2.4.0-beta.1\":1,\"2.4.0-beta.2\":29,\"2.3.1\":14541,\"2.4.2-beta.1\":1,\"2.3.2-beta.3\":1,\"2.4.3\":388841,\"2.4.0-beta.6\":1,\"2.0.0-beta.1\":144,\"2.2.0\":583,\"2.5.0\":18877,\"1.17.3\":2,\"1.14.1\":467,\"2.1.2\":4286,\"1.17.6\":1321,\"2.0.3\":50,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"1.17.1\":6978,\"1.17.2-beta.1\":12,\"1.17.0-beta.1\":1,\"2.5.0-beta.1\":69,\"2.1.0\":394,\"2.3.0-beta.2\":1,\"2.3.2-beta.2\":2,\"1.20.0\":62260,\"2.4.2\":7836,\"2.0.0-snapshot.2024-05-19.17e1fabc\":2,\"2.4.0-beta.4\":2,\"1.19.0\":641,\"1.15.4\":1234,\"1.17.5\":1013,\"0.0.0-snapshot.2025-01-13.16358634\":2,\"2.4.1\":42839,\"1.17.8\":3819,\"2.4.2-beta.2\":2,\"2.3.1-beta.1\":1,\"2.3.2\":8083,\"2.5.0-beta.5\":93,\"2.5.0-beta.4\":11,\"1.17.7\":297,\"2.3.1-beta.3\":2,\"1.15.3\":1198,\"2.5.0-beta.7\":58,\"1.15.1\":13,\"1.17.3-beta.1\":1,\"1.16.0\":64,\"1.14.0-beta.5\":1,\"2.6.0-beta.2\":42,\"2.3.0-beta.1\":1,\"1.14.1-beta.1\":1,\"2.3.1-beta.2\":1,\"0.0.0-reserved\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.4.0-beta.5\":1,\"2.4.0-beta.3\":1,\"2.3.0\":5311,\"2.5.2\":37217,\"0.0.0-snapshot.2024-12-19.153ba041\":1}}\n{\"date\":\"2025-09-03\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.8.0-beta.2\":1,\"1.17.4\":713,\"1.8.3-beta.1\":2,\"1.8.1-beta.1\":1,\"1.16.1\":17,\"1.16.0\":53,\"1.15.3\":2,\"1.14.0\":66,\"1.4.0-beta.5\":3,\"1.9.0-beta.1\":2,\"1.8.0-beta.10\":1,\"1.0.1\":1,\"1.17.3\":2,\"1.13.1\":87,\"1.9.1\":8,\"1.17.5\":5,\"1.4.0-beta.1\":1,\"1.17.3-beta.1\":1,\"1.8.0-beta.3\":1,\"1.9.0\":1,\"1.11.0-beta.1\":1,\"1.6.1-beta.1\":1,\"1.7.1-beta.2\":1,\"1.13.0\":869,\"1.8.0-beta.15\":1,\"1.17.1\":6003,\"1.14.0-beta.4\":1,\"1.3.0-beta.1\":1,\"1.19.0-beta.1\":1,\"1.16.1-beta.1\":1,\"1.7.0\":15,\"1.12.0-beta.1\":1,\"1.17.2-beta.1\":1,\"1.4.0-beta.3\":1,\"1.7.0-beta.1\":1,\"1.15.0\":1,\"1.8.0-beta.5\":1,\"1.19.2\":1,\"1.14.0-beta.5\":1,\"1.14.0-beta.1\":1,\"1.8.0-beta.4\":1,\"1.13.2\":86,\"1.14.1\":1,\"1.10.1\":2,\"1.14.1-beta.1\":1,\"1.13.0-beta.1\":1,\"1.19.0\":21,\"1.18.0\":1,\"1.10.1-beta.1\":1,\"1.2.0\":1,\"1.3.0-beta.2\":1,\"1.9.2\":52,\"1.1.0\":2,\"1.17.0\":301,\"1.12.0\":36,\"1.10.2-beta.1\":1,\"1.13.2-beta.1\":2,\"1.0.0\":1,\"1.8.0-beta.13\":1,\"1.11.0\":14,\"1.8.5-beta.1\":1,\"1.4.0\":1,\"1.9.0-beta.2\":2,\"1.19.1\":128,\"1.17.7\":57,\"1.8.0-beta.1\":1,\"1.8.1\":9,\"1.13.1-beta.1\":1,\"1.5.0-beta.1\":1,\"1.8.3\":1,\"1.15.1\":2,\"1.8.0-beta.6\":1,\"1.2.1\":1,\"1.16.0-beta.1\":1,\"1.14.0-beta.3\":1,\"1.10.0-beta.1\":1,\"1.17.2\":251,\"1.5.0\":9,\"1.17.6\":15,\"1.17.0-beta.1\":1,\"1.15.2\":82,\"1.8.2-beta.1\":1,\"1.7.3\":2139,\"1.6.0-beta.1\":1,\"1.6.0\":3,\"1.8.0-beta.11\":2,\"1.8.0-beta.8\":2,\"1.12.2\":39,\"1.8.0-beta.7\":1,\"1.8.0-beta.14\":1,\"1.20.0\":7786,\"1.3.0-beta.3\":1,\"1.8.4\":20,\"1.4.0-beta.4\":1,\"1.7.2\":3192,\"1.7.1\":6,\"1.8.2\":8,\"1.7.1-beta.1\":1,\"1.12.1\":1,\"1.17.8\":3365,\"1.15.4\":53,\"1.4.0-beta.2\":1,\"1.8.0-beta.9\":1,\"1.3.0\":43,\"1.19.3\":2491,\"1.8.0\":2,\"1.10.0\":12,\"1.8.0-beta.12\":1,\"1.10.2\":1,\"1.14.0-beta.2\":1}}\n{\"date\":\"2025-09-03\",\"package\":\"nuqs\",\"downloads\":{\"2.1.2\":4234,\"1.17.6\":1298,\"2.0.4\":1472,\"2.1.1\":4096,\"1.17.0\":784,\"2.3.0-beta.1\":1,\"2.3.0\":5443,\"2.3.1\":15081,\"0.0.0-reserved\":1,\"2.5.0-beta.4\":12,\"1.17.7\":284,\"2.4.1\":41743,\"2.5.0-beta.7\":67,\"1.15.3\":1184,\"1.18.0\":410,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.4.2-beta.2\":2,\"1.17.8\":3916,\"2.3.1-beta.3\":2,\"1.15.1\":9,\"2.0.3\":45,\"1.17.1\":6736,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"0.0.0-snapshot.2025-01-13.16358634\":2,\"2.3.2\":7789,\"1.14.1-beta.1\":1,\"1.17.3\":2,\"1.15.2\":553,\"2.3.2-beta.2\":2,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.2.2\":1337,\"1.19.3\":1969,\"2.3.2-beta.4\":1,\"2.4.0\":11366,\"2.4.0-beta.4\":2,\"1.19.0\":598,\"2.3.3-beta.1\":1,\"2.2.1\":3600,\"2.3.1-beta.2\":1,\"2.0.1\":137,\"1.16.0\":69,\"1.17.4\":5986,\"2.4.0-beta.2\":23,\"1.14.0\":58,\"2.6.0-beta.2\":69,\"2.4.0-beta.3\":1,\"2.5.0-beta.1\":75,\"1.17.2-beta.1\":7,\"1.17.0-beta.1\":1,\"2.3.2-beta.1\":2,\"2.0.0-snapshot.2024-05-19.17e1fabc\":2,\"2.4.0-beta.5\":1,\"2.4.3\":374294,\"1.14.1\":418,\"1.14.0-beta.4\":1,\"2.4.2\":7955,\"1.20.0\":63528,\"2.0.0-beta.1\":140,\"2.4.0-beta.6\":1,\"1.17.5\":1028,\"2.5.2\":57812,\"1.15.4\":1386,\"2.2.0\":582,\"2.6.0-beta.1\":55,\"2.4.2-beta.3\":2,\"2.5.1\":93420,\"1.16.1\":170,\"2.5.0-beta.6\":176,\"1.16.0-beta.1\":2,\"2.5.0-beta.5\":73,\"2.3.1-beta.1\":1,\"2.5.0\":16547,\"1.17.2\":211,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.5.0-beta.2\":210,\"1.16.1-beta.1\":1,\"2.0.0\":90,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.0.2\":23,\"1.19.1\":8920,\"1.15.0\":3,\"2.2.3\":13131,\"1.19.2\":603,\"2.0.0-snapshot.2024-06-27.47926d24\":3,\"1.19.0-beta.1\":2,\"2.3.0-beta.2\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"2.5.0-beta.3\":59,\"2.4.0-beta.1\":1,\"2.1.0\":403}}\n{\"date\":\"2025-09-04\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.13.1\":90,\"1.8.1-beta.1\":1,\"1.1.0\":1,\"1.7.3\":2213,\"1.17.0\":280,\"1.16.1\":19,\"1.4.0-beta.5\":1,\"1.9.0-beta.1\":1,\"1.14.0\":67,\"1.2.1\":1,\"1.8.0-beta.5\":1,\"1.17.7\":57,\"1.19.1\":125,\"1.6.0\":3,\"1.8.0-beta.11\":1,\"1.8.2-beta.1\":1,\"1.19.2\":1,\"1.19.0\":21,\"1.17.2\":319,\"1.10.0-beta.1\":1,\"1.17.2-beta.1\":1,\"1.14.1\":1,\"1.10.0\":12,\"1.7.1\":5,\"1.14.0-beta.5\":1,\"1.8.2\":16,\"1.9.1\":9,\"1.17.5\":5,\"1.4.0-beta.2\":1,\"1.8.0-beta.9\":1,\"1.20.0\":8105,\"1.8.0-beta.13\":1,\"1.11.0\":13,\"1.12.0-beta.1\":1,\"1.8.0-beta.12\":1,\"1.13.0-beta.1\":1,\"1.7.2\":3191,\"1.15.3\":2,\"1.8.4\":23,\"1.16.0\":56,\"1.6.0-beta.1\":1,\"1.12.2\":37,\"1.8.0-beta.7\":1,\"1.17.4\":696,\"1.16.1-beta.1\":1,\"1.8.0-beta.2\":1,\"1.5.0\":2,\"1.8.1\":8,\"1.18.0\":1,\"1.10.1-beta.1\":1,\"1.15.2\":78,\"1.13.0\":999,\"1.7.1-beta.2\":1,\"1.17.1\":5574,\"1.14.0-beta.4\":1,\"1.8.0-beta.4\":1,\"1.12.0\":36,\"1.10.2-beta.1\":1,\"1.3.0\":26,\"1.14.0-beta.3\":1,\"1.16.0-beta.1\":1,\"1.8.0-beta.1\":1,\"1.17.3-beta.1\":1,\"1.4.0-beta.1\":1,\"1.17.8\":3291,\"1.4.0\":1,\"1.8.5-beta.1\":1,\"1.13.1-beta.1\":1,\"1.5.0-beta.1\":1,\"1.8.3\":1,\"1.17.6\":14,\"1.15.4\":82,\"1.17.0-beta.1\":1,\"1.3.0-beta.3\":1,\"1.15.0\":1,\"1.3.0-beta.2\":1,\"1.4.0-beta.4\":1,\"1.13.2-beta.1\":1,\"1.8.0-beta.14\":1,\"1.7.0\":13,\"1.10.1\":2,\"1.14.1-beta.1\":1,\"1.9.0\":1,\"1.8.0-beta.10\":1,\"1.2.0\":1,\"1.8.0-beta.8\":1,\"1.0.1\":1,\"1.10.2\":1,\"1.9.2\":53,\"1.0.0\":1,\"1.8.3-beta.1\":1,\"1.12.1\":1,\"1.13.2\":98,\"1.3.0-beta.1\":1,\"1.7.1-beta.1\":1,\"1.19.3\":2954,\"1.6.1-beta.1\":1,\"1.7.0-beta.1\":1,\"1.8.0-beta.15\":1,\"1.15.1\":1,\"1.8.0-beta.6\":1,\"1.9.0-beta.2\":1,\"1.11.0-beta.1\":1,\"1.19.0-beta.1\":1,\"1.14.0-beta.2\":1,\"1.8.0-beta.3\":1,\"1.17.3\":1,\"1.14.0-beta.1\":1,\"1.4.0-beta.3\":1,\"1.8.0\":1}}\n{\"date\":\"2025-09-04\",\"package\":\"nuqs\",\"downloads\":{\"2.0.0-snapshot.2024-06-27.47926d24\":3,\"1.15.2\":502,\"2.5.1\":76736,\"2.4.0\":11587,\"2.0.0-snapshot.2024-05-19.17e1fabc\":2,\"2.5.2\":80103,\"1.15.4\":1343,\"2.2.0\":524,\"2.2.1\":3606,\"1.17.5\":907,\"1.17.2-beta.1\":8,\"2.3.0\":5307,\"1.16.1\":283,\"2.5.0-beta.3\":66,\"1.17.4\":5815,\"1.20.0\":64777,\"2.4.2\":7444,\"2.0.3\":50,\"2.4.2-beta.2\":2,\"1.14.0\":59,\"2.5.0-beta.4\":10,\"1.16.0-beta.1\":2,\"0.0.0-reserved\":1,\"2.5.0-beta.6\":167,\"2.3.1\":16177,\"2.5.0-beta.2\":175,\"1.14.1\":364,\"2.5.0-beta.7\":64,\"2.0.0\":100,\"2.4.3\":353161,\"2.3.2-beta.3\":1,\"1.19.3\":2142,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.0.2\":23,\"2.1.0\":362,\"1.17.2\":217,\"1.19.0-beta.1\":2,\"2.2.3\":12365,\"1.19.2\":605,\"2.4.1\":41912,\"2.0.1\":136,\"2.3.1-beta.1\":1,\"2.5.0-beta.5\":63,\"2.4.0-beta.3\":1,\"2.3.0-beta.2\":1,\"1.17.3\":2,\"2.5.0\":15770,\"2.4.0-beta.4\":2,\"2.6.0-beta.1\":61,\"2.5.0-beta.1\":51,\"2.3.1-beta.3\":2,\"1.15.3\":1155,\"2.3.0-beta.1\":1,\"1.18.0\":492,\"2.0.0-beta.1\":135,\"2.4.0-beta.6\":1,\"1.19.1\":9037,\"1.17.8\":4046,\"2.3.1-beta.2\":1,\"2.4.0-beta.1\":1,\"1.14.0-beta.4\":1,\"1.17.1\":6231,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.4.2-beta.1\":1,\"2.3.2-beta.1\":2,\"1.16.1-beta.1\":1,\"2.0.4\":1542,\"2.1.2\":4659,\"1.17.6\":1109,\"2.3.2-beta.2\":2,\"2.4.2-beta.3\":2,\"2.2.2\":1204,\"1.19.0\":622,\"1.16.0\":76,\"1.17.3-beta.1\":1,\"2.3.2\":7502,\"1.17.0-beta.1\":1,\"1.17.0\":793,\"2.1.1\":4331,\"2.3.2-beta.4\":1,\"0.0.0-snapshot.2025-01-13.16358634\":2,\"1.14.1-beta.1\":1,\"2.4.0-beta.2\":16,\"1.17.7\":286,\"2.6.0-beta.2\":123,\"2.4.0-beta.5\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.15.0\":3,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.15.1\":9,\"2.3.3-beta.1\":1,\"1.14.0-beta.5\":1}}\n{\"date\":\"2025-09-05\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.8.4\":23,\"1.16.1\":17,\"1.16.0\":64,\"1.7.2\":3260,\"1.9.1\":9,\"1.19.1\":119,\"1.17.7\":69,\"1.12.2\":36,\"1.15.1\":1,\"1.8.0-beta.6\":1,\"1.17.0\":323,\"1.10.2-beta.1\":1,\"1.17.3-beta.1\":1,\"1.12.0\":33,\"1.4.0-beta.1\":1,\"1.11.0\":13,\"1.7.3\":2283,\"1.1.0\":1,\"1.3.0-beta.2\":1,\"1.8.0-beta.14\":1,\"1.13.2-beta.1\":1,\"1.20.0\":7819,\"1.12.0-beta.1\":1,\"1.13.0\":1015,\"1.7.0\":14,\"1.7.1-beta.2\":1,\"1.14.0-beta.4\":1,\"1.19.0\":21,\"1.18.0\":1,\"1.10.1-beta.1\":1,\"1.9.0\":1,\"1.7.1\":5,\"1.19.2\":1,\"1.14.0-beta.5\":1,\"1.8.2\":17,\"1.13.2\":96,\"1.12.1\":1,\"1.8.0-beta.2\":1,\"1.4.0\":1,\"1.15.3\":1,\"1.4.0-beta.4\":1,\"1.9.0-beta.2\":1,\"1.8.3-beta.1\":1,\"1.8.0-beta.7\":1,\"1.10.0\":12,\"1.17.6\":13,\"1.4.0-beta.2\":1,\"1.8.0-beta.9\":1,\"1.15.4\":89,\"1.8.0-beta.5\":1,\"1.17.5\":5,\"1.11.0-beta.1\":1,\"1.14.0-beta.2\":1,\"1.8.0-beta.4\":1,\"1.8.1-beta.1\":1,\"1.8.0-beta.10\":1,\"1.0.1\":1,\"1.14.0\":68,\"1.4.0-beta.5\":1,\"1.9.0-beta.1\":1,\"1.4.0-beta.3\":1,\"1.8.5-beta.1\":1,\"1.8.0-beta.13\":1,\"1.13.1-beta.1\":1,\"1.8.3\":1,\"1.15.2\":64,\"1.17.2\":476,\"1.5.0-beta.1\":1,\"1.17.2-beta.1\":1,\"1.8.0-beta.15\":1,\"1.6.0-beta.1\":1,\"1.0.0\":1,\"1.10.1\":1,\"1.2.0\":1,\"1.8.0-beta.8\":1,\"1.17.1\":4914,\"1.6.1-beta.1\":1,\"1.15.0\":1,\"1.8.1\":9,\"1.5.0\":6,\"1.14.0-beta.1\":1,\"1.8.0-beta.12\":1,\"1.9.2\":50,\"1.14.1\":1,\"1.17.3\":1,\"1.3.0-beta.1\":1,\"1.19.0-beta.1\":1,\"1.16.1-beta.1\":1,\"1.3.0\":8,\"1.14.0-beta.3\":1,\"1.16.0-beta.1\":1,\"1.17.8\":3363,\"1.8.0-beta.3\":1,\"1.8.0-beta.1\":1,\"1.17.0-beta.1\":1,\"1.3.0-beta.3\":1,\"1.8.0-beta.11\":1,\"1.6.0\":1,\"1.7.0-beta.1\":1,\"1.10.0-beta.1\":1,\"1.8.2-beta.1\":1,\"1.19.3\":3931,\"1.8.0\":1,\"1.17.4\":794,\"1.2.1\":1,\"1.7.1-beta.1\":1,\"1.10.2\":1,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.13.1\":86}}\n{\"date\":\"2025-09-05\",\"package\":\"nuqs\",\"downloads\":{\"2.6.0-beta.2\":148,\"1.14.0\":59,\"2.5.1\":59130,\"1.19.3\":2198,\"2.6.0-beta.1\":64,\"2.1.0\":316,\"1.17.4\":5630,\"2.4.0-beta.1\":1,\"2.4.0-beta.2\":5,\"2.5.0-beta.3\":65,\"2.3.1\":16152,\"1.19.1\":9385,\"1.15.0\":3,\"2.2.1\":3543,\"1.16.1-beta.1\":1,\"2.2.0\":487,\"2.2.2\":1316,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.4.0-beta.3\":1,\"2.0.1\":127,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.4.0-beta.4\":1,\"2.5.2\":110888,\"1.15.4\":1351,\"1.17.2-beta.1\":5,\"1.17.0-beta.1\":1,\"2.3.0\":5176,\"2.5.0-beta.1\":45,\"2.5.0-beta.5\":64,\"2.3.1-beta.1\":1,\"1.15.3\":1180,\"2.3.1-beta.3\":1,\"1.15.1\":1,\"2.5.0-beta.7\":64,\"2.4.3\":343913,\"2.4.2-beta.1\":1,\"2.5.0-beta.4\":9,\"2.3.2-beta.4\":1,\"2.4.1\":42051,\"2.4.2-beta.2\":1,\"1.17.8\":4251,\"1.14.1-beta.1\":1,\"2.4.2-beta.3\":1,\"2.5.0\":15305,\"1.17.2\":211,\"1.16.0-beta.1\":2,\"1.18.0\":674,\"2.5.0-beta.6\":158,\"2.3.2-beta.1\":2,\"2.5.0-beta.2\":188,\"2.0.0\":94,\"1.14.0-beta.4\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.1.2\":4655,\"1.17.6\":1357,\"1.17.1\":5363,\"2.0.3\":46,\"0.0.0-reserved\":1,\"2.0.2\":20,\"2.3.1-beta.2\":1,\"2.0.0-beta.1\":165,\"1.17.5\":885,\"0.0.0-snapshot.2025-01-13.16358634\":2,\"2.3.2\":7651,\"1.16.1\":323,\"1.14.1\":333,\"2.3.0-beta.1\":1,\"2.3.3-beta.1\":1,\"2.3.2-beta.2\":1,\"1.19.0\":660,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.3.2-beta.3\":1,\"2.4.0-beta.5\":1,\"1.17.3\":2,\"1.17.7\":277,\"2.4.0\":11772,\"2.0.4\":1517,\"1.15.2\":507,\"2.2.3\":12262,\"1.19.2\":560,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"1.17.3-beta.1\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.6.0-beta.3\":47,\"2.4.0-beta.6\":1,\"1.20.0\":65055,\"2.4.2\":6775,\"2.1.1\":4430,\"1.17.0\":779,\"1.16.0\":69,\"1.14.0-beta.5\":1,\"2.3.0-beta.2\":1,\"1.19.0-beta.1\":1}}\n{\"date\":\"2025-09-06\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.17.4\":734,\"1.8.0-beta.2\":2,\"1.17.8\":3399,\"1.16.1-beta.1\":1,\"1.8.2-beta.1\":1,\"1.15.2\":67,\"1.9.1\":8,\"1.8.2\":19,\"1.14.0-beta.5\":1,\"1.19.2\":1,\"1.7.1\":4,\"1.17.5\":5,\"1.17.2\":552,\"1.10.0-beta.1\":1,\"1.6.0\":1,\"1.8.0-beta.13\":1,\"1.8.0-beta.10\":1,\"1.8.0-beta.8\":1,\"1.13.1\":75,\"1.2.0\":1,\"1.3.0-beta.2\":1,\"1.8.1-beta.1\":1,\"1.7.3\":2357,\"1.6.0-beta.1\":1,\"1.8.0\":1,\"1.19.3\":4194,\"1.16.1\":16,\"1.10.1\":22,\"1.9.2\":46,\"1.13.2\":110,\"1.14.1\":1,\"1.8.0-beta.12\":1,\"1.10.0\":14,\"1.19.0\":22,\"1.18.0\":1,\"1.10.1-beta.1\":1,\"1.12.0\":37,\"1.17.0\":298,\"1.15.4\":114,\"1.20.0\":8143,\"1.4.0-beta.2\":1,\"1.8.0-beta.9\":1,\"1.7.2\":3080,\"1.13.0\":1011,\"1.8.0-beta.7\":1,\"1.12.2\":36,\"1.15.0\":1,\"1.12.0-beta.1\":1,\"1.17.2-beta.1\":1,\"1.19.1\":123,\"1.17.7\":69,\"1.8.4\":29,\"1.17.1\":5410,\"1.17.3\":1,\"1.8.0-beta.14\":1,\"1.8.0-beta.11\":1,\"1.11.0-beta.1\":1,\"1.3.0-beta.1\":1,\"1.19.0-beta.1\":1,\"1.4.0-beta.1\":1,\"1.8.0-beta.3\":1,\"1.17.3-beta.1\":1,\"1.7.1-beta.1\":1,\"1.2.1\":1,\"1.0.1\":1,\"1.14.0-beta.1\":1,\"1.15.3\":1,\"1.16.0\":60,\"1.14.0\":60,\"1.4.0-beta.5\":1,\"1.9.0-beta.1\":1,\"1.11.0\":1,\"1.13.2-beta.1\":1,\"1.8.5-beta.1\":1,\"1.5.0-beta.1\":1,\"1.8.3\":1,\"1.10.2\":1,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.12.1\":2,\"1.8.3-beta.1\":1,\"1.8.1\":7,\"1.5.0\":6,\"1.0.0\":1,\"1.1.0\":1,\"1.9.0\":1,\"1.7.1-beta.2\":1,\"1.13.1-beta.1\":1,\"1.3.0\":6,\"1.14.0-beta.3\":1,\"1.16.0-beta.1\":1,\"1.6.1-beta.1\":1,\"1.9.0-beta.2\":1,\"1.4.0\":1,\"1.4.0-beta.4\":1,\"1.8.0-beta.1\":1,\"1.3.0-beta.3\":1,\"1.17.6\":11,\"1.17.0-beta.1\":1,\"1.10.2-beta.1\":1,\"1.8.0-beta.5\":1,\"1.8.0-beta.4\":1,\"1.15.1\":1,\"1.8.0-beta.6\":1,\"1.7.0\":13,\"1.4.0-beta.3\":1,\"1.8.0-beta.15\":1,\"1.14.0-beta.4\":1,\"1.14.0-beta.2\":1,\"1.7.0-beta.1\":1}}\n{\"date\":\"2025-09-06\",\"package\":\"nuqs\",\"downloads\":{\"2.5.0-beta.6\":167,\"2.3.1\":15874,\"0.0.0-reserved\":1,\"1.18.0\":683,\"1.16.0-beta.1\":2,\"2.6.0\":5994,\"1.17.5\":825,\"1.14.1\":332,\"2.3.0-beta.1\":1,\"2.2.0\":475,\"2.1.2\":4630,\"1.17.6\":1306,\"1.16.1\":363,\"2.5.2\":134317,\"1.15.4\":1326,\"1.20.0\":60159,\"2.4.2\":7079,\"2.5.0-beta.1\":30,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":5,\"1.17.0\":675,\"2.1.1\":4477,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"2.2.3\":12107,\"1.19.2\":523,\"2.4.0\":12113,\"1.15.2\":535,\"2.3.2\":7706,\"2.5.0-beta.5\":70,\"1.16.0\":72,\"2.5.0\":14813,\"2.5.0-beta.2\":187,\"2.3.2-beta.1\":2,\"1.17.4\":5884,\"2.4.0-beta.2\":10,\"2.4.0-beta.1\":1,\"2.5.0-beta.3\":59,\"2.0.0\":81,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.19.3\":2314,\"2.3.2-beta.4\":1,\"1.17.2\":275,\"2.2.1\":3669,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.16.1-beta.1\":1,\"1.15.0\":2,\"1.19.1\":9596,\"2.0.2\":17,\"1.15.3\":1176,\"1.14.1-beta.1\":1,\"2.4.3\":341005,\"1.17.1\":5575,\"2.0.3\":45,\"2.1.0\":258,\"1.14.0\":57,\"2.6.0-beta.2\":167,\"2.5.1\":38389,\"2.0.4\":1513,\"1.17.8\":4507,\"2.4.1\":43032,\"2.4.2-beta.2\":1,\"2.3.0\":5160,\"1.17.7\":288,\"2.5.0-beta.4\":6,\"2.2.2\":1499,\"2.3.0-beta.2\":1,\"2.0.0-beta.1\":154,\"2.4.0-beta.6\":1,\"2.0.1\":126,\"2.4.0-beta.3\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"2.3.2-beta.2\":1,\"1.17.3\":1,\"1.19.0-beta.1\":1,\"2.3.3-beta.1\":1,\"2.3.1-beta.1\":1,\"2.4.0-beta.4\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.19.0\":719,\"2.4.2-beta.3\":1,\"1.14.0-beta.4\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.6.0-beta.3\":65,\"2.6.0-beta.1\":67,\"2.3.1-beta.2\":1,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.4.0-beta.5\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.3.1-beta.3\":1,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.5.0-beta.7\":42,\"1.15.1\":1}}\n{\"date\":\"2025-09-07\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.16.1\":28,\"1.9.2\":62,\"1.13.0-beta.1\":2,\"1.14.1-beta.1\":2,\"1.9.0\":2,\"1.10.2\":3,\"1.14.1\":2,\"1.8.0\":2,\"1.12.2\":37,\"1.8.0-beta.7\":2,\"1.13.0\":1010,\"1.19.0\":20,\"1.18.0\":2,\"1.10.1-beta.1\":2,\"1.13.1\":75,\"1.7.2\":3085,\"1.8.4\":30,\"1.9.0-beta.2\":2,\"1.15.4\":122,\"1.17.6\":12,\"1.3.0-beta.3\":2,\"1.4.0-beta.2\":2,\"1.8.0-beta.9\":2,\"1.20.0\":8229,\"1.10.0\":14,\"1.16.0-beta.1\":2,\"1.8.2\":20,\"1.7.1\":5,\"1.9.1\":9,\"1.19.2\":2,\"1.14.0-beta.5\":2,\"1.14.0-beta.1\":2,\"1.17.5\":6,\"1.16.1-beta.1\":2,\"1.3.0-beta.1\":2,\"1.19.0-beta.1\":2,\"1.11.0-beta.1\":2,\"1.16.0\":61,\"1.15.3\":2,\"1.15.2\":68,\"1.4.0-beta.1\":2,\"1.17.3-beta.1\":2,\"1.17.8\":3154,\"1.8.0-beta.3\":2,\"1.8.0-beta.10\":2,\"1.8.0-beta.8\":2,\"1.10.1\":23,\"1.8.0-beta.12\":2,\"1.17.4\":737,\"1.8.0-beta.2\":3,\"1.7.1-beta.1\":2,\"1.9.0-beta.1\":2,\"1.4.0-beta.5\":2,\"1.14.0\":61,\"1.10.0-beta.1\":2,\"1.17.2\":563,\"1.19.3\":4293,\"1.8.3-beta.1\":2,\"1.6.0-beta.1\":2,\"1.8.1-beta.1\":2,\"1.7.3\":2349,\"1.7.1-beta.2\":2,\"1.12.0-beta.1\":2,\"1.7.0\":14,\"1.17.2-beta.1\":2,\"1.8.0-beta.15\":2,\"1.4.0-beta.3\":2,\"1.17.0-beta.1\":2,\"1.4.0-beta.4\":2,\"1.4.0\":2,\"1.17.1\":5554,\"1.14.0-beta.4\":2,\"1.17.7\":70,\"1.19.1\":124,\"1.8.0-beta.5\":2,\"1.6.1-beta.1\":2,\"1.8.0-beta.1\":2,\"1.14.0-beta.2\":2,\"1.3.0\":7,\"1.14.0-beta.3\":2,\"1.15.1\":2,\"1.12.1\":3,\"1.13.2\":115,\"1.3.0-beta.2\":2,\"1.2.0\":2,\"1.8.1\":10,\"1.8.5-beta.1\":2,\"1.11.0\":2,\"1.13.2-beta.1\":2,\"1.8.0-beta.13\":2,\"1.5.0-beta.1\":2,\"1.17.3\":2,\"1.0.1\":2,\"1.15.0\":2,\"1.8.0-beta.11\":2,\"1.6.0\":2,\"1.8.0-beta.14\":2,\"1.8.2-beta.1\":2,\"1.2.1\":2,\"1.7.0-beta.1\":2,\"1.10.2-beta.1\":2,\"1.17.0\":304,\"1.12.0\":38,\"1.8.0-beta.4\":2,\"1.8.0-beta.6\":2,\"1.0.0\":2,\"1.1.0\":2,\"1.8.3\":2,\"1.13.1-beta.1\":2,\"1.5.0\":7}}\n{\"date\":\"2025-09-07\",\"package\":\"nuqs\",\"downloads\":{\"2.3.2-beta.2\":1,\"2.4.0\":12167,\"2.5.0-beta.5\":71,\"2.3.1-beta.1\":1,\"1.16.0\":73,\"2.3.2\":7679,\"1.14.1-beta.1\":1,\"1.15.3\":1206,\"2.3.2-beta.4\":1,\"2.6.0-beta.1\":70,\"1.19.3\":2337,\"1.15.2\":538,\"2.2.3\":11871,\"1.19.2\":500,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"2.5.1\":33022,\"1.14.0\":59,\"2.6.0-beta.2\":171,\"1.17.2\":279,\"2.0.0\":81,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.14.0-beta.4\":1,\"2.5.0-beta.2\":154,\"2.4.2-beta.1\":1,\"2.5.0-beta.6\":149,\"2.3.1\":15908,\"1.18.0\":725,\"2.1.1\":4447,\"1.17.0\":687,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"2.5.2\":129346,\"1.15.4\":1297,\"1.19.0-beta.1\":1,\"2.3.0-beta.2\":1,\"1.16.1\":361,\"2.3.0\":5146,\"1.17.5\":823,\"1.14.1\":268,\"2.5.0-beta.4\":6,\"1.17.7\":270,\"2.2.0\":477,\"2.0.0-beta.1\":144,\"2.2.2\":1514,\"2.1.2\":4543,\"1.17.6\":1199,\"2.4.2-beta.3\":1,\"2.6.0-beta.3\":69,\"1.17.4\":5966,\"2.4.0-beta.2\":10,\"2.4.0-beta.1\":1,\"2.5.0-beta.3\":59,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.16.1-beta.1\":1,\"1.17.8\":4588,\"2.4.1\":42926,\"2.4.2-beta.2\":1,\"2.3.2-beta.1\":1,\"2.1.0\":232,\"2.0.3\":42,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.5.0\":14443,\"1.17.3\":1,\"1.15.0\":2,\"2.0.2\":17,\"1.19.1\":9564,\"2.4.3\":339328,\"2.3.2-beta.3\":1,\"2.4.0-beta.5\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.16.0-beta.1\":1,\"0.0.0-reserved\":1,\"2.0.1\":126,\"2.4.0-beta.3\":1,\"2.0.4\":1545,\"2.5.0-beta.1\":36,\"1.17.2-beta.1\":5,\"1.20.0\":56357,\"2.4.2\":7142,\"2.4.0-beta.6\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"1.15.1\":1,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.3.1-beta.3\":1,\"2.5.0-beta.7\":40,\"2.6.0\":21954,\"1.17.1\":5787,\"2.3.0-beta.1\":1,\"2.3.1-beta.2\":1,\"2.2.1\":3687,\"2.4.0-beta.4\":1,\"1.19.0\":712,\"2.3.3-beta.1\":1,\"1.17.0-beta.1\":1}}\n{\"date\":\"2025-09-08\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.15.0\":2,\"1.8.0-beta.14\":2,\"1.7.0\":14,\"1.17.2-beta.1\":2,\"1.12.0-beta.1\":2,\"1.4.0-beta.3\":2,\"1.17.8\":3008,\"1.8.0-beta.8\":2,\"1.17.7\":70,\"1.19.1\":125,\"1.8.2\":19,\"1.7.1\":5,\"1.8.0-beta.6\":2,\"1.15.1\":2,\"1.7.3\":2324,\"1.6.0-beta.1\":2,\"1.12.0\":38,\"1.17.0\":300,\"1.10.2-beta.1\":2,\"1.14.0\":61,\"1.9.0-beta.1\":2,\"1.4.0-beta.5\":2,\"1.8.0-beta.10\":2,\"1.0.1\":2,\"1.8.1-beta.1\":2,\"1.9.2\":62,\"1.7.0-beta.1\":2,\"1.8.0-beta.11\":2,\"1.6.0\":2,\"1.8.0-beta.4\":2,\"1.16.0-beta.1\":2,\"1.14.0-beta.3\":2,\"1.8.0-beta.5\":2,\"1.3.0-beta.3\":2,\"1.20.0\":8268,\"1.4.0\":2,\"1.9.0-beta.2\":2,\"1.14.1\":2,\"1.13.2\":108,\"1.9.0\":2,\"1.18.0\":2,\"1.10.1-beta.1\":2,\"1.19.0\":20,\"1.16.0\":61,\"1.16.1\":47,\"1.15.3\":3,\"1.13.1\":71,\"1.8.3-beta.1\":2,\"1.10.1\":23,\"1.8.0-beta.2\":3,\"1.17.4\":728,\"1.3.0\":7,\"1.13.0-beta.1\":2,\"1.14.1-beta.1\":2,\"1.2.0\":2,\"1.15.4\":123,\"1.8.0-beta.9\":2,\"1.4.0-beta.2\":2,\"1.1.0\":2,\"1.8.1\":11,\"1.10.0\":5,\"1.13.2-beta.1\":2,\"1.2.1\":2,\"1.19.3\":4343,\"1.8.0\":2,\"1.0.0\":2,\"1.9.1\":7,\"1.17.5\":6,\"1.5.0\":8,\"1.13.0\":1002,\"1.7.1-beta.2\":2,\"1.7.1-beta.1\":2,\"1.5.0-beta.1\":2,\"1.13.1-beta.1\":2,\"1.8.3\":2,\"1.6.1-beta.1\":2,\"1.10.2\":3,\"1.8.0-beta.12\":2,\"1.17.2\":563,\"1.10.0-beta.1\":2,\"1.15.2\":62,\"1.8.2-beta.1\":2,\"1.19.0-beta.1\":2,\"1.16.1-beta.1\":2,\"1.3.0-beta.1\":2,\"1.14.0-beta.5\":2,\"1.19.2\":2,\"1.14.0-beta.1\":2,\"1.14.0-beta.2\":2,\"1.17.6\":6,\"1.17.0-beta.1\":2,\"1.8.0-beta.1\":2,\"1.8.4\":32,\"1.4.0-beta.4\":2,\"1.11.0-beta.1\":2,\"1.8.0-beta.3\":2,\"1.4.0-beta.1\":2,\"1.17.3-beta.1\":2,\"1.17.3\":2,\"1.12.1\":3,\"1.7.2\":3065,\"1.12.2\":39,\"1.8.0-beta.7\":2,\"1.3.0-beta.2\":2,\"1.8.0-beta.13\":2,\"1.8.5-beta.1\":2,\"1.11.0\":2,\"1.17.1\":5638,\"1.14.0-beta.4\":2,\"1.8.0-beta.15\":2}}\n{\"date\":\"2025-09-08\",\"package\":\"nuqs\",\"downloads\":{\"2.5.2\":122254,\"1.15.4\":1239,\"1.17.4\":6005,\"2.4.0-beta.2\":10,\"2.2.1\":3653,\"2.3.1-beta.2\":1,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.4.1\":43124,\"2.5.0-beta.6\":132,\"1.16.0-beta.1\":1,\"2.2.0\":469,\"2.3.0\":4923,\"2.1.2\":4550,\"1.17.6\":1054,\"1.16.0\":78,\"2.4.3\":338911,\"1.17.1\":5836,\"2.0.3\":36,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.17.5\":832,\"1.20.0\":55995,\"2.4.2\":7261,\"2.0.0-beta.1\":144,\"2.4.0-beta.6\":1,\"1.14.0-beta.4\":1,\"1.14.0\":56,\"2.6.0-beta.2\":172,\"1.17.3\":1,\"1.14.1-beta.1\":1,\"2.3.2-beta.2\":1,\"2.1.1\":4416,\"1.17.0\":649,\"2.3.2\":7614,\"1.19.3\":2303,\"2.3.2-beta.4\":1,\"2.5.0-beta.2\":145,\"1.15.2\":453,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":5,\"2.5.0-beta.1\":44,\"2.4.0-beta.4\":1,\"1.19.0\":713,\"2.3.0-beta.1\":1,\"1.17.2\":278,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.4.0\":12199,\"2.4.0-beta.3\":1,\"2.0.0\":79,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.2.2\":1512,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.6.0\":28726,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.16.1\":362,\"1.15.3\":1302,\"2.2.3\":11425,\"1.19.2\":447,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"1.19.0-beta.1\":1,\"2.3.0-beta.2\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"1.14.1\":262,\"2.4.0-beta.5\":1,\"2.4.0-beta.1\":1,\"2.5.0-beta.3\":57,\"2.6.0-beta.3\":76,\"1.16.1-beta.1\":1,\"1.18.0\":741,\"2.1.0\":223,\"2.3.2-beta.1\":1,\"2.4.2-beta.3\":1,\"2.5.1\":31851,\"1.19.1\":9512,\"1.15.0\":2,\"2.0.2\":17,\"2.6.0-beta.1\":27,\"0.0.0-reserved\":1,\"2.3.1\":15843,\"2.0.4\":1534,\"2.5.0\":14332,\"2.5.0-beta.5\":68,\"2.3.1-beta.1\":1,\"2.0.1\":127,\"2.4.2-beta.2\":1,\"1.17.8\":4512,\"2.3.3-beta.1\":1,\"1.17.7\":248,\"2.5.0-beta.4\":6,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.5.0-beta.7\":37}}\n{\"date\":\"2025-09-09\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.19.2\":2,\"1.14.0-beta.5\":2,\"1.14.0-beta.1\":2,\"1.17.4\":772,\"1.8.0-beta.2\":3,\"1.2.1\":2,\"1.16.1-beta.1\":2,\"1.3.0-beta.1\":2,\"1.17.2\":612,\"1.10.0-beta.1\":2,\"1.15.4\":123,\"1.20.0\":8660,\"1.3.0-beta.3\":2,\"1.4.0-beta.2\":2,\"1.8.0-beta.9\":2,\"1.17.6\":4,\"1.17.0-beta.1\":2,\"1.14.1\":2,\"1.10.0\":5,\"1.8.2-beta.1\":2,\"1.15.2\":47,\"1.10.1\":32,\"1.8.0-beta.3\":2,\"1.17.8\":2924,\"1.17.3\":2,\"1.11.0-beta.1\":2,\"1.14.0-beta.2\":2,\"1.8.0-beta.5\":2,\"1.8.0-beta.11\":2,\"1.6.0\":2,\"1.9.1\":7,\"1.8.1-beta.1\":2,\"1.15.1\":2,\"1.8.0-beta.6\":2,\"1.6.0-beta.1\":2,\"1.8.0-beta.8\":2,\"1.7.0-beta.1\":2,\"1.9.2\":68,\"1.10.2\":4,\"1.14.0\":54,\"1.9.0-beta.1\":2,\"1.4.0-beta.5\":2,\"1.12.0\":32,\"1.17.0\":305,\"1.3.0\":7,\"1.16.0-beta.1\":2,\"1.14.0-beta.3\":2,\"1.8.0-beta.1\":2,\"1.10.2-beta.1\":2,\"1.4.0-beta.1\":2,\"1.17.3-beta.1\":2,\"1.19.0-beta.1\":2,\"1.7.1-beta.1\":2,\"1.19.0\":25,\"1.13.0-beta.1\":2,\"1.14.1-beta.1\":2,\"1.9.0\":2,\"1.10.1-beta.1\":2,\"1.18.0\":2,\"1.8.3-beta.1\":2,\"1.12.2\":21,\"1.8.0-beta.7\":2,\"1.8.4\":30,\"1.16.1\":48,\"1.16.0\":61,\"1.4.0-beta.4\":2,\"1.9.0-beta.2\":2,\"1.4.0\":2,\"1.7.2\":2946,\"1.13.1\":57,\"1.8.2\":17,\"1.7.1\":5,\"1.17.5\":4,\"1.12.1\":3,\"1.13.2\":103,\"1.7.3\":2421,\"1.8.0-beta.13\":2,\"1.8.5-beta.1\":2,\"1.11.0\":2,\"1.5.0-beta.1\":2,\"1.13.1-beta.1\":2,\"1.8.0-beta.12\":2,\"1.19.1\":118,\"1.17.7\":70,\"1.3.0-beta.2\":2,\"1.2.0\":2,\"1.8.0-beta.4\":2,\"1.8.0-beta.10\":2,\"1.0.1\":2,\"1.5.0\":8,\"1.8.0\":2,\"1.19.3\":4626,\"1.15.3\":3,\"1.17.1\":6594,\"1.6.1-beta.1\":2,\"1.0.0\":2,\"1.1.0\":2,\"1.17.2-beta.1\":2,\"1.12.0-beta.1\":2,\"1.4.0-beta.3\":2,\"1.8.0-beta.15\":2,\"1.7.0\":10,\"1.13.0\":954,\"1.8.0-beta.14\":2,\"1.8.3\":2,\"1.15.0\":2,\"1.13.2-beta.1\":2,\"1.8.1\":8,\"1.7.1-beta.2\":2,\"1.14.0-beta.4\":2}}\n{\"date\":\"2025-09-09\",\"package\":\"nuqs\",\"downloads\":{\"2.6.0\":52466,\"1.18.0\":732,\"2.0.0\":79,\"1.14.1\":262,\"2.5.0-beta.2\":132,\"1.17.0\":866,\"2.1.1\":4610,\"1.17.2\":297,\"2.6.0-beta.3\":89,\"2.4.3\":352123,\"2.0.2\":11,\"1.19.1\":9935,\"2.0.4\":1671,\"1.15.0\":1,\"2.3.1\":16208,\"2.5.0-beta.3\":42,\"1.17.4\":6222,\"2.5.0-beta.6\":123,\"2.5.0\":15165,\"2.1.0\":313,\"2.2.3\":11645,\"1.19.2\":554,\"1.20.0\":57843,\"2.4.2\":8000,\"2.5.2\":115306,\"2.1.2\":4551,\"1.17.6\":1092,\"1.16.0\":84,\"1.15.2\":382,\"1.17.5\":907,\"2.3.0\":5114,\"2.2.1\":4067,\"1.16.1\":356,\"1.19.3\":2608,\"2.4.0-beta.2\":14,\"2.5.1\":30364,\"2.4.0\":12444,\"2.6.0-beta.2\":146,\"1.14.0\":43,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.2.0\":464,\"2.5.0-beta.5\":50,\"2.3.2\":7865,\"2.0.1\":134,\"1.15.4\":1213,\"1.17.1\":5779,\"2.0.3\":25,\"2.0.0-beta.1\":164,\"1.17.2-beta.1\":3,\"2.5.0-beta.1\":58,\"2.6.0-beta.1\":20,\"2.4.1\":44520,\"1.17.8\":5083,\"1.19.0\":730,\"1.17.7\":228,\"2.5.0-beta.4\":5,\"2.5.0-beta.7\":23,\"1.15.3\":1395,\"2.2.2\":1590}}\n{\"date\":\"2025-09-10\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.15.4\":119,\"1.20.0\":8888,\"1.3.0-beta.3\":1,\"1.7.2\":3173,\"1.10.2\":4,\"1.9.2\":44,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.16.0\":52,\"1.8.4\":18,\"1.12.2\":17,\"1.13.2\":83,\"1.14.1\":1,\"1.10.1\":34,\"1.19.0\":25,\"1.13.0\":792,\"1.17.2-beta.1\":1,\"1.12.0-beta.1\":1,\"1.4.0-beta.3\":1,\"1.17.1\":6996,\"1.10.1-beta.1\":1,\"1.15.0\":1,\"1.18.0\":1,\"1.13.1\":57,\"1.9.1\":4,\"1.8.0-beta.5\":1,\"1.19.3\":5063,\"1.8.0-beta.7\":1,\"1.8.5-beta.1\":1,\"1.11.0\":1,\"1.3.0\":8,\"1.16.0-beta.1\":1,\"1.9.0\":1,\"1.8.0-beta.12\":1,\"1.9.0-beta.2\":1,\"1.8.0-beta.8\":1,\"1.9.0-beta.1\":1,\"1.4.0-beta.5\":1,\"1.8.1\":8,\"1.15.2\":47,\"1.10.0\":3,\"1.12.1\":2,\"1.8.0-beta.15\":1,\"1.7.0\":3,\"1.7.3\":2549,\"1.8.1-beta.1\":1,\"1.16.1\":49,\"1.15.3\":2,\"1.4.0-beta.4\":1,\"1.17.2\":700,\"1.17.8\":3070,\"1.17.4\":744,\"1.8.0-beta.2\":2,\"1.14.0-beta.4\":1,\"1.6.1-beta.1\":1,\"1.4.0\":1,\"1.7.1-beta.2\":1,\"1.8.3-beta.1\":1,\"1.8.0\":1,\"1.17.0\":298,\"1.12.0\":29,\"1.8.0-beta.1\":1,\"1.14.0-beta.3\":1,\"1.17.6\":8,\"1.8.0-beta.9\":1,\"1.17.0-beta.1\":1,\"1.4.0-beta.2\":1,\"1.3.0-beta.2\":1,\"1.5.0-beta.1\":1,\"1.8.0-beta.13\":1,\"1.13.2-beta.1\":1,\"1.8.0-beta.14\":1,\"1.8.3\":1,\"1.14.0\":55,\"1.5.0\":6,\"1.8.2-beta.1\":1,\"1.6.0-beta.1\":1,\"1.8.2\":14,\"1.19.2\":1,\"1.7.1\":3,\"1.10.0-beta.1\":1,\"1.8.0-beta.6\":1,\"1.17.3\":1,\"1.15.1\":1,\"1.19.1\":105,\"1.17.7\":69,\"1.8.0-beta.11\":1,\"1.6.0\":1,\"1.2.1\":1,\"1.8.0-beta.10\":1,\"1.0.1\":1,\"1.2.0\":1,\"1.13.1-beta.1\":1,\"1.0.0\":1,\"1.1.0\":1,\"1.4.0-beta.1\":1,\"1.17.3-beta.1\":1,\"1.8.0-beta.3\":1,\"1.14.0-beta.2\":1,\"1.11.0-beta.1\":1,\"1.14.0-beta.5\":1,\"1.14.0-beta.1\":1,\"1.8.0-beta.4\":1,\"1.19.0-beta.1\":1,\"1.7.1-beta.1\":1,\"1.16.1-beta.1\":1,\"1.3.0-beta.1\":1,\"1.17.5\":1,\"1.10.2-beta.1\":1,\"1.7.0-beta.1\":1}}\n{\"date\":\"2025-09-10\",\"package\":\"nuqs\",\"downloads\":{\"2.2.3\":11605,\"1.19.2\":540,\"1.17.2\":304,\"2.6.0-beta.3\":98,\"2.5.0-beta.5\":49,\"1.16.0\":80,\"1.15.2\":456,\"2.4.0\":12588,\"2.5.0-beta.2\":119,\"2.0.0\":57,\"2.5.0\":15453,\"2.5.0-beta.7\":11,\"2.2.2\":1518,\"1.15.3\":1441,\"1.19.3\":2603,\"1.17.8\":5078,\"2.4.1\":45477,\"1.14.0\":31,\"2.6.0-beta.2\":134,\"1.17.7\":217,\"2.3.2\":7901,\"2.6.0-beta.1\":19,\"2.0.0-beta.1\":147,\"1.15.0\":1,\"1.19.1\":10498,\"2.0.2\":8,\"2.0.4\":1598,\"2.5.0-beta.4\":4,\"1.17.1\":6026,\"2.0.3\":27,\"2.1.0\":289,\"2.4.3\":351818,\"2.5.2\":104977,\"1.15.4\":1047,\"1.19.0\":727,\"1.14.1\":289,\"1.20.0\":57339,\"2.4.2\":8155,\"1.17.4\":6172,\"2.5.0-beta.3\":38,\"2.3.1\":15497,\"2.1.1\":4557,\"1.17.0\":943,\"1.18.0\":678,\"1.16.1\":338,\"2.5.0-beta.6\":129,\"2.1.2\":4409,\"1.17.6\":963,\"2.5.1\":28064,\"2.5.0-beta.1\":91,\"2.6.0\":76205,\"2.2.0\":413,\"1.17.5\":974,\"2.2.1\":3910,\"2.0.1\":101,\"2.3.0\":5008,\"1.17.2-beta.1\":2,\"2.4.0-beta.2\":14}}\n{\"date\":\"2025-09-11\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.8.0-beta.2\":2,\"1.2.1\":1,\"1.17.4\":738,\"1.9.0-beta.1\":1,\"1.4.0-beta.5\":1,\"1.14.0\":71,\"1.8.1\":12,\"1.8.0-beta.8\":1,\"1.15.2\":46,\"1.8.2-beta.1\":1,\"1.7.1-beta.1\":1,\"1.19.0-beta.1\":1,\"1.16.1-beta.1\":1,\"1.3.0-beta.1\":1,\"1.19.2\":1,\"1.14.0-beta.5\":1,\"1.14.0-beta.1\":1,\"1.17.2\":807,\"1.10.0-beta.1\":1,\"1.8.4\":14,\"1.7.3\":2418,\"1.8.1-beta.1\":1,\"1.5.0\":6,\"1.17.2-beta.1\":1,\"1.12.0-beta.1\":1,\"1.4.0-beta.3\":1,\"1.8.0-beta.15\":1,\"1.7.0\":3,\"1.9.0\":1,\"1.19.0\":29,\"1.10.1-beta.1\":1,\"1.3.0-beta.2\":1,\"1.8.0-beta.5\":1,\"1.8.5-beta.1\":1,\"1.11.0\":1,\"1.8.0-beta.13\":1,\"1.13.1-beta.1\":1,\"1.10.1\":34,\"1.14.1\":1,\"1.10.0\":3,\"1.15.4\":98,\"1.3.0-beta.3\":1,\"1.20.0\":8757,\"1.6.0-beta.1\":1,\"1.1.0\":1,\"1.15.0\":1,\"1.8.0-beta.10\":1,\"1.0.1\":1,\"1.2.0\":1,\"1.0.0\":1,\"1.5.0-beta.1\":1,\"1.8.3\":1,\"1.13.2\":89,\"1.8.0-beta.1\":1,\"1.8.0-beta.11\":1,\"1.6.0\":1,\"1.13.2-beta.1\":1,\"1.8.0-beta.14\":1,\"1.3.0\":11,\"1.16.0-beta.1\":1,\"1.14.0-beta.3\":1,\"1.17.0-beta.1\":1,\"1.17.6\":21,\"1.8.0-beta.9\":1,\"1.4.0-beta.2\":1,\"1.19.3\":4807,\"1.7.2\":3123,\"1.17.1\":6855,\"1.13.0\":658,\"1.15.1\":1,\"1.9.1\":3,\"1.8.2\":7,\"1.19.1\":123,\"1.17.7\":69,\"1.17.8\":3113,\"1.17.0\":279,\"1.12.0\":25,\"1.4.0\":1,\"1.4.0-beta.4\":1,\"1.9.0-beta.2\":1,\"1.15.3\":2,\"1.6.1-beta.1\":1,\"1.14.0-beta.4\":1,\"1.12.2\":13,\"1.8.0-beta.7\":1,\"1.7.1-beta.2\":1,\"1.8.3-beta.1\":1,\"1.7.1\":3,\"1.17.5\":1,\"1.8.0-beta.6\":1,\"1.17.3\":1,\"1.8.0-beta.4\":1,\"1.11.0-beta.1\":1,\"1.14.0-beta.2\":1,\"1.7.0-beta.1\":1,\"1.18.0\":1,\"1.10.2-beta.1\":1,\"1.4.0-beta.1\":1,\"1.17.3-beta.1\":1,\"1.8.0-beta.3\":1,\"1.16.1\":47,\"1.16.0\":28,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.9.2\":46,\"1.10.2\":4,\"1.13.1\":59,\"1.8.0-beta.12\":1,\"1.12.1\":2,\"1.8.0\":1}}\n{\"date\":\"2025-09-11\",\"package\":\"nuqs\",\"downloads\":{\"2.6.0\":103576,\"2.5.0-beta.6\":123,\"1.18.0\":639,\"1.16.1\":270,\"2.1.1\":4726,\"1.17.0\":1092,\"2.2.0\":406,\"2.3.0\":5038,\"1.17.5\":851,\"2.5.0-beta.1\":103,\"2.1.2\":4104,\"1.17.6\":1030,\"1.20.0\":56590,\"2.4.2\":8119,\"2.5.2\":91412,\"1.15.4\":998,\"2.2.2\":1462,\"2.0.1\":94,\"2.4.1\":45846,\"1.14.1\":349,\"2.4.3\":347671,\"2.5.0-beta.2\":120,\"2.0.0\":54,\"1.17.2\":308,\"2.5.0-beta.5\":45,\"2.3.2\":8294,\"1.17.8\":4755,\"2.5.1\":26889,\"1.14.0\":25,\"2.6.0-beta.2\":90,\"1.17.7\":207,\"2.5.0-beta.4\":4,\"2.1.0\":273,\"1.19.3\":2463,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.4.0-beta.2\":26,\"1.17.4\":6211,\"2.5.0-beta.3\":30,\"2.3.1\":14197,\"2.2.1\":3702,\"2.0.0-beta.1\":136,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.19.0\":674,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.17.1\":6030,\"2.0.3\":42,\"2.6.0-beta.3\":109,\"2.4.0\":12119,\"1.15.2\":478,\"1.15.3\":1495,\"2.5.0-beta.7\":15,\"2.0.2\":6,\"1.15.0\":1,\"1.19.1\":10385,\"2.0.4\":1608,\"2.6.0-beta.1\":13,\"1.16.0\":78,\"2.2.3\":11750,\"1.19.2\":564,\"2.5.0\":14514}}\n{\"date\":\"2025-09-12\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.10.1\":35,\"1.17.2\":746,\"1.15.4\":97,\"1.20.0\":9234,\"1.3.0-beta.3\":1,\"1.17.6\":21,\"1.10.0\":3,\"1.14.1\":1,\"1.13.2\":94,\"1.3.0\":11,\"1.8.3-beta.1\":1,\"1.13.0\":659,\"1.12.2\":12,\"1.8.0-beta.7\":1,\"1.7.1-beta.2\":1,\"1.13.1\":52,\"1.4.0-beta.2\":1,\"1.8.0-beta.9\":1,\"1.17.0-beta.1\":1,\"1.8.0-beta.1\":1,\"1.16.0-beta.1\":1,\"1.14.0-beta.3\":1,\"1.0.0\":1,\"1.8.2-beta.1\":1,\"1.19.2\":1,\"1.17.4\":698,\"1.8.0-beta.2\":2,\"1.16.1-beta.1\":1,\"1.3.0-beta.1\":1,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.10.2\":5,\"1.9.2\":53,\"1.19.3\":4179,\"1.15.3\":2,\"1.8.4\":14,\"1.4.0\":1,\"1.4.0-beta.4\":1,\"1.9.0-beta.2\":1,\"1.17.1\":6817,\"1.14.0-beta.4\":1,\"1.7.2\":3146,\"1.16.1\":49,\"1.16.0\":9,\"1.12.1\":2,\"1.9.0\":1,\"1.18.0\":1,\"1.19.0\":26,\"1.8.0-beta.11\":1,\"1.6.0\":1,\"1.8.0-beta.5\":1,\"1.13.2-beta.1\":1,\"1.8.0-beta.12\":1,\"1.8.0\":1,\"1.15.2\":49,\"1.2.1\":1,\"1.19.0-beta.1\":1,\"1.7.1-beta.1\":1,\"1.10.0-beta.1\":1,\"1.6.1-beta.1\":1,\"1.14.0-beta.5\":1,\"1.14.0-beta.1\":1,\"1.10.1-beta.1\":1,\"1.17.2-beta.1\":1,\"1.12.0-beta.1\":1,\"1.4.0-beta.3\":1,\"1.8.5-beta.1\":1,\"1.11.0\":3,\"1.5.0-beta.1\":1,\"1.8.3\":1,\"1.8.0-beta.13\":1,\"1.15.0\":1,\"1.3.0-beta.2\":1,\"1.13.1-beta.1\":1,\"1.8.0-beta.15\":1,\"1.7.0\":2,\"1.5.0\":3,\"1.8.0-beta.10\":1,\"1.0.1\":1,\"1.8.0-beta.8\":1,\"1.6.0-beta.1\":1,\"1.7.3\":2394,\"1.8.1-beta.1\":1,\"1.9.0-beta.1\":1,\"1.4.0-beta.5\":1,\"1.8.1\":12,\"1.14.0\":86,\"1.8.0-beta.14\":1,\"1.2.0\":1,\"1.7.1\":5,\"1.8.2\":6,\"1.17.7\":57,\"1.17.5\":1,\"1.9.1\":2,\"1.19.1\":137,\"1.1.0\":1,\"1.15.1\":1,\"1.11.0-beta.1\":1,\"1.14.0-beta.2\":1,\"1.8.0-beta.3\":1,\"1.17.0\":229,\"1.12.0\":22,\"1.10.2-beta.1\":1,\"1.4.0-beta.1\":1,\"1.17.3-beta.1\":1,\"1.17.8\":3082,\"1.8.0-beta.4\":1,\"1.8.0-beta.6\":1,\"1.17.3\":1,\"1.7.0-beta.1\":1}}\n{\"date\":\"2025-09-12\",\"package\":\"nuqs\",\"downloads\":{\"2.6.0-beta.3\":66,\"1.17.2\":298,\"2.3.2\":8075,\"2.5.0-beta.2\":88,\"2.4.3\":340605,\"2.0.0\":75,\"1.16.1\":407,\"1.18.0\":514,\"2.5.0-beta.6\":121,\"2.1.1\":4803,\"1.17.0\":1138,\"1.17.8\":4540,\"2.4.0\":12065,\"2.5.0\":13654,\"1.16.0\":75,\"1.17.7\":213,\"2.5.0-beta.4\":3,\"2.5.1\":24382,\"1.14.0\":25,\"2.6.0-beta.2\":76,\"1.15.2\":522,\"1.19.3\":2421,\"2.6.0-beta.1\":10,\"1.15.3\":1475,\"2.5.0-beta.5\":44,\"2.2.3\":11929,\"1.19.2\":616,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.0.1\":109,\"2.4.1\":44903,\"2.2.2\":1257,\"2.6.0\":138422,\"2.0.0-beta.1\":112,\"2.2.0\":394,\"2.3.0\":4952,\"1.17.5\":767,\"2.5.0-beta.1\":103,\"2.5.2\":69149,\"1.15.4\":887,\"1.20.0\":58698,\"2.4.2\":8249,\"1.19.0\":871,\"2.0.3\":60,\"1.17.1\":6973,\"1.14.1\":381,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.5.0-beta.7\":11,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"1.19.1\":10167,\"2.0.2\":11,\"2.0.4\":1574,\"2.3.1\":13517,\"2.5.0-beta.3\":40,\"1.17.4\":5945,\"2.2.1\":3425,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.1.2\":3913,\"1.17.6\":804,\"2.1.0\":275,\"1.15.0\":1,\"2.4.0-beta.2\":30,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1}}\n{\"date\":\"2025-09-13\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.7.2\":3085,\"1.14.1\":1,\"1.13.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.10.2\":5,\"1.9.0\":1,\"1.12.2\":6,\"1.8.4\":8,\"1.4.0-beta.4\":1,\"1.15.4\":73,\"1.20.0\":9334,\"1.3.0-beta.3\":1,\"1.8.0-beta.13\":1,\"1.8.5-beta.1\":1,\"1.11.0\":3,\"1.3.0-beta.2\":1,\"1.8.0-beta.8\":1,\"1.6.0-beta.1\":1,\"1.7.3\":2363,\"1.5.0\":3,\"1.9.0-beta.1\":1,\"1.4.0-beta.5\":1,\"1.8.1\":14,\"1.14.0\":90,\"1.9.0-beta.2\":1,\"1.13.2\":101,\"1.10.0\":1,\"1.12.1\":1,\"1.19.0\":23,\"1.10.1-beta.1\":1,\"1.15.0\":1,\"1.18.0\":1,\"1.17.0-beta.1\":1,\"1.17.6\":40,\"1.8.0-beta.9\":1,\"1.17.1\":7056,\"1.14.0-beta.4\":1,\"1.3.0\":6,\"1.16.0-beta.1\":1,\"1.14.0-beta.3\":1,\"1.8.0-beta.15\":1,\"1.7.0\":1,\"1.13.0\":572,\"1.9.2\":55,\"1.8.0-beta.12\":1,\"1.13.1\":60,\"1.8.3-beta.1\":1,\"1.8.0-beta.7\":1,\"1.19.3\":4156,\"1.8.0\":1,\"1.16.0\":5,\"1.16.1\":53,\"1.15.3\":2,\"1.13.1-beta.1\":1,\"1.13.2-beta.1\":1,\"1.5.0-beta.1\":1,\"1.8.0-beta.14\":1,\"1.8.3\":1,\"1.8.0-beta.10\":1,\"1.0.1\":1,\"1.4.0-beta.2\":1,\"1.8.1-beta.1\":1,\"1.0.0\":1,\"1.1.0\":1,\"1.17.4\":710,\"1.8.0-beta.2\":1,\"1.2.1\":1,\"1.19.2\":1,\"1.8.2\":5,\"1.7.1\":5,\"1.10.1\":14,\"1.8.0-beta.1\":1,\"1.8.2-beta.1\":1,\"1.17.8\":2942,\"1.17.2\":687,\"1.2.0\":1,\"1.4.0\":1,\"1.6.1-beta.1\":1,\"1.17.2-beta.1\":1,\"1.12.0-beta.1\":1,\"1.4.0-beta.3\":1,\"1.7.1-beta.2\":1,\"1.7.1-beta.1\":1,\"1.19.0-beta.1\":1,\"1.16.1-beta.1\":1,\"1.3.0-beta.1\":1,\"1.10.0-beta.1\":1,\"1.15.2\":43,\"1.14.0-beta.5\":1,\"1.14.0-beta.1\":1,\"1.15.1\":1,\"1.8.0-beta.5\":1,\"1.8.0-beta.11\":1,\"1.6.0\":2,\"1.11.0-beta.1\":1,\"1.14.0-beta.2\":1,\"1.17.3-beta.1\":1,\"1.4.0-beta.1\":1,\"1.8.0-beta.6\":1,\"1.17.3\":1,\"1.8.0-beta.4\":1,\"1.17.0\":204,\"1.8.0-beta.3\":1,\"1.12.0\":11,\"1.10.2-beta.1\":1,\"1.19.1\":149,\"1.17.7\":57,\"1.9.1\":4,\"1.17.5\":1,\"1.7.0-beta.1\":1}}\n{\"date\":\"2025-09-13\",\"package\":\"nuqs\",\"downloads\":{\"2.4.0\":11628,\"2.5.1\":22891,\"2.5.0-beta.2\":88,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"1.15.2\":501,\"1.17.2\":261,\"2.5.0-beta.5\":43,\"2.3.2\":7974,\"2.5.2\":52522,\"1.15.4\":878,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.19.0\":879,\"2.1.2\":3991,\"1.17.6\":806,\"2.2.0\":384,\"2.5.0-beta.1\":114,\"2.3.0\":4873,\"1.14.0\":25,\"2.6.0-beta.2\":64,\"1.17.7\":219,\"1.19.3\":2519,\"2.6.0-beta.3\":64,\"1.17.8\":4225,\"2.4.1\":45151,\"2.5.0-beta.7\":11,\"1.15.3\":1475,\"2.4.3\":337092,\"1.16.0\":80,\"2.6.0-beta.1\":7,\"1.19.2\":641,\"2.2.3\":12141,\"2.0.1\":115,\"2.5.0-beta.6\":103,\"1.18.0\":496,\"2.1.1\":4871,\"1.17.0\":1250,\"2.5.0\":12827,\"2.0.0\":92,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.2.2\":1080,\"1.16.1\":511,\"1.14.1\":382,\"2.6.0\":171260,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.0.2\":14,\"1.15.0\":1,\"2.0.4\":1628,\"1.17.1\":7242,\"2.0.3\":70,\"1.17.5\":676,\"1.20.0\":61948,\"2.4.2\":8141,\"2.3.1\":13383,\"1.19.1\":10008,\"2.0.0-beta.1\":98,\"2.2.1\":3218,\"1.17.4\":5817,\"2.5.0-beta.3\":55,\"2.1.0\":243,\"2.4.0-beta.2\":29,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1}}\n{\"date\":\"2025-09-14\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.15.2\":42,\"1.8.2\":4,\"1.17.7\":56,\"1.19.1\":148,\"1.9.1\":3,\"1.17.8\":2985,\"1.17.4\":699,\"1.17.2\":676,\"1.12.0\":11,\"1.17.0\":192,\"1.7.1\":4,\"1.7.3\":2359,\"1.8.1\":11,\"1.14.0\":89,\"1.10.2\":3,\"1.9.2\":42,\"1.19.0\":22,\"1.13.1\":59,\"1.16.0\":4,\"1.7.2\":2953,\"1.17.1\":6943,\"1.8.4\":7,\"1.12.2\":5,\"1.13.0\":575,\"1.17.6\":41,\"1.20.0\":9380,\"1.13.2\":99,\"1.5.0\":2,\"1.11.0\":2,\"1.19.3\":4120,\"1.16.1\":41,\"1.6.0\":1,\"1.15.3\":1,\"1.15.4\":65,\"1.10.0\":4,\"1.10.1\":13,\"1.3.0\":5}}\n{\"date\":\"2025-09-14\",\"package\":\"nuqs\",\"downloads\":{\"2.1.1\":4815,\"2.6.0\":171388,\"1.18.0\":491,\"1.16.1\":519,\"2.5.0-beta.6\":105,\"1.14.1\":386,\"2.3.1\":13250,\"1.17.4\":5704,\"2.5.0-beta.3\":56,\"2.0.2\":14,\"1.15.0\":1,\"1.19.1\":10025,\"2.1.0\":235,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.4.0-beta.2\":29,\"2.2.1\":3199,\"1.17.0\":1229,\"2.0.4\":1608,\"2.2.0\":383,\"2.4.1\":44965,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.2.2\":1068,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.3.0\":4899,\"2.5.0-beta.1\":107,\"1.17.5\":668,\"1.16.0\":81,\"1.19.3\":2494,\"1.15.2\":508,\"1.20.0\":61557,\"2.4.2\":8101,\"1.19.0\":888,\"2.5.2\":51667,\"1.15.4\":852,\"2.1.2\":4053,\"1.17.6\":795,\"2.5.0\":12720,\"2.4.0\":11816,\"1.17.8\":4137,\"2.6.0-beta.2\":61,\"1.14.0\":24,\"1.17.7\":225,\"2.5.1\":22931,\"1.15.3\":1458,\"2.5.0-beta.7\":11,\"2.6.0-beta.3\":64,\"2.3.2\":7932,\"1.17.2\":256,\"2.4.3\":336582,\"2.0.1\":120,\"2.0.0-beta.1\":98,\"1.17.1\":7122,\"2.0.3\":75,\"1.19.2\":651,\"2.2.3\":11952,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.6.0-beta.1\":4,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.5.0-beta.5\":46,\"2.0.0\":88,\"2.5.0-beta.2\":91,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1}}\n{\"date\":\"2025-09-15\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.7.3\":2367,\"1.14.0\":89,\"1.8.1\":11,\"1.6.0\":1,\"1.17.4\":670,\"1.17.2\":679,\"1.15.2\":36,\"1.15.4\":64,\"1.20.0\":9271,\"1.17.6\":41,\"1.13.1\":58,\"1.10.2\":3,\"1.9.2\":42,\"1.10.0\":5,\"1.13.2\":106,\"1.16.1\":23,\"1.16.0\":4,\"1.13.0\":615,\"1.19.0\":22,\"1.7.2\":2894,\"1.17.1\":6833,\"1.10.1\":13,\"1.3.0\":5,\"1.19.3\":4144,\"1.12.2\":3,\"1.5.0\":1,\"1.8.4\":5,\"1.11.0\":2,\"1.8.2\":4,\"1.17.7\":56,\"1.19.1\":151,\"1.17.0\":190,\"1.12.0\":11,\"1.9.1\":3,\"1.7.1\":4,\"1.17.8\":3023}}\n{\"date\":\"2025-09-15\",\"package\":\"nuqs\",\"downloads\":{\"1.20.0\":62644,\"2.4.2\":8041,\"2.5.2\":50042,\"1.15.4\":821,\"2.2.0\":390,\"2.0.0-beta.1\":99,\"1.17.5\":673,\"2.3.0\":4931,\"2.5.0-beta.1\":108,\"2.1.2\":4033,\"1.17.6\":777,\"1.17.1\":7097,\"2.0.3\":76,\"1.18.0\":478,\"2.5.0-beta.6\":110,\"1.16.1\":557,\"2.1.1\":4793,\"1.17.0\":1239,\"1.14.1\":388,\"1.17.2\":256,\"2.6.0-beta.3\":58,\"2.5.0-beta.5\":50,\"2.3.2\":8056,\"2.3.2-beta.1\":1,\"2.5.0-beta.2\":94,\"2.4.3\":335108,\"2.4.0-beta.5\":1,\"1.19.2\":709,\"2.2.3\":11976,\"1.19.0\":894,\"2.4.0-beta.4\":1,\"2.0.0\":102,\"2.4.0-beta.6\":1,\"2.4.0-beta.3\":1,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.4.0\":11923,\"2.5.0\":12409,\"2.2.2\":1065,\"2.0.0-snapshot.2024-10-22.1456d503\":2,\"1.15.2\":545,\"2.4.1\":44591,\"2.0.1\":124,\"1.15.3\":1338,\"2.5.0-beta.7\":10,\"1.14.0\":25,\"2.6.0-beta.2\":108,\"2.5.1\":22732,\"2.4.2-beta.3\":1,\"1.17.7\":233,\"1.19.3\":2524,\"0.0.0-snapshot.2024-12-19.153ba041\":2,\"2.4.2-beta.2\":1,\"1.17.8\":4050,\"2.3.0-beta.1\":1,\"2.3.2-beta.2\":1,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.3.1-beta.1\":1,\"2.6.0\":181119,\"1.14.0-beta.4\":1,\"1.17.3\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":2,\"1.16.0\":90,\"1.14.0-beta.5\":1,\"1.17.3-beta.1\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"2.3.0-beta.2\":1,\"1.19.0-beta.1\":1,\"1.14.1-beta.1\":1,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":2,\"2.5.0-beta.4\":1,\"2.3.3-beta.1\":1,\"2.6.0-beta.1\":6,\"2.3.2-beta.4\":1,\"2.0.2\":18,\"1.19.1\":9954,\"2.0.4\":1643,\"2.5.0-beta.3\":58,\"2.3.1\":13223,\"2.4.0-beta.1\":1,\"2.2.1\":3172,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.15.0\":2,\"2.1.0\":236,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":2,\"2.4.0-beta.2\":30,\"1.17.4\":5676,\"1.16.0-beta.1\":1,\"0.0.0-reserved\":1,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1}}\n{\"date\":\"2025-09-16\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.17.8\":2954,\"1.17.0\":209,\"1.12.0\":6,\"1.8.2\":7,\"1.9.1\":5,\"1.17.5\":1,\"1.17.2\":635,\"1.15.2\":39,\"1.13.1\":72,\"1.16.1\":21,\"1.9.2\":35,\"1.10.2\":3,\"1.7.1\":2,\"1.19.1\":135,\"1.17.7\":58,\"1.8.0-beta.13\":1,\"1.5.0\":1,\"1.17.1\":6635,\"1.7.2\":3038,\"1.17.4\":616,\"1.7.3\":2426,\"1.16.0\":3,\"1.19.3\":5222,\"1.10.0\":5,\"1.13.2\":120,\"1.20.0\":8987,\"1.10.1\":7,\"1.12.2\":7,\"1.13.0\":703,\"1.19.0\":23,\"1.11.0\":2,\"1.8.4\":1,\"1.8.1\":9,\"1.15.4\":69,\"1.17.6\":43,\"1.3.0\":6,\"1.14.0\":88,\"1.6.0\":1}}\n{\"date\":\"2025-09-16\",\"package\":\"nuqs\",\"downloads\":{\"2.2.1\":2888,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.16.0-beta.1\":1,\"2.4.0-beta.1\":1,\"2.3.1\":12865,\"2.5.0-beta.3\":57,\"1.17.4\":5518,\"2.4.0-beta.2\":32,\"2.1.0\":162,\"2.0.4\":1633,\"1.15.0\":3,\"2.0.2\":18,\"1.19.1\":9696,\"1.14.1\":423,\"1.16.1\":544,\"2.5.0-beta.6\":147,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1,\"1.15.2\":549,\"0.0.0-reserved\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"1.19.0-beta.1\":1,\"2.2.3\":12273,\"1.19.2\":618,\"2.5.0\":10833,\"2.4.0\":12098,\"1.16.0\":115,\"1.17.3-beta.1\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":2,\"2.0.1\":141,\"2.4.1\":45064,\"2.0.0-snapshot.2024-10-22.1456d503\":2,\"2.2.2\":1074,\"2.5.0-beta.7\":10,\"1.14.1-beta.1\":1,\"1.15.3\":1249,\"1.19.3\":2325,\"0.0.0-snapshot.2024-12-19.153ba041\":2,\"2.6.0-beta.1\":7,\"2.3.2-beta.4\":1,\"2.6.0-beta.2\":111,\"1.14.0\":23,\"1.17.7\":257,\"2.5.0-beta.4\":1,\"2.5.1\":21881,\"1.17.8\":3547,\"2.3.0-beta.1\":1,\"1.18.0\":418,\"2.6.0\":198715,\"1.17.0\":1086,\"2.1.1\":4571,\"2.0.0-beta.1\":90,\"2.2.0\":333,\"2.3.0\":4615,\"2.5.0-beta.1\":122,\"1.19.0\":978,\"1.20.0\":65366,\"2.4.2\":7900,\"2.4.0-beta.4\":1,\"2.5.2\":46117,\"1.15.4\":760,\"2.3.0-beta.2\":1,\"1.14.0-beta.5\":1,\"1.17.1\":7999,\"2.0.3\":82,\"2.1.2\":3985,\"1.17.6\":670,\"2.4.0-beta.3\":1,\"2.5.0-beta.2\":98,\"2.3.2\":8203,\"2.6.0-beta.3\":49,\"2.0.0\":102,\"0.0.0-snapshot.2025-01-13.a622d9b9\":2,\"2.4.3\":326115,\"2.4.0-beta.5\":1,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.3.3-beta.1\":1,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.4.2-beta.3\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":2,\"2.4.2-beta.2\":1,\"1.17.2\":296,\"2.5.0-beta.5\":63,\"2.3.1-beta.1\":1,\"2.3.2-beta.2\":1,\"1.14.0-beta.4\":1,\"1.17.3\":1,\"2.3.2-beta.1\":1,\"2.4.0-beta.6\":1,\"1.17.5\":407,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1}}\n{\"date\":\"2025-09-17\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.10.0\":5,\"1.13.2\":128,\"1.5.0\":1,\"1.20.0\":8858,\"1.17.6\":38,\"1.17.2\":562,\"1.13.1\":80,\"1.8.2\":21,\"1.9.1\":4,\"1.17.5\":3,\"1.9.2\":38,\"1.10.2\":2,\"1.17.8\":2847,\"1.6.0\":1,\"1.7.3\":2481,\"1.8.1\":9,\"1.3.0\":11,\"1.14.1\":1,\"1.10.1\":4,\"1.8.0-beta.13\":2,\"1.11.0\":2,\"1.15.4\":68,\"1.15.2\":34,\"1.2.1\":11,\"1.17.4\":591,\"1.19.1\":147,\"1.17.7\":58,\"1.7.1\":2,\"1.19.3\":5606,\"1.17.0\":225,\"1.12.0\":4,\"1.16.0\":1,\"1.16.1\":19,\"1.14.0\":71,\"1.17.1\":6957,\"1.7.2\":2801,\"1.12.2\":8,\"1.13.0\":859,\"1.19.0\":27}}\n{\"date\":\"2025-09-17\",\"package\":\"nuqs\",\"downloads\":{\"2.0.0\":131,\"2.5.0-beta.2\":118,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.4.0-beta.5\":1,\"2.4.3\":323659,\"2.0.0-snapshot.2024-10-22.1456d503\":2,\"2.2.2\":1175,\"2.6.0-beta.3\":46,\"2.5.0-beta.5\":64,\"2.3.2\":8374,\"2.4.1\":45220,\"2.0.1\":181,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"1.14.1\":415,\"2.4.0\":11922,\"2.5.0\":9208,\"1.18.0\":347,\"1.16.1\":524,\"2.5.0-beta.6\":166,\"1.15.2\":494,\"1.17.3-beta.1\":1,\"1.16.0\":120,\"2.1.0\":175,\"1.15.0\":4,\"2.0.2\":17,\"2.0.4\":1581,\"1.19.1\":9367,\"1.16.0-beta.1\":1,\"2.5.0-beta.3\":51,\"2.4.0-beta.1\":1,\"2.4.0-beta.2\":32,\"2.3.1\":12841,\"1.17.4\":5328,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.17.1\":7683,\"2.0.3\":89,\"2.1.2\":4086,\"1.17.6\":597,\"2.5.0-beta.1\":97,\"1.17.5\":210,\"2.3.0\":4640,\"2.0.0-beta.1\":106,\"2.2.0\":332,\"2.4.0-beta.4\":1,\"1.20.0\":68407,\"2.4.2\":8012,\"2.5.2\":42513,\"1.15.4\":766,\"1.19.0\":1031,\"1.14.0-beta.4\":1,\"1.17.3\":4,\"0.0.0-snapshot.2025-01-13.a622d9b9\":2,\"2.3.2-beta.1\":1,\"2.3.2-beta.2\":1,\"2.3.3-beta.1\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":2,\"1.17.2\":323,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.3.1-beta.1\":1,\"1.17.0\":1042,\"2.1.1\":4739,\"2.6.0\":220253,\"2.3.0-beta.1\":1,\"2.2.3\":12220,\"1.19.2\":598,\"2.3.0-beta.2\":1,\"1.19.0-beta.1\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":2,\"1.14.0-beta.5\":1,\"2.2.1\":2711,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1,\"0.0.0-reserved\":2,\"2.4.0-beta.6\":1,\"2.4.0-beta.3\":1,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.4.2-beta.2\":1,\"1.17.8\":3399,\"1.14.1-beta.1\":1,\"1.15.3\":1195,\"2.5.1\":20951,\"2.6.0-beta.2\":121,\"1.14.0\":22,\"1.17.7\":283,\"1.19.3\":2121,\"2.6.0-beta.1\":5,\"2.3.2-beta.4\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":2,\"2.5.0-beta.7\":10,\"2.5.0-beta.4\":1,\"2.4.2-beta.3\":1}}\n{\"date\":\"2025-09-18\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.5.0\":1,\"1.17.4\":580,\"1.8.0-beta.13\":2,\"1.9.1\":5,\"1.19.1\":266,\"1.17.7\":58,\"1.17.8\":2797,\"1.11.0\":2,\"1.7.0\":1,\"1.8.1\":5,\"1.14.0\":44,\"1.7.3\":2665,\"1.19.3\":5748,\"1.9.2\":39,\"1.17.1\":7479,\"1.7.2\":2762,\"1.12.2\":12,\"1.17.2\":417,\"1.15.2\":28,\"1.2.1\":14,\"1.6.0\":1,\"1.13.1\":77,\"1.17.5\":3,\"1.8.2\":24,\"1.7.1\":2,\"1.17.0\":228,\"1.12.0\":1,\"1.3.0\":31,\"1.13.2\":143,\"1.20.0\":9125,\"1.10.1\":13,\"1.10.2\":2,\"1.19.0\":23,\"1.16.0\":1,\"1.16.1\":24,\"1.13.0\":918,\"1.10.0\":8,\"1.14.1\":1,\"1.15.4\":78,\"1.17.6\":28}}\n{\"date\":\"2025-09-18\",\"package\":\"nuqs\",\"downloads\":{\"2.0.0-snapshot.2024-10-22.1456d503\":2,\"2.2.2\":1469,\"1.17.0\":959,\"2.1.1\":4425,\"1.16.1\":494,\"2.3.0-beta.1\":1,\"2.0.2\":18,\"2.0.4\":1561,\"1.15.0\":5,\"2.1.0\":221,\"1.16.0-beta.1\":1,\"2.5.0-beta.3\":47,\"1.17.4\":5216,\"2.3.1\":13236,\"2.4.0-beta.2\":20,\"2.4.1\":43783,\"2.0.1\":202,\"2.3.3-beta.1\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.4.0-beta.4\":1,\"1.19.0\":1098,\"2.5.2\":40485,\"1.15.4\":706,\"1.17.1\":8174,\"2.0.3\":74,\"1.17.5\":192,\"2.3.0\":4731,\"2.5.0-beta.1\":130,\"2.0.0-beta.1\":118,\"2.2.0\":371,\"2.2.3\":12249,\"1.19.2\":599,\"1.15.2\":507,\"2.4.0\":12113,\"2.5.0\":8680,\"1.16.0\":117,\"2.4.2-beta.2\":1,\"1.17.8\":3455,\"2.5.0-beta.7\":6,\"2.6.0-beta.2\":125,\"1.14.0\":21,\"2.4.2-beta.3\":1,\"2.5.1\":20228,\"1.18.0\":274,\"2.5.0-beta.6\":174,\"2.6.0\":237195,\"1.14.1\":409,\"1.19.1\":9608,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":2,\"2.4.0-beta.1\":1,\"0.0.0-reserved\":2,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.2.1\":2858,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":2,\"2.3.0-beta.2\":1,\"1.19.0-beta.1\":1,\"2.3.2-beta.1\":1,\"2.5.0-beta.2\":106,\"2.1.2\":4105,\"1.17.6\":509,\"2.4.0-beta.3\":1,\"2.6.0-beta.3\":51,\"2.3.2\":8302,\"2.5.0-beta.5\":61,\"1.20.0\":70343,\"2.4.2\":8217,\"1.14.0-beta.4\":1,\"1.17.3\":4,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":2,\"2.4.3\":322620,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.4.0-beta.6\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"1.19.3\":2350,\"2.6.0-beta.1\":5,\"2.3.2-beta.4\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"1.17.7\":288,\"2.5.0-beta.4\":1,\"1.14.1-beta.1\":1,\"1.15.3\":1114,\"2.0.0\":126,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.4.0-beta.5\":1,\"2.3.2-beta.2\":1,\"1.17.2\":346,\"2.3.1-beta.1\":1}}\n{\"date\":\"2025-09-19\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.16.1\":28,\"1.10.1\":18,\"1.20.0\":9216,\"1.17.6\":31,\"1.15.4\":80,\"1.12.2\":12,\"1.7.2\":2636,\"1.13.1\":78,\"1.3.0\":45,\"1.14.1\":1,\"1.10.0\":8,\"1.19.0\":21,\"1.16.0\":1,\"1.12.0\":12,\"1.17.0\":226,\"1.14.0\":29,\"1.6.0\":1,\"1.19.1\":380,\"1.17.7\":58,\"1.19.3\":6112,\"1.13.2\":155,\"1.8.1\":4,\"1.5.0\":3,\"1.9.2\":30,\"1.17.8\":2796,\"1.8.0-beta.13\":3,\"1.15.2\":31,\"1.7.3\":2752,\"1.17.2\":398,\"1.8.2\":26,\"1.9.1\":6,\"1.17.5\":3,\"1.10.2\":1,\"1.2.1\":28,\"1.17.4\":491,\"1.11.0\":1,\"1.17.1\":8101,\"1.13.0\":972,\"1.7.0\":10}}\n{\"date\":\"2025-09-19\",\"package\":\"nuqs\",\"downloads\":{\"1.14.1-beta.1\":1,\"2.5.0-beta.2\":123,\"2.4.2-beta.1\":2,\"2.3.2-beta.3\":1,\"2.3.2-beta.2\":1,\"2.4.0-beta.5\":1,\"2.4.3\":317135,\"2.6.0-beta.3\":101,\"1.17.2\":341,\"2.6.0-beta.1\":5,\"2.3.2-beta.4\":1,\"1.19.3\":2550,\"2.5.1\":20393,\"2.6.0-beta.2\":122,\"1.14.0-beta.4\":1,\"2.0.0\":139,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.14.0\":28,\"2.4.0\":12972,\"2.4.2-beta.3\":1,\"2.3.2-beta.1\":1,\"2.0.0-beta.1\":103,\"2.5.2\":37544,\"1.20.0\":70296,\"2.4.2\":8403,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.3.0\":4709,\"1.17.5\":182,\"1.17.1\":7856,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.0.3\":71,\"2.1.2\":3947,\"1.17.6\":453,\"1.17.3\":4,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"1.19.0-beta.1\":1,\"2.2.3\":12130,\"1.19.2\":499,\"1.15.2\":484,\"2.4.0-beta.6\":1,\"1.16.0\":125,\"2.5.0-beta.5\":56,\"1.17.3-beta.1\":1,\"2.3.2\":8287,\"2.2.1\":3073,\"1.19.0\":909,\"2.2.0\":383,\"2.1.0\":307,\"2.0.1\":179,\"2.4.0-beta.3\":1,\"2.6.0\":246824,\"1.17.0\":879,\"2.1.1\":4714,\"2.3.0-beta.2\":1,\"2.4.0-beta.1\":1,\"2.5.0-beta.6\":213,\"2.4.0-beta.2\":16,\"2.5.0-beta.3\":33,\"2.3.1\":13400,\"1.16.0-beta.1\":1,\"2.5.0\":7947,\"1.14.1\":415,\"1.16.1\":352,\"1.18.0\":268,\"2.3.1-beta.1\":1,\"1.14.0-beta.5\":1,\"2.0.2\":16,\"1.15.0\":4,\"2.0.4\":1528,\"1.19.1\":9801,\"2.5.0-beta.1\":142,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.3.3-beta.1\":1,\"1.15.4\":709,\"2.4.0-beta.4\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.3.0-beta.1\":1,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1,\"1.17.4\":5312,\"0.0.0-reserved\":2,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"1.17.7\":276,\"2.5.0-beta.4\":1,\"1.17.8\":3644,\"2.4.1\":44459,\"2.4.2-beta.2\":1,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.15.3\":1183,\"2.2.2\":1740,\"2.5.0-beta.7\":3,\"1.15.1\":1,\"2.3.1-beta.3\":1}}\n{\"date\":\"2025-09-20\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.2.1\":28,\"1.12.2\":11,\"1.19.3\":6090,\"1.8.0-beta.13\":3,\"1.9.2\":34,\"1.7.2\":2681,\"1.13.1\":77,\"1.17.8\":3017,\"1.14.0\":36,\"1.7.3\":2748,\"1.13.2\":137,\"1.14.1\":1,\"1.3.0\":46,\"1.10.1\":51,\"1.20.0\":9042,\"1.13.0\":1472,\"1.7.0\":10,\"1.17.1\":8701,\"1.17.2\":393,\"1.16.1\":24,\"1.17.4\":482,\"1.19.1\":459,\"1.15.2\":50,\"1.8.2\":26,\"1.5.0\":4,\"1.8.1\":4,\"1.12.0\":15,\"1.17.0\":213,\"1.10.0\":8,\"1.15.4\":81,\"1.17.6\":11,\"1.10.2\":1,\"1.11.0\":1,\"1.7.1\":2,\"1.19.0\":23,\"1.9.1\":7,\"1.17.7\":58,\"1.17.5\":3,\"1.4.0\":1}}\n{\"date\":\"2025-09-20\",\"package\":\"nuqs\",\"downloads\":{\"2.5.1\":20424,\"2.4.2-beta.3\":1,\"2.4.0\":13114,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"1.19.0-beta.1\":1,\"2.5.0-beta.6\":305,\"1.18.0\":236,\"1.17.0\":855,\"2.1.1\":4747,\"1.16.0\":119,\"2.6.0-beta.1\":5,\"1.17.3-beta.1\":2,\"2.0.1\":167,\"2.1.2\":4134,\"1.17.6\":439,\"2.4.0-beta.3\":1,\"2.2.0\":394,\"1.19.0\":928,\"1.17.3\":4,\"2.3.2-beta.1\":1,\"2.5.0-beta.2\":114,\"2.4.0-beta.5\":2,\"2.4.3\":317536,\"2.5.0-beta.5\":49,\"2.6.0-beta.3\":116,\"2.3.2\":8370,\"1.15.2\":471,\"2.3.0-beta.1\":1,\"1.14.1\":452,\"2.2.2\":1824,\"2.5.0-beta.7\":3,\"1.15.3\":1233,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.14.1-beta.1\":1,\"1.16.1\":300,\"1.16.0-beta.1\":2,\"1.17.8\":3885,\"2.3.0\":4684,\"1.17.7\":278,\"2.6.0-beta.2\":120,\"2.1.0\":292,\"2.0.3\":95,\"1.17.1\":7229,\"2.6.0\":245357,\"2.3.2-beta.4\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.5.2\":35642,\"1.15.4\":716,\"2.4.0-beta.4\":1,\"2.2.3\":11762,\"1.19.2\":492,\"2.3.1\":14256,\"1.20.0\":71043,\"2.4.2\":8927,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":2,\"2.0.0\":169,\"1.14.0-beta.4\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.5.0\":7996,\"1.17.2\":341,\"2.3.1-beta.1\":1,\"2.3.0-beta.2\":1,\"2.5.0-beta.1\":138,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.14.0-beta.5\":1,\"2.3.3-beta.1\":1,\"2.0.0-beta.1\":110,\"2.0.4\":1459,\"1.19.1\":9836,\"2.0.2\":17,\"1.15.0\":5,\"1.19.3\":2649,\"2.4.0-beta.1\":1,\"2.5.0-beta.3\":16,\"2.4.0-beta.2\":12,\"1.17.4\":5157,\"2.4.2-beta.2\":1,\"2.4.1\":44289,\"2.3.2-beta.2\":1,\"2.5.0-beta.4\":1,\"1.14.0\":33,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.17.5\":180,\"2.2.1\":3203,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.4.0-beta.6\":1,\"0.0.0-reserved\":2,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1}}\n{\"date\":\"2025-09-21\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.7.2\":2704,\"1.17.1\":8840,\"1.13.0\":1469,\"1.12.2\":12,\"1.7.0\":10,\"1.17.2\":394,\"1.20.0\":8942,\"1.19.0\":23,\"1.8.2\":26,\"1.9.1\":9,\"1.17.7\":58,\"1.17.5\":3,\"1.4.0-beta.5\":1,\"1.8.1\":7,\"1.4.0\":1,\"1.3.0\":46,\"1.17.8\":3000,\"1.13.2\":135,\"1.14.1\":1,\"1.10.0\":4,\"1.19.3\":6027,\"1.9.2\":31,\"1.16.1\":27,\"1.13.1\":78,\"1.10.1\":51,\"1.8.0-beta.13\":3,\"1.11.0\":1,\"1.14.0\":36,\"1.19.1\":470,\"1.15.2\":51,\"1.17.4\":470,\"1.2.1\":28,\"1.12.0\":14,\"1.17.0\":210,\"1.17.6\":9,\"1.15.4\":81,\"1.7.1\":3,\"1.7.3\":2741,\"1.5.0\":4,\"1.10.2\":1}}\n{\"date\":\"2025-09-21\",\"package\":\"nuqs\",\"downloads\":{\"1.15.3\":1276,\"1.14.1-beta.1\":1,\"2.5.0-beta.7\":3,\"2.6.0-beta.1\":5,\"2.3.2-beta.4\":1,\"1.19.3\":2627,\"2.6.0-beta.2\":119,\"2.5.1\":20630,\"2.5.0-beta.4\":1,\"2.4.2-beta.3\":1,\"1.17.7\":287,\"1.14.0\":36,\"1.17.8\":3831,\"2.4.2-beta.2\":1,\"1.17.3\":4,\"2.0.0\":178,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.14.0-beta.4\":1,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.2.2\":1874,\"2.4.0-beta.5\":2,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":2,\"2.4.3\":317957,\"2.6.0-beta.3\":117,\"2.3.0\":4777,\"1.17.5\":186,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"1.17.1\":7259,\"2.0.3\":98,\"1.18.0\":189,\"2.5.0-beta.6\":314,\"2.0.2\":20,\"1.15.0\":5,\"1.19.1\":9845,\"2.0.4\":1442,\"2.1.0\":295,\"2.4.0-beta.2\":12,\"2.5.0-beta.3\":15,\"1.16.0-beta.1\":2,\"2.4.0-beta.1\":1,\"1.17.4\":5104,\"2.3.1\":14426,\"2.0.0-beta.1\":110,\"2.2.0\":393,\"1.20.0\":71007,\"2.4.2\":9024,\"2.4.0-beta.4\":1,\"1.19.0\":926,\"2.3.2-beta.1\":1,\"2.3.0-beta.1\":1,\"1.14.1\":453,\"1.17.3-beta.1\":2,\"1.16.0\":115,\"1.17.0\":889,\"2.1.1\":4771,\"2.5.0\":7983,\"1.16.1\":294,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"1.19.0-beta.1\":1,\"2.3.2\":8362,\"2.5.0-beta.5\":45,\"2.3.1-beta.1\":1,\"1.17.2\":340,\"2.2.1\":3218,\"2.6.0\":240597,\"2.4.0-beta.3\":1,\"2.1.2\":4143,\"1.17.6\":441,\"2.0.1\":165,\"2.4.1\":44226,\"2.5.2\":34989,\"1.15.4\":743,\"2.5.0-beta.1\":143,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"0.0.0-reserved\":2,\"2.5.0-beta.2\":101,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":1,\"2.3.3-beta.1\":1,\"2.4.0\":12904,\"2.4.0-beta.6\":1,\"2.3.2-beta.2\":1,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.2.3\":11837,\"1.19.2\":487,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.15.2\":481,\"1.14.0-beta.5\":1,\"2.3.0-beta.2\":1}}\n{\"date\":\"2025-09-22\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.9.2\":35,\"1.19.3\":5948,\"1.8.0-beta.13\":3,\"1.8.1\":7,\"1.13.2\":134,\"1.17.8\":2872,\"1.10.2\":1,\"1.7.3\":2745,\"1.8.2\":26,\"1.9.1\":9,\"1.17.5\":3,\"1.7.1\":3,\"1.11.0\":1,\"1.20.0\":8991,\"1.17.6\":10,\"1.5.0\":4,\"1.7.0\":10,\"1.13.0\":1442,\"1.3.0\":46,\"1.17.2\":391,\"1.17.4\":474,\"1.2.1\":28,\"1.15.2\":51,\"1.15.4\":81,\"1.10.1\":51,\"1.14.0\":36,\"1.10.0\":3,\"1.14.1\":1,\"1.0.1\":1,\"1.19.1\":467,\"1.17.7\":58,\"1.17.1\":8922,\"1.17.0\":211,\"1.12.0\":14,\"1.4.0-beta.5\":1,\"1.12.2\":18,\"1.16.1\":26,\"1.19.0\":23,\"1.7.2\":2755,\"1.4.0\":3,\"1.8.4\":2,\"1.13.1\":84}}\n{\"date\":\"2025-09-22\",\"package\":\"nuqs\",\"downloads\":{\"1.15.2\":515,\"1.16.0\":102,\"2.5.0-beta.5\":32,\"1.19.0\":915,\"1.19.2\":455,\"2.2.3\":12000,\"2.5.0-beta.1\":134,\"1.17.3\":5,\"2.5.0\":7870,\"1.15.4\":744,\"1.17.3-beta.1\":1,\"2.3.2\":8342,\"2.2.0\":386,\"2.0.2\":18,\"1.15.0\":4,\"1.19.1\":9877,\"2.0.1\":168,\"1.17.1\":7266,\"2.0.3\":102,\"2.1.2\":4195,\"1.17.6\":593,\"2.5.0-beta.3\":14,\"2.4.0-beta.2\":11,\"2.3.1\":14628,\"1.17.4\":5032,\"1.16.0-beta.1\":1,\"1.17.2\":339,\"2.6.0-beta.3\":131,\"2.5.0-beta.7\":3,\"2.2.2\":1885,\"1.15.3\":1306,\"2.4.1\":44363,\"2.6.0\":244357,\"1.17.0\":905,\"2.1.1\":4792,\"2.0.4\":1455,\"2.1.0\":295,\"2.5.0-beta.6\":315,\"2.5.2\":34125,\"1.20.0\":69871,\"2.4.2\":9085,\"0.0.0-reserved\":1,\"2.5.0-beta.2\":92,\"2.4.3\":317608,\"2.4.0-beta.5\":1,\"2.4.2-beta.1\":1,\"2.0.0\":219,\"1.17.8\":3854,\"1.17.7\":285,\"1.14.1\":441,\"1.16.1\":254,\"1.18.0\":184,\"2.0.0-beta.1\":109,\"2.2.1\":3289,\"2.3.0\":4799,\"1.17.5\":206,\"2.6.0-beta.2\":83,\"2.5.1\":20677,\"2.4.0\":12801,\"1.19.3\":2622,\"1.14.0\":42,\"2.6.0-beta.1\":2}}\n{\"date\":\"2025-09-23\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.17.1\":9016,\"1.20.0\":9165,\"1.14.1\":1,\"1.7.0\":10,\"1.13.0\":1492,\"1.7.3\":2764,\"1.8.1\":7,\"1.12.2\":32,\"1.13.1\":85,\"1.13.2\":115,\"1.9.2\":46,\"1.17.6\":24,\"1.15.4\":67,\"1.3.0\":45,\"1.10.0\":3,\"1.17.8\":3070,\"1.10.1\":66,\"1.8.2\":21,\"1.9.1\":7,\"1.17.5\":2,\"1.17.4\":501,\"1.4.0\":3,\"1.8.4\":2,\"1.16.1\":24,\"1.5.0\":4,\"1.4.0-beta.5\":1,\"1.12.0\":20,\"1.17.0\":169,\"1.8.0-beta.13\":2,\"1.11.0\":1,\"1.17.2\":422,\"1.17.7\":56,\"1.19.1\":489,\"1.7.2\":2827,\"1.19.0\":46,\"1.15.2\":51,\"1.19.3\":5437,\"1.10.2\":1,\"1.14.0\":53,\"1.2.1\":29,\"1.7.1\":4,\"1.0.1\":1}}\n{\"date\":\"2025-09-23\",\"package\":\"nuqs\",\"downloads\":{\"2.5.0-beta.7\":8,\"2.2.2\":2023,\"2.4.1\":44494,\"1.17.8\":3818,\"1.17.7\":316,\"1.17.2\":269,\"2.4.3\":317640,\"2.5.0-beta.2\":92,\"1.15.3\":1368,\"2.5.0-beta.1\":105,\"2.2.0\":412,\"1.19.0\":868,\"1.15.4\":782,\"2.0.1\":127,\"1.14.0\":53,\"2.6.0-beta.2\":83,\"2.5.1\":20903,\"1.17.3\":5,\"2.2.3\":11891,\"1.19.2\":401,\"1.15.2\":530,\"1.16.0\":90,\"1.17.3-beta.1\":1,\"2.5.0-beta.5\":18,\"2.6.0-beta.3\":173,\"2.0.0\":224,\"2.1.0\":308,\"2.4.0-beta.5\":1,\"2.4.2-beta.1\":1,\"2.0.2\":19,\"1.15.0\":4,\"1.19.1\":9899,\"2.5.0-beta.3\":14,\"2.3.1\":14824,\"2.4.0-beta.2\":8,\"2.5.0-beta.6\":283,\"1.17.4\":5063,\"1.18.0\":178,\"2.4.0\":12303,\"2.2.1\":3478,\"1.17.5\":221,\"1.14.1\":368,\"2.6.0\":254327,\"1.17.1\":6491,\"2.0.3\":135,\"2.0.0-beta.1\":98,\"2.6.0-beta.1\":1,\"1.19.3\":2816,\"1.20.0\":69401,\"2.4.2\":9242,\"2.5.0\":7642,\"2.3.2\":7939,\"2.3.0\":4866,\"1.16.1\":302,\"2.0.4\":1420,\"1.16.0-beta.1\":1,\"0.0.0-reserved\":1,\"2.1.2\":4280,\"1.17.6\":597,\"2.1.1\":4791,\"1.17.0\":936,\"2.5.2\":32263}}\n{\"date\":\"2025-09-24\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.7.3\":2726,\"1.14.0\":71,\"1.4.0-beta.5\":1,\"1.8.1\":7,\"1.12.2\":37,\"1.15.4\":66,\"1.17.6\":41,\"1.13.2\":111,\"1.16.1\":21,\"1.13.1\":79,\"1.9.1\":7,\"1.8.2\":9,\"1.17.1\":8622,\"1.4.0\":3,\"1.0.1\":1,\"1.19.0\":72,\"1.20.0\":9186,\"1.3.0\":38,\"1.10.1\":75,\"1.13.0\":1488,\"1.7.0\":10,\"1.5.0\":5,\"1.12.1\":1,\"1.17.7\":61,\"1.19.1\":490,\"1.12.0\":20,\"1.17.0\":118,\"1.17.8\":3002,\"1.7.2\":2911,\"1.8.4\":2,\"1.17.4\":450,\"1.2.1\":18,\"1.10.0\":6,\"1.11.0\":1,\"1.8.0-beta.13\":1,\"1.19.3\":5447,\"1.9.2\":46,\"1.10.2\":1,\"1.7.1\":4,\"1.16.0\":4,\"1.15.2\":45,\"1.17.2\":409}}\n{\"date\":\"2025-09-24\",\"package\":\"nuqs\",\"downloads\":{\"2.2.0\":423,\"2.5.2\":31635,\"1.15.4\":779,\"1.19.0\":893,\"1.17.1\":7024,\"2.0.3\":139,\"2.6.0-beta.2\":66,\"1.19.3\":3297,\"2.0.0-beta.1\":80,\"2.4.3\":308944,\"1.17.2\":241,\"2.6.0-beta.3\":247,\"1.17.3\":2,\"2.5.0-beta.1\":96,\"2.3.0\":4568,\"1.17.5\":227,\"2.4.1\":43717,\"1.15.2\":566,\"2.4.0\":11946,\"2.5.0\":7435,\"2.5.0-beta.3\":24,\"2.4.0-beta.2\":25,\"1.17.4\":5264,\"1.19.1\":9605,\"1.15.0\":4,\"2.0.2\":22,\"2.1.0\":353,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.18.0\":201,\"1.15.3\":1374,\"2.5.0-beta.7\":8,\"2.6.0\":256840,\"1.17.8\":3862,\"1.14.0\":57,\"2.5.1\":20733,\"2.6.0-beta.1\":1,\"2.3.2\":7621,\"2.5.0-beta.5\":29,\"2.5.0-beta.2\":70,\"2.1.2\":4304,\"1.17.6\":583,\"2.4.2\":9381,\"1.20.0\":64815,\"2.0.1\":76,\"2.2.2\":2079,\"1.16.0\":86,\"2.3.1\":14535,\"1.16.0-beta.1\":1,\"2.0.4\":1377,\"2.2.1\":3553,\"1.17.7\":349,\"2.2.3\":11960,\"1.19.2\":350,\"2.4.0-beta.5\":1,\"2.4.2-beta.1\":1,\"2.0.0\":281,\"1.14.1\":327,\"1.17.0\":1000,\"2.1.1\":4445,\"2.5.0-beta.6\":268,\"1.16.1\":330,\"1.17.3-beta.1\":1}}\n{\"date\":\"2025-09-25\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.7.2\":2987,\"1.13.1\":104,\"1.12.2\":33,\"1.16.1\":16,\"1.19.3\":4923,\"1.13.2\":96,\"1.9.2\":50,\"1.17.6\":46,\"1.15.4\":57,\"1.20.0\":8919,\"1.8.2\":5,\"1.9.1\":8,\"1.17.8\":3045,\"1.8.4\":29,\"1.16.0\":4,\"1.11.0\":1,\"1.8.0-beta.13\":1,\"1.7.0\":11,\"1.10.2\":1,\"1.10.1\":66,\"1.4.0\":3,\"1.12.1\":1,\"1.19.1\":409,\"1.17.7\":62,\"1.13.0\":1612,\"1.17.1\":8728,\"1.15.2\":54,\"1.17.4\":512,\"1.2.1\":24,\"1.17.2\":397,\"1.7.1\":4,\"1.17.0\":152,\"1.12.0\":21,\"1.3.0\":15,\"1.10.0\":3,\"1.19.0\":80,\"1.8.1\":7,\"1.14.0\":80,\"1.5.0\":5,\"1.7.3\":2724,\"1.4.0-beta.5\":1,\"1.0.1\":1}}\n{\"date\":\"2025-09-25\",\"package\":\"nuqs\",\"downloads\":{\"2.6.0-beta.1\":2,\"2.2.2\":1871,\"2.2.3\":12393,\"1.19.2\":282,\"1.15.2\":554,\"2.4.0\":11963,\"2.4.1\":45037,\"2.0.1\":53,\"2.5.0\":6949,\"1.16.0\":87,\"2.3.2\":7252,\"2.5.0-beta.2\":95,\"2.6.0-beta.3\":276,\"1.17.2\":240,\"2.1.0\":343,\"2.0.2\":20,\"1.15.0\":3,\"2.0.4\":1385,\"2.4.3\":304003,\"2.0.0\":330,\"1.18.0\":238,\"2.3.1\":14498,\"2.5.1\":20116,\"1.15.1\":2,\"2.5.0-beta.5\":35,\"1.17.3-beta.1\":1,\"2.6.0-beta.2\":76,\"1.14.0\":76,\"1.17.7\":387,\"1.17.3\":2,\"2.5.0-beta.7\":7,\"2.6.0\":259338,\"1.17.8\":3782,\"1.17.1\":6732,\"2.0.3\":153,\"1.19.1\":9022,\"2.0.0-beta.1\":53,\"1.19.3\":3135,\"1.17.4\":5202,\"2.5.0-beta.3\":27,\"1.20.0\":61405,\"2.4.2\":9796,\"2.4.0-beta.2\":33,\"1.17.0\":950,\"2.1.1\":4220,\"1.14.1\":278,\"2.2.1\":3551,\"2.4.0-beta.5\":1,\"2.5.0-beta.6\":258,\"1.16.0-beta.1\":1,\"1.15.3\":1377,\"1.17.5\":163,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.16.1\":335,\"2.5.0-beta.1\":52,\"2.3.0\":4389,\"2.5.2\":31288,\"1.15.4\":955,\"2.2.0\":384,\"1.17.6\":597,\"2.1.2\":4249,\"1.19.0\":865}}\n{\"date\":\"2025-09-26\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.15.4\":49,\"1.17.6\":57,\"1.20.0\":8842,\"1.12.2\":37,\"1.16.1\":14,\"1.13.1\":119,\"1.19.0\":97,\"1.19.1\":303,\"1.12.0\":11,\"1.17.0\":184,\"1.8.4\":72,\"1.3.0\":1,\"1.10.0\":3,\"1.14.1\":1,\"1.16.0\":4,\"1.0.1\":1,\"1.7.2\":3001,\"1.7.3\":2688,\"1.13.2\":89,\"1.4.0-beta.5\":1,\"1.14.0\":84,\"1.8.1\":12,\"1.10.2\":1,\"1.9.2\":56,\"1.17.7\":62,\"1.13.0\":1552,\"1.17.1\":8766,\"1.9.1\":7,\"1.8.2\":11,\"1.7.1\":6,\"1.17.4\":648,\"1.2.1\":13,\"1.7.0\":5,\"1.4.0\":3,\"1.12.1\":1,\"1.19.3\":4276,\"1.10.1\":90,\"1.5.0\":2,\"1.17.2\":298,\"1.15.2\":61,\"1.17.8\":3056}}\n{\"date\":\"2025-09-26\",\"package\":\"nuqs\",\"downloads\":{\"1.17.2\":268,\"2.0.0\":336,\"2.6.0-beta.1\":2,\"1.15.2\":525,\"2.4.0\":11254,\"2.5.0-beta.2\":90,\"2.4.3\":313396,\"1.19.3\":3052,\"2.5.0-beta.7\":7,\"1.14.0\":88,\"2.6.0-beta.2\":72,\"1.17.7\":462,\"1.17.1\":7543,\"2.0.3\":169,\"2.0.0-beta.1\":52,\"1.19.1\":8754,\"2.5.0-beta.3\":29,\"2.2.1\":4052,\"1.17.5\":132,\"2.6.0-beta.3\":226,\"2.5.1\":20087,\"2.4.0-beta.5\":1,\"1.19.0\":885,\"2.2.2\":1976,\"1.15.1\":2,\"2.0.1\":54,\"2.4.1\":45200,\"2.1.2\":4366,\"1.17.6\":554,\"2.2.0\":390,\"2.5.0-beta.1\":39,\"2.3.0\":4388,\"1.16.0\":95,\"2.3.2\":7431,\"2.5.0-beta.5\":48,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.4.0-beta.2\":29,\"1.17.4\":5161,\"1.20.0\":60347,\"2.4.2\":10156,\"1.15.3\":1341,\"2.0.2\":17,\"1.15.0\":4,\"1.14.1\":222,\"1.17.8\":3687,\"1.18.0\":195,\"1.16.0-beta.1\":1,\"2.5.0-beta.6\":217,\"2.3.1\":14633,\"2.5.0\":6909,\"1.17.3\":2,\"1.17.3-beta.1\":1,\"2.2.3\":12246,\"1.19.2\":291,\"2.5.2\":32484,\"1.15.4\":1129,\"2.1.0\":318,\"2.6.0\":268128,\"2.1.1\":3663,\"1.17.0\":1035,\"2.0.4\":1441,\"1.16.1\":359}}\n{\"date\":\"2025-09-27\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.14.1\":1,\"1.13.2\":84,\"1.12.1\":1,\"1.9.2\":51,\"1.15.4\":45,\"1.20.0\":8790,\"1.10.0\":4,\"1.17.8\":2736,\"1.8.2\":10,\"1.7.1\":7,\"1.10.1\":60,\"1.17.6\":56,\"1.13.1\":112,\"1.16.1\":14,\"1.16.0\":4,\"1.19.3\":4545,\"1.9.1\":6,\"1.7.3\":2596,\"1.15.2\":51,\"1.8.4\":72,\"1.4.0\":2,\"1.17.2\":230,\"1.2.1\":13,\"1.17.4\":714,\"1.7.0\":6,\"1.13.0\":1252,\"1.12.0\":9,\"1.17.0\":206,\"1.7.2\":3048,\"1.19.1\":216,\"1.10.2\":1,\"1.14.0\":78,\"1.4.0-beta.5\":1,\"1.0.1\":1,\"1.17.7\":63,\"1.19.0\":103,\"1.8.1\":18,\"1.12.2\":50,\"1.17.1\":8259,\"1.5.0\":1}}\n{\"date\":\"2025-09-27\",\"package\":\"nuqs\",\"downloads\":{\"2.5.0\":6632,\"2.3.2\":7457,\"1.16.0\":94,\"2.5.0-beta.5\":48,\"2.0.0\":310,\"2.6.0-beta.3\":200,\"1.17.2\":240,\"2.4.3\":310991,\"2.5.0-beta.2\":109,\"2.0.4\":1484,\"2.5.0-beta.6\":130,\"2.3.1\":13690,\"1.17.3\":2,\"2.2.3\":12277,\"1.19.2\":327,\"2.4.0\":10985,\"1.15.2\":528,\"1.18.0\":209,\"2.3.0\":4278,\"1.17.5\":134,\"2.2.0\":432,\"2.0.0-beta.1\":52,\"2.5.2\":32232,\"1.15.4\":1185,\"2.6.0\":272431,\"1.17.0\":1061,\"2.1.1\":3522,\"2.6.0-beta.2\":77,\"2.5.1\":20232,\"1.14.1\":156,\"1.19.3\":2964,\"2.6.0-beta.1\":2,\"1.17.7\":472,\"2.5.0-beta.3\":29,\"2.4.0-beta.2\":33,\"1.17.4\":5412,\"1.15.3\":1338,\"1.15.0\":3,\"2.0.2\":15,\"1.19.1\":8678,\"2.1.0\":444,\"1.17.1\":8315,\"2.0.3\":154,\"2.2.2\":1870,\"2.5.0-beta.7\":7,\"1.15.1\":2,\"2.4.1\":46336,\"2.7.0-beta.1\":42,\"1.20.0\":58495,\"2.4.2\":10057,\"2.1.2\":4141,\"1.17.6\":746,\"2.2.1\":4865,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.14.0\":90,\"1.16.1\":322,\"2.0.1\":42,\"2.5.0-beta.1\":31,\"1.17.8\":3521,\"1.19.0\":868}}\n{\"date\":\"2025-09-28\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.12.0\":9,\"1.14.0\":78,\"1.17.0\":217,\"1.20.0\":9004,\"1.15.4\":54,\"1.7.3\":2611,\"1.8.1\":16,\"1.2.0\":1,\"1.13.0\":1271,\"1.17.4\":729,\"1.2.1\":13,\"1.17.1\":8132,\"1.12.1\":1,\"1.13.2\":87,\"1.17.2\":229,\"1.17.7\":63,\"1.19.1\":204,\"1.8.0-beta.5\":25,\"1.10.2\":1,\"1.0.1\":1,\"1.10.1\":60,\"1.10.0\":4,\"1.14.1\":1,\"1.17.6\":56,\"1.15.2\":50,\"1.5.0\":2,\"1.7.1\":6,\"1.17.8\":2554,\"1.9.1\":4,\"1.8.2\":11,\"1.7.0\":6,\"1.13.1\":115,\"1.19.3\":4735,\"1.19.0\":103,\"1.9.2\":54,\"1.7.2\":3043,\"1.12.2\":49,\"1.16.1\":12,\"1.4.0\":2,\"1.8.4\":72,\"1.16.0\":4}}\n{\"date\":\"2025-09-28\",\"package\":\"nuqs\",\"downloads\":{\"2.1.2\":4124,\"1.17.6\":752,\"1.19.0\":878,\"1.20.0\":59005,\"2.4.2\":9939,\"2.2.1\":4972,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.4.3\":311735,\"1.17.2\":239,\"2.0.0\":393,\"2.5.0-beta.2\":115,\"2.2.0\":432,\"2.5.0-beta.3\":29,\"2.0.1\":40,\"2.5.0-beta.1\":28,\"1.15.0\":3,\"2.2.2\":1859,\"1.15.3\":1291,\"1.17.8\":3521,\"1.18.0\":225,\"1.16.1\":322,\"1.17.0\":1077,\"2.1.1\":3524,\"1.19.2\":337,\"2.5.0\":6604,\"1.14.1\":143,\"2.0.3\":156,\"1.17.1\":8335,\"1.15.2\":516,\"2.0.0-beta.1\":53,\"2.3.0\":4143,\"2.5.2\":32099,\"2.6.0-beta.3\":205,\"1.17.4\":5440,\"2.4.0-beta.2\":33,\"2.5.0-beta.6\":119,\"2.3.1\":13722,\"2.0.2\":12,\"2.0.4\":1513,\"1.19.1\":8450,\"2.6.0\":274235,\"1.15.4\":1198,\"2.1.0\":437,\"1.17.5\":127,\"1.17.7\":456,\"2.5.0-beta.7\":7,\"1.15.1\":2,\"2.4.1\":46471,\"1.19.3\":2986,\"2.6.0-beta.1\":2,\"2.3.2\":7418,\"1.16.0\":95,\"2.5.0-beta.5\":53,\"2.2.3\":12190,\"2.5.1\":19891,\"2.4.0\":10912,\"1.14.0\":88,\"1.17.3\":2,\"2.6.0-beta.2\":77,\"2.7.0-beta.1\":67}}\n{\"date\":\"2025-09-29\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.10.0\":4,\"1.13.0\":1281,\"1.17.6\":55,\"1.15.4\":59,\"1.19.3\":4828,\"1.9.2\":51,\"1.13.1\":108,\"1.7.0\":8,\"1.20.0\":9047,\"1.17.1\":8107,\"1.10.1\":60,\"1.12.1\":1,\"1.13.2\":87,\"1.17.8\":2637,\"1.15.2\":50,\"1.17.2\":229,\"1.17.4\":736,\"1.2.1\":13,\"1.8.2\":11,\"1.7.3\":2605,\"1.8.1\":15,\"1.5.0\":2,\"1.10.2\":1,\"1.14.1\":1,\"1.2.0\":1,\"1.8.0-beta.5\":85,\"1.14.0\":78,\"1.17.7\":63,\"1.19.1\":209,\"1.7.1\":6,\"1.12.0\":9,\"1.17.0\":227,\"1.9.1\":4,\"1.16.1\":13,\"1.7.2\":3123,\"1.19.0\":103,\"1.8.4\":70,\"1.16.0\":4,\"1.12.2\":43}}\n{\"date\":\"2025-09-29\",\"package\":\"nuqs\",\"downloads\":{\"2.4.3\":311570,\"2.0.0\":351,\"1.15.3\":1313,\"2.5.0-beta.7\":6,\"1.15.1\":2,\"2.5.0-beta.2\":133,\"2.4.0\":11079,\"2.4.1\":46692,\"1.17.8\":3546,\"1.15.2\":489,\"1.17.7\":452,\"2.6.0-beta.3\":190,\"1.17.2\":239,\"1.16.0\":94,\"1.19.2\":350,\"2.2.3\":11910,\"2.6.0\":268976,\"2.5.0-beta.5\":53,\"2.3.2\":7335,\"2.5.0\":6546,\"2.1.1\":3467,\"1.17.0\":1096,\"1.14.1\":146,\"2.5.0-beta.6\":112,\"1.18.0\":223,\"2.5.2\":31858,\"1.15.4\":1193,\"1.19.0\":879,\"1.16.1\":352,\"2.0.4\":1526,\"2.3.0\":4006,\"2.5.0-beta.1\":26,\"2.2.0\":431,\"1.17.3\":2,\"1.20.0\":59564,\"2.4.2\":9923,\"2.2.2\":1871,\"2.0.1\":32,\"2.1.2\":4149,\"1.17.6\":599,\"1.17.1\":8514,\"2.0.3\":156,\"2.0.0-beta.1\":53,\"1.17.5\":112,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.19.1\":8347,\"1.15.0\":3,\"2.0.2\":10,\"2.5.0-beta.3\":28,\"1.17.4\":5490,\"2.3.1\":13474,\"2.4.0-beta.2\":33,\"2.1.0\":436,\"2.2.1\":4997,\"1.14.0\":86,\"2.5.1\":20032,\"2.6.0-beta.2\":65,\"2.7.0-beta.1\":71,\"2.6.0-beta.1\":2,\"1.19.3\":2972}}\n{\"date\":\"2025-09-30\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.12.0\":10,\"1.17.0\":251,\"1.17.8\":2508,\"1.19.1\":218,\"1.8.0-beta.5\":85,\"1.17.7\":63,\"1.2.1\":12,\"1.17.4\":802,\"1.7.1\":6,\"1.17.2\":151,\"1.10.0\":4,\"1.8.2\":12,\"1.17.6\":40,\"1.15.4\":87,\"1.15.2\":44,\"1.10.1\":45,\"1.11.0\":1,\"1.20.0\":9604,\"1.13.2\":111,\"1.14.1\":1,\"1.12.1\":1,\"1.9.2\":39,\"1.10.2\":1,\"1.8.1\":19,\"1.5.0\":5,\"1.19.3\":4671,\"1.14.0\":62,\"1.7.3\":2945,\"1.2.0\":1,\"1.13.0\":1123,\"1.12.2\":28,\"1.19.0\":76,\"1.16.1\":15,\"1.9.1\":4,\"1.13.1\":93,\"1.7.2\":3500,\"1.17.1\":8086,\"1.8.4\":70,\"1.16.0\":4,\"1.7.0\":8}}\n{\"date\":\"2025-09-30\",\"package\":\"nuqs\",\"downloads\":{\"2.2.1\":5711,\"1.17.5\":90,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.20.0\":59212,\"2.4.2\":9795,\"2.0.3\":128,\"1.17.1\":8251,\"2.1.1\":3455,\"2.0.0-beta.1\":53,\"1.18.0\":231,\"2.5.0-beta.6\":118,\"1.16.1\":358,\"2.6.0\":272467,\"1.17.0\":1156,\"1.14.1\":169,\"2.0.0\":365,\"2.4.3\":315183,\"2.5.0-beta.2\":144,\"2.6.0-beta.3\":151,\"1.17.2\":265,\"2.1.0\":451,\"2.0.2\":12,\"1.15.0\":3,\"2.0.4\":1518,\"1.19.1\":8218,\"2.5.0-beta.3\":25,\"2.3.1\":12582,\"1.17.4\":5558,\"2.3.2\":7511,\"2.5.0-beta.5\":53,\"2.0.1\":47,\"1.19.0\":879,\"2.5.0\":6524,\"1.17.3\":2,\"1.19.2\":374,\"2.2.3\":12396,\"2.2.0\":420,\"1.16.0\":78,\"1.15.2\":515,\"2.3.0\":4302,\"2.5.0-beta.1\":27,\"2.5.2\":31387,\"1.15.4\":1259,\"1.15.3\":1251,\"2.1.2\":4250,\"1.17.6\":583,\"2.5.1\":19957,\"1.19.3\":2877,\"2.4.0-beta.2\":29,\"2.6.0-beta.2\":49,\"1.14.0\":81,\"2.2.2\":1817,\"2.7.0-beta.1\":78,\"1.17.7\":455,\"1.17.8\":3341,\"2.4.1\":47034,\"2.6.0-beta.1\":3,\"2.4.0\":11812,\"1.15.1\":2}}\n{\"date\":\"2025-10-01\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.20.0\":9873,\"1.9.2\":44,\"1.16.0\":1,\"1.16.1\":35,\"1.2.0\":1,\"1.13.1\":115,\"1.8.1\":21,\"1.7.3\":3048,\"1.9.1\":5,\"1.13.0\":1062,\"1.15.4\":95,\"1.14.0\":56,\"1.10.0\":1,\"1.11.0\":1,\"1.7.0\":10,\"1.19.0\":45,\"1.7.2\":3567,\"1.17.0\":307,\"1.12.0\":24,\"1.17.1\":8522,\"1.12.2\":26,\"1.8.4\":70,\"1.17.7\":59,\"1.19.1\":210,\"1.10.1\":42,\"1.13.2\":107,\"1.14.1\":1,\"1.7.1\":7,\"1.8.2\":14,\"1.17.6\":25,\"1.19.3\":4980,\"1.17.8\":2669,\"1.10.2\":1,\"1.8.0-beta.5\":85,\"1.5.0\":5,\"1.17.4\":985,\"1.17.2\":147,\"1.2.1\":12,\"1.15.2\":47}}\n{\"date\":\"2025-10-01\",\"package\":\"nuqs\",\"downloads\":{\"2.4.3\":321310,\"2.5.0-beta.3\":17,\"1.16.0\":81,\"1.15.0\":3,\"2.0.2\":10,\"1.19.1\":8154,\"2.1.0\":396,\"2.0.4\":1632,\"2.5.2\":30915,\"1.15.4\":1373,\"2.7.0-beta.1\":95,\"2.2.0\":400,\"1.19.0\":975,\"2.3.1\":12031,\"2.5.0-beta.1\":27,\"1.15.3\":1382,\"2.2.2\":1732,\"2.5.0-beta.2\":148,\"2.3.2-beta.3\":1,\"1.17.2\":300,\"2.0.0\":281,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.3.0\":4873,\"2.1.2\":4389,\"1.17.6\":608,\"2.4.1\":48281,\"2.0.1\":55,\"2.6.0-beta.1\":3,\"1.20.0\":63614,\"2.4.2\":9761,\"1.15.1\":3,\"2.2.1\":6169,\"2.0.0-beta.1\":40,\"2.4.0-beta.6\":1,\"1.17.4\":5659,\"2.4.0-beta.2\":30,\"2.5.1\":20082,\"1.17.8\":3328,\"1.17.5\":76,\"1.19.3\":2557,\"2.6.0-beta.2\":45,\"1.14.0\":77,\"2.5.0-beta.4\":1,\"1.17.7\":424,\"2.3.2\":7986,\"1.17.3\":4,\"1.16.1\":453,\"1.18.0\":246,\"2.6.0-beta.3\":95,\"2.5.0\":6637,\"2.5.0-beta.6\":128,\"2.0.3\":149,\"1.17.1\":8578,\"2.2.3\":12466,\"1.19.2\":416,\"1.19.0-beta.1\":1,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.5.0-beta.5\":41,\"2.1.1\":3529,\"1.17.0\":1139,\"1.15.2\":488,\"2.7.0\":8014,\"2.4.0\":12138,\"2.6.0\":276827,\"1.14.1\":169}}\n{\"date\":\"2025-10-02\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.10.2\":1,\"1.9.2\":36,\"1.19.3\":5424,\"1.17.1\":8511,\"1.15.2\":41,\"1.7.0\":8,\"1.13.0\":826,\"1.12.2\":36,\"1.20.0\":10635,\"1.19.0\":33,\"1.8.4\":44,\"1.13.2\":115,\"1.14.1\":1,\"1.10.1\":42,\"1.2.1\":3,\"1.17.4\":956,\"1.17.2\":211,\"1.7.1\":7,\"1.9.1\":7,\"1.17.8\":2672,\"1.12.1\":1,\"1.19.1\":170,\"1.17.7\":58,\"1.8.0-beta.5\":85,\"1.17.6\":17,\"1.15.4\":103,\"1.10.0\":1,\"1.8.2\":15,\"1.17.0\":292,\"1.12.0\":25,\"1.14.0\":44,\"1.7.3\":3098,\"1.8.1\":26,\"1.11.0\":1,\"1.5.0\":5,\"1.2.0\":1,\"1.16.1\":44,\"1.13.1\":110,\"1.7.2\":3574,\"1.16.0\":1}}\n{\"date\":\"2025-10-02\",\"package\":\"nuqs\",\"downloads\":{\"1.16.0\":87,\"2.5.0\":6986,\"1.15.3\":1538,\"1.17.8\":3103,\"2.3.2\":8442,\"2.5.0-beta.5\":30,\"1.19.0-beta.1\":1,\"2.2.3\":12153,\"1.19.2\":428,\"2.3.2-beta.4\":1,\"2.6.0-beta.1\":3,\"1.19.3\":2411,\"2.6.0\":263012,\"2.4.3\":320662,\"2.3.2-beta.2\":1,\"2.6.0-beta.2\":21,\"2.5.1\":20420,\"1.14.0\":59,\"1.16.1\":479,\"1.17.3\":4,\"1.14.1\":143,\"1.17.7\":419,\"2.5.0-beta.4\":1,\"2.3.1\":11368,\"2.3.2-beta.3\":1,\"2.1.0\":390,\"1.19.1\":8453,\"1.15.0\":3,\"2.0.2\":13,\"2.0.4\":1624,\"1.17.3-beta.1\":1,\"2.6.0-beta.3\":51,\"1.17.2\":269,\"2.5.0-beta.2\":127,\"2.3.2-beta.1\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"1.17.1\":8495,\"2.0.3\":186,\"1.18.0\":242,\"2.0.0-beta.1\":124,\"1.17.0\":1170,\"2.1.1\":3762,\"1.17.4\":5731,\"2.5.0-beta.3\":14,\"2.3.1-beta.1\":1,\"2.0.0\":240,\"2.2.1\":6645,\"2.0.1\":48,\"2.4.1\":48124,\"1.15.1\":1,\"2.5.0-beta.7\":1,\"1.20.0\":65893,\"2.4.2\":9579,\"2.5.2\":29770,\"2.2.2\":1589,\"1.17.5\":90,\"2.3.0\":4825,\"2.1.2\":4462,\"1.17.6\":637,\"1.14.1-beta.1\":1,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.19.0\":1058,\"1.15.4\":1238,\"1.17.0-beta.1\":1,\"2.5.0-beta.1\":32,\"1.16.0-beta.1\":1,\"2.5.0-beta.6\":116,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.3.1-beta.2\":2,\"2.4.0-beta.2\":22,\"2.2.0\":401,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.14.0-beta.4\":1,\"2.4.0-beta.3\":1,\"2.4.0-beta.6\":1,\"2.4.0\":12256,\"2.4.2-beta.3\":1,\"1.15.2\":479,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.7.0-beta.1\":101,\"2.7.0\":37106}}\n{\"date\":\"2025-10-03\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.14.0\":40,\"1.7.0\":5,\"1.7.1\":5,\"1.8.2\":8,\"1.19.0\":16,\"1.9.2\":29,\"1.17.8\":2688,\"1.17.7\":58,\"1.19.1\":185,\"1.7.3\":3169,\"1.12.0\":26,\"1.17.0\":263,\"1.17.1\":8174,\"1.2.0\":1,\"1.9.1\":16,\"1.8.1\":27,\"1.19.2\":1,\"1.8.0-beta.5\":85,\"1.15.2\":33,\"1.17.2\":199,\"1.20.0\":10712,\"1.12.1\":1,\"1.8.4\":1,\"1.16.1\":38,\"1.16.0\":2,\"1.12.2\":30,\"1.13.1\":90,\"1.10.1\":18,\"1.13.0\":718,\"1.15.4\":122,\"1.17.4\":830,\"1.13.2\":125,\"1.10.0\":1,\"1.7.2\":3613,\"1.5.0\":5,\"1.17.6\":4,\"1.11.0\":1,\"1.10.2\":1,\"1.19.3\":5773}}\n{\"date\":\"2025-10-03\",\"package\":\"nuqs\",\"downloads\":{\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.1.2\":4484,\"1.17.6\":647,\"2.3.0\":4953,\"2.4.1\":47177,\"1.16.1\":427,\"1.16.0\":78,\"1.19.3\":2147,\"2.3.2-beta.4\":1,\"1.14.1-beta.1\":1,\"1.14.0\":43,\"2.6.0-beta.2\":30,\"2.5.0-beta.6\":120,\"1.16.0-beta.1\":1,\"2.2.0\":398,\"2.5.2\":27939,\"1.15.4\":1041,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.2.1\":6337,\"2.3.1-beta.2\":2,\"1.17.4\":5849,\"2.4.0-beta.2\":26,\"2.4.2-beta.2\":1,\"1.17.8\":3157,\"1.17.7\":322,\"2.5.0-beta.4\":1,\"2.5.0-beta.7\":1,\"1.19.0\":1065,\"2.4.0-beta.4\":1,\"2.0.4\":1598,\"2.3.1\":10510,\"0.0.0-reserved\":1,\"2.5.0-beta.1\":30,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.4.0-beta.3\":1,\"2.2.2\":1475,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.17.1\":7182,\"2.0.3\":183,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"1.18.0\":244,\"2.4.3\":309534,\"1.14.1\":126,\"2.6.0\":238677,\"2.5.0\":6838,\"1.14.0-beta.4\":1,\"2.5.0-beta.5\":15,\"2.3.1-beta.1\":1,\"1.16.1-beta.1\":1,\"2.5.1\":20342,\"2.4.2-beta.3\":1,\"1.20.0\":65263,\"2.4.2\":9419,\"1.17.5\":83,\"1.15.0\":3,\"2.0.2\":17,\"1.19.1\":8157,\"2.5.0-beta.3\":16,\"2.4.0-beta.1\":1,\"2.0.0-beta.1\":166,\"2.4.0-beta.6\":1,\"2.7.0-beta.1\":126,\"2.3.2\":8294,\"1.15.3\":1573,\"2.3.2-beta.2\":1,\"2.3.0-beta.1\":1,\"1.17.2\":232,\"1.17.3\":4,\"2.1.0\":372,\"2.0.0\":205,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.1.1\":3770,\"1.17.0\":1143,\"2.5.0-beta.2\":109,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.6.0-beta.1\":3,\"1.15.2\":510,\"2.7.0\":63290,\"2.4.0\":12068,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.4.0-beta.5\":1,\"2.3.2-beta.1\":1,\"2.6.0-beta.3\":62,\"2.3.3-beta.1\":1,\"2.0.1\":63,\"1.19.2\":401,\"2.2.3\":12151,\"1.19.0-beta.1\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.3.0-beta.2\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1}}\n{\"date\":\"2025-10-04\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.17.8\":2867,\"1.8.2\":10,\"1.19.2\":1,\"1.17.2\":228,\"1.17.4\":752,\"1.15.2\":29,\"1.14.0\":38,\"1.15.4\":131,\"1.17.6\":4,\"1.12.2\":17,\"1.20.0\":10584,\"1.16.1\":44,\"1.16.0\":2,\"1.7.3\":3305,\"1.7.2\":3530,\"1.19.1\":203,\"1.17.7\":57,\"1.8.0-beta.5\":85,\"1.12.0\":28,\"1.17.0\":270,\"1.7.0\":4,\"1.10.2\":1,\"1.9.2\":44,\"1.19.3\":5144,\"1.17.1\":7828,\"1.5.0\":5,\"1.11.0\":1,\"1.7.1\":2,\"1.3.0\":2,\"1.17.3\":1,\"1.9.1\":14,\"1.13.0\":495,\"1.13.1\":90,\"1.4.0-beta.5\":2,\"1.8.4\":1,\"1.10.1\":27,\"1.13.2\":133,\"1.12.1\":1,\"1.2.0\":1,\"1.8.1\":19,\"1.19.0\":7}}\n{\"date\":\"2025-10-04\",\"package\":\"nuqs\",\"downloads\":{\"2.6.0\":220945,\"1.18.0\":241,\"1.15.0\":3,\"2.0.2\":23,\"1.19.1\":7957,\"1.14.1\":106,\"2.3.0-beta.1\":1,\"2.1.0\":280,\"2.5.0-beta.3\":16,\"2.4.0-beta.1\":1,\"1.17.4\":5688,\"2.3.1\":10118,\"0.0.0-reserved\":1,\"1.17.2\":219,\"2.6.0-beta.3\":58,\"1.16.1\":371,\"2.0.0\":202,\"1.16.1-beta.1\":1,\"1.17.2-beta.1\":1,\"2.5.0-beta.1\":32,\"2.3.0\":4902,\"2.4.3\":304448,\"2.4.0-beta.5\":1,\"2.3.2-beta.3\":1,\"2.4.2-beta.1\":1,\"2.5.0-beta.2\":90,\"2.3.2-beta.1\":1,\"2.1.2\":4377,\"1.17.6\":472,\"2.0.0-beta.1\":217,\"2.4.0\":12119,\"2.5.1\":19428,\"2.2.0\":345,\"2.5.0-beta.5\":15,\"2.3.2\":8365,\"2.5.0\":6486,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.0.3\":180,\"1.17.1\":6495,\"2.2.1\":5746,\"1.17.5\":78,\"1.19.0-beta.1\":1,\"2.2.3\":12343,\"1.19.2\":373,\"2.3.0-beta.2\":1,\"1.17.7\":295,\"2.5.2\":27057,\"1.15.4\":959,\"1.20.0\":65883,\"2.4.2\":9030,\"1.16.0\":70,\"2.5.0-beta.7\":2,\"2.2.2\":1653,\"1.15.1\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.3.3-beta.1\":1,\"2.4.1\":44940,\"1.17.8\":3204,\"2.0.1\":70,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.19.0\":1067,\"2.1.1\":3743,\"2.5.0-beta.6\":102,\"1.16.0-beta.1\":1,\"2.5.0-beta.4\":1,\"1.14.0\":38,\"2.6.0-beta.2\":21,\"1.17.0\":1080,\"2.0.4\":1501,\"1.19.3\":2063,\"2.3.2-beta.4\":1,\"1.15.3\":1647,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.14.0-beta.4\":1,\"2.3.1-beta.2\":2,\"2.4.2-beta.2\":1,\"2.4.0-beta.2\":24,\"2.7.0-beta.1\":150,\"2.7.0\":88132,\"2.4.2-beta.3\":1,\"2.4.0-beta.3\":1,\"2.3.1-beta.1\":1,\"2.6.0-beta.1\":3,\"2.3.2-beta.2\":1,\"1.17.3\":4,\"2.4.0-beta.6\":1,\"2.4.0-beta.4\":1,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.14.0-beta.5\":1,\"1.17.3-beta.1\":1,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.3.1-beta.3\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"1.17.0-beta.1\":1,\"1.15.2\":544,\"1.14.1-beta.1\":1}}\n{\"date\":\"2025-10-05\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.14.0\":38,\"1.4.0-beta.5\":2,\"1.7.3\":3289,\"1.12.2\":17,\"1.8.4\":1,\"1.16.1\":43,\"1.16.0\":2,\"1.4.0-beta.4\":2,\"1.7.2\":3634,\"1.17.8\":3035,\"1.17.1\":7687,\"1.7.1\":2,\"1.8.2\":11,\"1.19.2\":1,\"1.19.0\":7,\"1.13.0\":479,\"1.17.0\":271,\"1.8.0-beta.6\":1,\"1.17.2\":234,\"1.17.7\":57,\"1.15.2\":35,\"1.13.1\":86,\"1.15.4\":124,\"1.17.6\":7,\"1.8.1\":18,\"1.5.0\":4,\"1.7.0\":4,\"1.19.3\":4993,\"1.17.3\":1,\"1.13.2\":129,\"1.12.1\":1,\"1.9.1\":14,\"1.20.0\":10490,\"1.12.0\":28,\"1.9.2\":41,\"1.19.1\":208,\"1.8.0-beta.5\":60,\"1.17.4\":746,\"1.11.0\":1,\"1.3.0\":2,\"1.10.1\":27,\"1.10.2\":1}}\n{\"date\":\"2025-10-05\",\"package\":\"nuqs\",\"downloads\":{\"2.5.2\":27283,\"1.15.4\":932,\"1.17.6\":476,\"2.1.2\":4300,\"1.17.2-beta.1\":1,\"2.3.0\":4847,\"2.5.0-beta.1\":32,\"2.2.0\":345,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.2.2\":1644,\"2.5.1\":19277,\"2.4.0\":12242,\"2.4.2-beta.3\":1,\"1.17.7\":293,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.7.0-beta.1\":130,\"2.4.1\":44681,\"1.17.8\":3253,\"1.18.0\":231,\"2.5.0-beta.6\":102,\"2.3.2-beta.4\":1,\"1.19.3\":2078,\"2.5.0-beta.4\":1,\"2.6.0-beta.2\":22,\"1.14.0\":35,\"2.4.2-beta.2\":1,\"1.15.3\":1632,\"1.14.1-beta.1\":1,\"2.5.0-beta.3\":17,\"2.4.0-beta.1\":1,\"1.17.4\":5727,\"2.3.1\":9959,\"1.17.5\":78,\"2.2.1\":5706,\"1.19.1\":8082,\"1.15.0\":3,\"2.0.2\":24,\"2.0.4\":1506,\"2.4.0-beta.6\":1,\"2.0.0-beta.1\":257,\"2.1.0\":279,\"1.14.1\":107,\"2.0.3\":176,\"1.17.1\":6384,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"2.4.0-beta.3\":1,\"1.16.1\":371,\"2.6.0-beta.1\":5,\"2.4.2-beta.1\":1,\"1.17.0\":1085,\"2.6.0\":214106,\"2.5.0-beta.2\":83,\"1.17.0-beta.1\":1,\"2.0.0\":120,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"2.7.0\":104656,\"1.19.0\":1069,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"2.6.0-beta.3\":48,\"1.17.2\":225,\"2.0.1\":69,\"2.1.1\":3714,\"1.16.0-beta.1\":1,\"1.15.1\":1,\"2.5.0-beta.7\":2,\"2.3.1-beta.3\":1,\"2.4.0-beta.2\":24,\"0.0.0-reserved\":1,\"1.15.2\":570,\"2.5.0\":6391,\"1.17.3\":4,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":2,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"2.5.0-beta.5\":11,\"2.3.2\":8375,\"1.16.0\":70,\"1.14.0-beta.5\":1,\"1.20.0\":65976,\"2.4.2\":8997,\"2.3.0-beta.1\":1,\"1.19.0-beta.1\":1,\"2.2.3\":12762,\"1.19.2\":422,\"2.4.0-beta.4\":1,\"2.3.3-beta.1\":1,\"2.4.3\":302453,\"2.3.2-beta.3\":1,\"2.4.0-beta.5\":1,\"1.14.0-beta.4\":1,\"2.3.2-beta.2\":1,\"2.3.2-beta.1\":1,\"2.3.1-beta.1\":1,\"1.17.3-beta.1\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.3.0-beta.2\":1}}\n{\"date\":\"2025-10-06\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.20.0\":10486,\"1.13.2\":121,\"1.3.0\":2,\"1.10.1\":27,\"1.17.6\":9,\"1.15.4\":120,\"1.8.0-beta.6\":1,\"1.8.2\":11,\"1.7.1\":2,\"1.19.2\":1,\"1.13.0\":458,\"1.19.1\":207,\"1.9.2\":41,\"1.9.1\":16,\"1.17.8\":2873,\"1.19.3\":4917,\"1.17.3\":1,\"1.8.1\":18,\"1.5.0\":4,\"1.19.0\":7,\"1.7.3\":3287,\"1.17.1\":7730,\"1.14.0\":38,\"1.4.0-beta.5\":2,\"1.7.0\":6,\"1.12.2\":17,\"1.7.2\":3626,\"1.17.0\":263,\"1.12.0\":29,\"1.12.1\":1,\"1.17.7\":57,\"1.4.0-beta.4\":2,\"1.8.4\":2,\"1.16.1\":42,\"1.16.0\":2,\"1.10.2\":1,\"1.11.0\":1,\"1.15.2\":38,\"1.17.2\":236,\"1.17.4\":740,\"1.13.1\":86,\"1.15.3\":1}}\n{\"date\":\"2025-10-06\",\"package\":\"nuqs\",\"downloads\":{\"2.1.0\":281,\"2.5.0-beta.3\":17,\"2.4.0-beta.1\":1,\"1.17.4\":5759,\"2.3.1\":10055,\"2.4.0-beta.2\":24,\"2.0.4\":1451,\"1.15.0\":3,\"1.19.1\":8058,\"2.0.2\":24,\"2.5.0\":6475,\"2.3.2\":8448,\"2.5.0-beta.5\":12,\"2.3.2-beta.1\":1,\"2.5.0-beta.2\":65,\"2.3.2-beta.2\":1,\"2.4.3\":300537,\"2.4.0-beta.5\":1,\"2.4.2-beta.1\":1,\"2.0.0\":120,\"1.14.0-beta.4\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.17.2\":236,\"2.6.0-beta.3\":48,\"1.20.0\":65752,\"2.4.2\":9004,\"2.4.0-beta.6\":1,\"2.0.0-beta.1\":293,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.17.5\":65,\"2.1.1\":3665,\"1.15.3\":1603,\"1.18.0\":233,\"2.5.0-beta.6\":102,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"1.17.1\":6196,\"2.0.3\":183,\"2.5.2\":27375,\"1.15.4\":940,\"1.16.0\":71,\"2.5.0-beta.4\":2,\"1.14.0\":30,\"2.6.0-beta.2\":22,\"1.16.1-beta.1\":1,\"2.3.1-beta.2\":2,\"1.19.3\":2102,\"0.0.0-reserved\":1,\"1.15.2\":577,\"2.2.3\":13170,\"1.19.2\":391,\"1.19.0-beta.1\":1,\"2.2.0\":345,\"2.5.0-beta.1\":30,\"2.3.0\":4900,\"1.17.6\":473,\"2.1.2\":4156,\"1.19.0\":1077,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.17.7\":291,\"2.4.1\":44365,\"1.17.8\":3241,\"1.15.1\":1,\"2.3.1-beta.3\":1,\"2.2.2\":1645,\"2.5.0-beta.7\":2,\"2.3.3-beta.1\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"2.0.1\":69,\"2.3.1-beta.1\":1,\"2.6.0-beta.1\":5,\"2.3.2-beta.3\":1,\"2.7.0\":123964,\"2.7.0-beta.1\":132,\"2.2.1\":5691,\"1.17.3\":4,\"1.14.1-beta.1\":1,\"2.4.0\":12251,\"2.5.1\":19171,\"1.16.0-beta.1\":1,\"2.4.2-beta.2\":1,\"2.3.2-beta.4\":1,\"2.4.0-beta.3\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.17.0-beta.1\":1,\"2.3.0-beta.2\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"1.17.2-beta.1\":1,\"2.4.0-beta.4\":1,\"1.14.1\":101,\"2.3.0-beta.1\":1,\"2.6.0\":204605,\"1.16.1\":341,\"1.17.0\":1093,\"2.4.2-beta.3\":1}}\n{\"date\":\"2025-10-07\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.14.0\":29,\"1.7.3\":2690,\"1.6.0\":1,\"1.4.0-beta.5\":2,\"1.8.1\":16,\"1.13.0\":361,\"1.7.0\":9,\"1.17.3\":1,\"1.19.2\":1,\"1.8.2\":11,\"1.17.8\":2967,\"1.17.1\":7422,\"1.12.2\":15,\"1.17.0\":314,\"1.12.0\":26,\"1.9.2\":47,\"1.19.1\":243,\"1.17.7\":58,\"1.9.1\":18,\"1.15.3\":2,\"1.16.1\":42,\"1.7.2\":3121,\"1.19.3\":4061,\"1.10.1\":30,\"1.13.2\":105,\"1.17.2\":222,\"1.15.2\":39,\"1.17.4\":663,\"1.17.6\":15,\"1.15.4\":105,\"1.5.0\":1,\"1.13.1\":90,\"1.7.1\":1,\"1.10.2\":1,\"1.8.0-beta.6\":1,\"1.17.5\":1,\"1.19.0\":6,\"1.16.0\":3,\"1.8.4\":3,\"1.4.0-beta.4\":2,\"1.20.0\":9959,\"1.12.1\":1,\"1.3.0\":2}}\n{\"date\":\"2025-10-07\",\"package\":\"nuqs\",\"downloads\":{\"1.20.0\":66296,\"2.4.2\":8985,\"1.17.5\":135,\"0.0.0-snapshot.2025-01-13.16358634\":1,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"1.17.1\":6354,\"2.0.3\":178,\"2.1.2\":4091,\"1.17.6\":495,\"2.0.1\":74,\"2.2.0\":349,\"2.0.0-beta.1\":340,\"2.4.0-beta.6\":1,\"2.5.0-beta.1\":28,\"1.17.0-beta.1\":1,\"1.17.2-beta.1\":1,\"2.3.0\":4632,\"2.5.2\":27535,\"1.15.4\":906,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.15.1\":1,\"2.5.0-beta.7\":2,\"2.5.0-beta.6\":102,\"1.18.0\":250,\"1.19.1\":7908,\"2.0.2\":23,\"1.15.3\":1734,\"2.2.1\":5441,\"2.3.1-beta.2\":2,\"1.16.1-beta.1\":2,\"1.17.8\":3454,\"2.2.3\":12760,\"1.19.2\":362,\"2.3.0-beta.2\":2,\"1.19.0-beta.1\":1,\"2.7.0\":162357,\"2.4.2-beta.2\":1,\"2.4.1\":43002,\"2.1.0\":249,\"2.5.0-beta.4\":2,\"2.4.0\":11466,\"1.16.0\":72,\"2.5.1\":18300,\"2.4.2-beta.3\":2,\"1.14.0\":30,\"2.5.0-beta.3\":17,\"2.4.0-beta.1\":1,\"1.17.4\":6001,\"2.4.0-beta.2\":25,\"2.3.1\":10991,\"2.7.0-beta.1\":162,\"1.14.1\":70,\"2.5.0\":6272,\"1.17.3\":4,\"2.5.0-beta.5\":12,\"2.3.2\":8428,\"2.3.1-beta.1\":1,\"1.16.1\":290,\"2.1.1\":3874,\"1.17.0\":1067,\"2.4.3\":290011,\"2.4.2-beta.1\":1,\"2.4.0-beta.5\":1,\"2.6.0\":176490,\"1.17.2\":194,\"2.6.0-beta.3\":41,\"2.4.0-beta.3\":1,\"1.16.0-beta.1\":1,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.0.4\":1467,\"2.3.1-beta.3\":1,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"1.15.0\":2,\"0.0.0-snapshot.2024-12-19.153ba041\":1,\"1.14.1-beta.1\":1,\"1.19.0\":1056,\"2.4.0-beta.4\":1,\"2.3.3-beta.1\":1,\"1.17.7\":261,\"2.7.1\":3853,\"2.2.2\":1646,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.19.3\":2088,\"2.6.0-beta.1\":5,\"2.3.2-beta.4\":1,\"0.0.0-reserved\":1,\"1.15.2\":586,\"2.6.0-beta.2\":19,\"2.3.2-beta.2\":1,\"2.3.0-beta.1\":1,\"0.0.0-snapshot.2025-01-13.a622d9b9\":1,\"1.14.0-beta.4\":1,\"2.0.0\":93,\"2.5.0-beta.2\":49,\"2.3.2-beta.1\":1,\"2.3.2-beta.3\":1}}\n{\"date\":\"2025-10-08\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.17.8\":2835,\"1.8.2\":8,\"1.17.2\":212,\"1.15.2\":42,\"1.13.2\":115,\"1.12.1\":1,\"1.10.1\":51,\"1.17.3\":1,\"1.9.1\":19,\"1.19.2\":1,\"1.9.2\":39,\"1.10.2\":1,\"1.8.0-beta.13\":1,\"1.13.0\":192,\"1.17.5\":1,\"1.20.0\":10223,\"1.19.3\":2984,\"1.13.1\":103,\"1.7.2\":3032,\"1.8.4\":8,\"1.16.1\":22,\"1.4.0-beta.4\":2,\"1.15.3\":2,\"1.16.0\":2,\"1.17.1\":7329,\"1.3.0\":2,\"1.17.4\":562,\"1.7.0\":13,\"1.7.3\":2544,\"1.15.4\":98,\"1.17.6\":27,\"1.12.2\":18,\"1.8.1\":15,\"1.4.0-beta.5\":2,\"1.14.0\":14,\"1.19.0\":4,\"1.17.0\":309,\"1.12.0\":18,\"1.6.0\":1,\"1.19.1\":338,\"1.17.7\":57,\"1.8.0-beta.6\":1,\"1.10.0\":1}}\n{\"date\":\"2025-10-08\",\"package\":\"nuqs\",\"downloads\":{\"1.18.0\":249,\"2.5.0-beta.6\":89,\"2.5.0-beta.3\":19,\"2.3.1\":11970,\"2.4.0-beta.1\":3,\"1.17.4\":5913,\"2.4.0-beta.2\":9,\"1.14.1\":61,\"2.3.1-beta.2\":2,\"1.16.1-beta.1\":2,\"1.16.1\":167,\"2.5.0\":6217,\"2.0.4\":1376,\"1.19.1\":8016,\"1.15.0\":2,\"2.0.2\":21,\"2.1.0\":266,\"2.5.0-beta.5\":11,\"2.3.2\":7961,\"2.3.2-beta.2\":3,\"1.16.0-beta.1\":1,\"2.1.1\":3814,\"0.0.0-reserved\":1,\"1.17.0-beta.1\":1,\"1.16.0\":75,\"2.0.1\":74,\"1.14.1-beta.1\":1,\"1.15.3\":1760,\"2.3.3-beta.1\":3,\"2.0.0-snapshot.2024-05-19.17e1fabc\":1,\"1.19.0\":997,\"2.4.0-beta.4\":3,\"2.3.1-beta.1\":1,\"2.3.2-beta.4\":3,\"1.19.3\":1976,\"2.2.3\":12955,\"1.19.2\":281,\"2.3.0-beta.2\":4,\"2.0.0-snapshot.2024-06-27.47926d24\":1,\"2.4.2-beta.2\":3,\"2.7.0-beta.1\":172,\"1.15.2\":619,\"1.17.7\":247,\"1.17.3\":2,\"1.14.0\":42,\"2.5.1\":17788,\"2.4.2-beta.3\":4,\"2.4.0\":11168,\"2.6.0\":149014,\"2.6.0-beta.1\":7,\"0.0.0-snapshot.2024-12-19.153ba041\":3,\"2.3.0-beta.1\":3,\"2.4.1\":42287,\"1.17.8\":3419,\"1.17.0\":1054,\"2.3.2-beta.1\":3,\"2.5.0-beta.2\":53,\"2.5.0-beta.7\":2,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"2.2.2\":1637,\"2.3.1-beta.3\":3,\"1.15.1\":2,\"2.4.3\":282416,\"2.4.0-beta.5\":3,\"2.4.2-beta.1\":3,\"2.3.2-beta.3\":2,\"1.14.0-beta.4\":1,\"2.0.0\":94,\"1.17.2\":159,\"2.6.0-beta.3\":56,\"1.17.3-beta.1\":1,\"1.14.0-beta.5\":1,\"2.5.2\":26817,\"1.15.4\":995,\"2.2.0\":362,\"2.4.0-beta.3\":3,\"2.1.2\":3947,\"1.17.6\":492,\"2.3.0\":4268,\"2.5.0-beta.1\":26,\"2.6.0-beta.2\":17,\"2.5.0-beta.4\":1,\"1.17.5\":141,\"2.2.1\":5501,\"0.0.0-snapshot.2025-01-13.16358634\":2,\"2.7.1\":30761,\"1.17.8-snapshot.2024-08-07.fb4c41b3\":1,\"1.17.1\":6316,\"2.0.3\":171,\"2.7.0\":170302,\"2.0.0-beta.1\":386,\"0.0.0-snapshot.2025-01-13.a622d9b9\":2,\"1.17.2-beta.1\":1,\"1.20.0\":66056,\"2.4.2\":9194,\"2.4.0-beta.6\":2}}\n{\"date\":\"2025-10-09\",\"package\":\"next-usequerystate\",\"downloads\":{\"1.17.8\":2800,\"1.19.2\":1,\"1.8.2\":7,\"1.13.1\":98,\"1.9.2\":37,\"1.10.2\":1,\"1.19.3\":2867,\"1.13.2\":104,\"1.17.3\":1,\"1.17.4\":531,\"1.2.1\":2,\"1.10.1\":54,\"1.8.0-beta.6\":1,\"1.17.5\":8,\"1.17.7\":57,\"1.19.1\":358,\"1.9.1\":16,\"1.15.3\":2,\"1.8.4\":8,\"1.16.1\":15,\"1.16.0\":2,\"1.15.2\":47,\"1.17.2\":120,\"1.19.0\":4,\"1.7.2\":2830,\"1.17.0\":319,\"1.12.0\":19,\"1.7.3\":2284,\"1.8.1\":16,\"1.5.0\":1,\"1.4.0-beta.4\":2,\"1.6.0\":1,\"1.20.0\":10288,\"1.4.0-beta.5\":2,\"1.14.0\":9,\"1.12.2\":15,\"1.8.0-beta.13\":1,\"1.10.0\":1,\"1.15.4\":87,\"1.17.6\":44,\"1.7.0\":14,\"1.13.0\":134,\"1.17.1\":6610,\"1.3.0\":2}}\n{\"date\":\"2025-10-09\",\"package\":\"nuqs\",\"downloads\":{\"1.18.0\":247,\"2.5.0-beta.6\":95,\"2.0.4\":1327,\"2.3.0-beta.2\":5,\"2.2.3\":12963,\"1.19.2\":272,\"2.7.0\":155407,\"1.16.0\":68,\"2.4.0\":11072,\"1.15.2\":671,\"2.4.0-beta.2\":18,\"2.5.0-beta.3\":21,\"2.4.0-beta.1\":3,\"1.17.4\":5879,\"2.5.0\":6006,\"1.16.1\":159,\"2.3.2-beta.2\":3,\"1.19.1\":7970,\"2.3.2\":7531,\"2.5.0-beta.5\":15,\"2.1.0\":268,\"1.14.0\":48,\"2.6.0-beta.2\":17,\"2.4.2-beta.3\":3,\"1.14.1\":68,\"2.3.0-beta.1\":3,\"1.17.0\":1107,\"2.1.1\":3897,\"2.6.0\":140205,\"1.19.3\":1880,\"2.6.0-beta.1\":6,\"1.14.0-beta.5\":1,\"2.7.0-beta.1\":180,\"1.16.1-beta.1\":2,\"2.2.1\":6252,\"2.7.1\":62123,\"2.2.0\":368,\"1.17.2-beta.1\":1,\"2.5.0-beta.1\":27,\"2.3.0\":4145,\"2.3.1\":11976,\"0.0.0-reserved\":1,\"1.19.0\":967,\"2.4.0-beta.4\":3,\"2.5.1\":17213,\"2.3.2-beta.4\":2,\"2.1.2\":4028,\"1.17.6\":443,\"1.17.1\":6553,\"2.0.3\":160,\"1.17.3\":2,\"2.0.0-beta.1\":376,\"2.4.0-beta.6\":2,\"2.0.2\":18,\"1.15.0\":2,\"1.20.0\":64547,\"2.4.2\":8869,\"2.5.2\":25915,\"1.15.4\":1009,\"2.4.0-beta.3\":2,\"1.17.5\":136,\"0.0.0-snapshot.2025-01-13.16358634\":3,\"2.0.1\":74,\"2.2.2\":1889,\"2.0.0-snapshot.2024-10-22.1456d503\":1,\"1.15.3\":1917,\"2.3.1-beta.3\":3,\"1.15.1\":2,\"2.0.0\":90,\"0.0.0-snapshot.2025-01-13.a622d9b9\":2,\"1.17.2\":168,\"2.6.0-beta.3\":55,\"1.17.8\":3893,\"2.4.1\":41721,\"2.4.2-beta.2\":3,\"2.3.3-beta.1\":3,\"1.17.7\":221,\"2.4.2-beta.1\":4,\"2.4.3\":280146,\"2.4.0-beta.5\":3,\"2.3.2-beta.3\":2,\"2.5.0-beta.2\":60,\"2.3.2-beta.1\":2,\"2.5.0-beta.7\":1,\"0.0.0-snapshot.2024-12-19.153ba041\":3,\"2.5.0-beta.4\":1}}\n`\n\nconst versionLineSchema = z.object({\n  package: z.union([z.literal('nuqs'), z.literal('next-usequerystate')]),\n  date: z.string().length(10),\n  downloads: z.record(z.string(), z.number())\n})\n\n// const creationDateSchema = z.record(\n//   z\n//     .string()\n//     .datetime()\n//     .transform(str => new Date(str).toISOString().slice(0, 10))\n// )\n\nexport type VersionDatum = {\n  date: string\n  nuqs: Record<string, number>\n  'next-usequerystate': Record<string, number>\n}\n\nexport async function getVersions(beta: boolean): Promise<VersionDatum[]> {\n  // const dates = await getVersionsPublicationDates()\n  return Object.values(\n    versionsData\n      .split('\\n')\n      .filter(line => {\n        const trimmed = line.trim()\n        return trimmed.length > 0 && trimmed[0] === '{'\n      })\n      .map(line => {\n        const json = JSON.parse(line)\n        return versionLineSchema.parse(json)\n      })\n      .reduce(\n        (acc, line) => {\n          if (!acc[line.date]) {\n            acc[line.date] = {\n              date: line.date,\n              nuqs: {},\n              'next-usequerystate': {}\n            }\n          }\n          acc[line.date][line.package] = Object.fromEntries(\n            Object.entries(line.downloads)\n              .filter(([version]) => {\n                if (beta) {\n                  return (\n                    version.includes('beta') || version.includes('snapshot')\n                  )\n                }\n                return (\n                  !version.includes('beta') && !version.includes('snapshot')\n                )\n              })\n              .sort(([, a], [, b]) => b - a)\n              .map(([key, value]) => [key, Math.round(value / 7)]) // Normalise to average daily downloads\n          )\n          return acc\n        },\n        {} as Record<string, VersionDatum>\n      )\n  )\n  //   const record: VersionRecord = {\n  //     date: parsed.date,\n  //     total: Object.values(parsed.downloads).reduce((a, b) => a + b, 0),\n  //     published: dates[parsed.date]?.published ?? [],\n  //     latest: getLatestForDate(parsed.date, dates),\n  //     downloads: Object.fromEntries(\n  //       Object.entries(parsed.downloads).sort(([, a], [, b]) => b - a)\n  //     ),\n  //     package: parsed.package,\n  //     relative: {}\n  //   }\n  //   record.relative = Object.fromEntries(\n  //     Object.entries(record.downloads).map(([key, value]) => [\n  //       key,\n  //       value / record.total\n  //     ])\n  //   )\n  //   return record\n  // })\n}\n\nexport function sumVersions(versions: VersionDatum[]) {\n  return versions.map(v => ({\n    date: v.date,\n    both: Object.fromEntries(\n      Object.entries(v.nuqs)\n        .map(\n          ([version, downloads]) =>\n            [\n              version,\n              downloads + (v['next-usequerystate'][version] ?? 0)\n            ] as const\n        )\n        .sort(([, a], [, b]) => b - a)\n    )\n  }))\n}\n\n// async function getVersionsPublicationDates() {\n//   const res = await fetch(`https://registry.npmjs.org/next-usequerystate`).then(\n//     r => r.json()\n//   )\n//   let latest = '0.0.0'\n//   const versionToDate = creationDateSchema.parse(res.time)\n//   const dates = Object.entries(versionToDate)\n//     .filter(([version]) => semverValid(version))\n//     .sort(([a], [b]) => semverSort(a, b))\n//     .reduce(\n//       (acc, [version, date]) => {\n//         latest = version\n//         if (!acc[date]) {\n//           acc[date] = {\n//             published: [],\n//             latest\n//           }\n//         }\n//         acc[date].published.push(version)\n//         acc[date].latest = latest\n//         return acc\n//       },\n//       {} as Record<\n//         string,\n//         {\n//           published: string[]\n//           latest: string\n//         }\n//       >\n//     )\n//   return dates\n// }\n\n// function getLatestForDate(\n//   date: string,\n//   dates: Awaited<ReturnType<typeof getVersionsPublicationDates>>\n// ): string {\n//   const datesArray = Object.keys(dates)\n//   const index = datesArray.indexOf(date)\n//   if (index !== -1) {\n//     return datesArray[index]\n//   }\n//   // Get the latest date before the given date\n//   const before = datesArray\n//     .toSorted((a, b) => new Date(b).getTime() - new Date(a).getTime())\n//     .filter(d => d < date)\n//   return dates[before[0]]?.latest ?? 'N.A.'\n// }\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/page.tsx",
    "content": "import { SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport {\n  NPMDownloads,\n  NPMDownloadsSkeleton,\n  NPMStats,\n  NPMStatsSkeleton\n} from './_components/downloads'\nimport { StarHistoryGraph, StarHistoryGraphSkeleton } from './_components/stars'\nimport { Versions } from './_components/versions'\nimport { Widget } from './_components/widget'\nimport { WidgetSkeleton } from './_components/widget.skeleton'\nimport { getVersions, sumVersions } from './lib/versions'\nimport { loadSearchParams } from './searchParams'\n\nexport const dynamic = 'force-dynamic'\n\ntype StatsPageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function StatsPage({ searchParams }: StatsPageProps) {\n  return (\n    <div className=\"mx-auto max-w-[88rem] px-4\">\n      <h1 className=\"sr-only\">Project Stats</h1>\n      <section className=\"my-4 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n        <Suspense fallback={<StarHistoryGraphSkeleton />}>\n          <StarHistoryGraph />\n        </Suspense>\n        <Widget className=\"flex h-auto flex-col gap-2\">\n          <img\n            width={814}\n            height={318}\n            alt=\"Project analytics and stats\"\n            src=\"https://repobeats.axiom.co/api/embed/3ee740e4729dce3992bfa8c74645cfebad8ba034.svg\"\n          />\n          <div className=\"flex flex-1 items-center gap-6 p-4\">\n            <Suspense fallback={<NPMStatsSkeleton />}>\n              <NPMStats />\n            </Suspense>\n          </div>\n        </Widget>\n        <Suspense fallback={<NPMDownloadsSkeleton />}>\n          <NPMDownloads />\n        </Suspense>\n        <Suspense fallback={<WidgetSkeleton />}>\n          <VersionsLoader searchParams={searchParams} />\n        </Suspense>\n      </section>\n    </div>\n  )\n}\n\n// --\n\ntype VersionsLoaderProps = {\n  searchParams: Promise<SearchParams>\n}\n\nasync function VersionsLoader({ searchParams }: VersionsLoaderProps) {\n  const { pkg, beta } = await loadSearchParams(searchParams)\n  const allVersions = await getVersions(beta)\n  const pkgVersions = pkg === 'both' ? sumVersions(allVersions) : allVersions\n  // @ts-expect-error\n  const versionsToShow = Object.entries(pkgVersions.at(-1)?.[pkg] ?? {})\n    .slice(0, 5)\n    .map(([key, _]) => key)\n  return (\n    <Suspense\n      fallback={<div className=\"animate-pulse text-center\">Loading...</div>}\n    >\n      <Versions\n        records={pkgVersions.map(v => ({\n          date: v.date,\n          // @ts-ignore\n          ...v[pkg]\n        }))}\n        versions={versionsToShow}\n      />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/stats/searchParams.ts",
    "content": "import { createLoader, parseAsBoolean, parseAsStringLiteral } from 'nuqs/server'\n\nexport const pkgOptions = ['nuqs', 'next-usequerystate', 'both'] as const\nexport const pkgParser = parseAsStringLiteral(pkgOptions).withDefault('nuqs')\n\nexport const searchParams = {\n  pkg: pkgParser,\n  beta: parseAsBoolean.withDefault(false)\n}\n\nexport const loadSearchParams = createLoader(searchParams)\n"
  },
  {
    "path": "packages/docs/src/app/(pages)/users/page.tsx",
    "content": "import { Description, H1 } from '@/src/components/typography'\nimport { cn } from '@/src/lib/utils'\nimport { Star } from 'lucide-react'\nimport { type Metadata } from 'next'\nimport { Suspense } from 'react'\nimport { type Dependent, fetchDependents } from '../_landing/dependents'\nimport { formatNumber } from '../stats/lib/format'\n\nexport const metadata = {\n  title: 'Users',\n  description: 'A list of popular projects that use nuqs.'\n} satisfies Metadata\n\nexport default function UsersPage() {\n  return (\n    <main className=\"container py-8\">\n      <header className=\"prose mb-8\">\n        <H1>{metadata.title}</H1>\n        <Description>{metadata.description}</Description>\n      </header>\n      <Suspense>\n        <UsersList />\n      </Suspense>\n    </main>\n  )\n}\n\nasync function UsersList() {\n  const dependents = await fetchDependents()\n  return (\n    <ol className=\"list-decimal space-y-2 pl-8\">\n      {dependents.map(dependent => (\n        <li key={dependent.owner + dependent.name}>\n          <LeaderboardItem dependent={dependent} />\n        </li>\n      ))}\n    </ol>\n  )\n}\n\n// --\n\ntype LeaderboardItemProps = {\n  dependent: Dependent\n}\n\nfunction LeaderboardItem({ dependent }: LeaderboardItemProps) {\n  return (\n    <div className=\"flex flex-wrap items-center gap-4\">\n      <div\n        className={cn(\n          'h-4 w-1 translate-px rounded-sm',\n          dependent.pkg === 'nuqs' ? 'bg-green-500/25' : 'bg-amber-500'\n        )}\n        aria-label={`uses ${dependent.pkg}`}\n      />\n      <img\n        src={dependent.avatarURL}\n        alt={dependent.owner + '/' + dependent.name}\n        className=\"inline-block h-6 w-6 flex-0 rounded-full\"\n      />\n      <span>\n        <a\n          href={`https://github.com/${dependent.owner}`}\n          className=\"text-muted-foreground hover:underline\"\n        >\n          {dependent.owner}\n        </a>\n        <span className=\"text-zinc-500\">{' / '}</span>\n        <a\n          href={`https://github.com/${dependent.owner}/${dependent.name}`}\n          className=\"hover:underline\"\n        >\n          {dependent.name}\n        </a>\n      </span>\n      <div className=\"ml-auto flex items-center gap-4\">\n        {dependent.version && (\n          <span className=\"font-mono text-xs text-zinc-500\">\n            {dependent.version}\n          </span>\n        )}\n        <span className=\"flex items-center gap-1 text-sm text-gray-500\">\n          {formatNumber(dependent.stars)} <Star size={12} />\n        </span>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/api/isr/.gitignore",
    "content": "invalidate-*.sh\n"
  },
  {
    "path": "packages/docs/src/app/api/isr/route.ts",
    "content": "import { revalidateTag } from 'next/cache'\nimport { NextRequest, NextResponse } from 'next/server'\n\nconst ACCEPTED_TAGS = [\n  'github',\n  'github-actions-status',\n  'npm-stats',\n  'contributors'\n]\n\nexport async function GET(req: NextRequest) {\n  const token = req.nextUrl.searchParams.get('token')\n  if (token !== process.env.ISR_TOKEN) {\n    console.log('Invalid token `%s`', req.nextUrl.toString())\n    return NextResponse.json({ error: 'Invalid token' }, { status: 400 })\n  }\n  const tag = req.nextUrl.searchParams.get('tag')\n  if (!tag || !ACCEPTED_TAGS.includes(tag)) {\n    return NextResponse.json({ error: 'Invalid tag' }, { status: 400 })\n  }\n  revalidateTag(tag, 'max')\n  return NextResponse.json({\n    at: new Date().toISOString(),\n    revalidated: tag\n  })\n}\n"
  },
  {
    "path": "packages/docs/src/app/api/search/route.ts",
    "content": "import { source, type Page } from '@/src/app/source'\nimport { createSearchAPI } from 'fumadocs-core/search/server'\n\nexport const { GET } = createSearchAPI('advanced', {\n  indexes: source\n    .getPages()\n    .filter((page): page is Page => page.data.exposeTo.includes('user'))\n    .map(page => ({\n      id: page.url,\n      title: page.data.title,\n      description: page.data.description,\n      url: page.url,\n      structuredData: page.data.structuredData\n    }))\n})\n"
  },
  {
    "path": "packages/docs/src/app/banners.tsx",
    "content": "import { Banner } from 'fumadocs-ui/components/banner'\nimport { PlayIcon } from 'lucide-react'\nimport Link from 'next/link'\nimport { type ComponentProps, Suspense } from 'react'\nimport { Countdown } from '../components/countdown'\nimport { ReactParisLogo } from '../components/react-paris'\nimport { cn } from '../lib/utils'\n\n// Note: top-level banners go into src/app/layout.tsx\n// Note: sidebar banners go into src/app/docs/layout.tsx & playground/layout.tsx\n\nexport function SideBanner() {\n  // Note: add banners here to enable them\n  return null\n}\n\nexport function NuqsV2AnnouncementTopBanner() {\n  return (\n    <Banner\n      variant=\"rainbow\"\n      className=\"text-md gap-4 font-semibold\"\n      id=\"nuqs-2-announcement\"\n    >\n      <span aria-hidden>🎉</span>\n      <Link\n        href=\"/blog/nuqs-2\"\n        className=\"decoration-slice decoration-1 transition-all hover:underline hover:underline-offset-8 focus-visible:underline focus-visible:outline-none\"\n        prefetch={false}\n      >\n        Announcing nuqs version 2\n      </Link>\n      <span aria-hidden>🎉</span>\n    </Banner>\n  )\n}\n\nexport function NuqsV2AnnouncementSidebarBanner() {\n  return (\n    <div className=\"my-2 flex justify-center gap-2 rounded-lg border border-blue-500/40 bg-blue-100/50 py-2.5 font-semibold dark:bg-blue-700/10\">\n      <span aria-hidden>🎉</span>\n      <Link\n        href=\"/blog/nuqs-2\"\n        className=\"text-blue-900 hover:underline focus-visible:underline focus-visible:outline-none dark:text-blue-100\"\n        prefetch={false}\n      >\n        Announcing nuqs v2 !\n      </Link>\n      <span aria-hidden>🎉</span>\n    </div>\n  )\n}\n\nexport function ReactParis2025SideBanner() {\n  return (\n    <div className=\"my-2 flex flex-col items-center gap-1.5 rounded-lg border border-gray-500/40 bg-gray-100/50 px-2 py-4 dark:bg-gray-700/10\">\n      <p className=\"text-muted-foreground\">🗣️ nuqs will be featured at</p>\n      <div className=\"flex gap-2\">\n        <ReactParisLogo className=\"h-12\" />\n        <p className=\"mr-1\">\n          <span className=\"text-lg font-bold text-[#002654] uppercase dark:text-[#00acff]\">\n            React\n          </span>{' '}\n          <span className=\"text-lg text-[#cd1126] uppercase dark:text-[#fe6497]\">\n            Paris\n          </span>{' '}\n          <span className=\"text-lg\">'25</span>\n          <br />\n          <a\n            href=\"https://react.paris/#tickets\"\n            className=\"text-sm hover:underline\"\n          >\n            Get your ticket now!\n          </a>\n        </p>\n      </div>\n      <Suspense>\n        <Countdown\n          targetDate={new Date('2025-03-20T15:00:00+01:00')}\n          className=\"my-2\"\n        />\n      </Suspense>\n      <p className=\"text-muted-foreground text-center text-xs\">\n        Use the code <code>Francois_Paris</code> for a 20% discount on your\n        ticket.\n      </p>\n    </div>\n  )\n}\n\n// --\n\nconst NEXTJS_CONF_2025_TALK_TIME = new Date('2025-10-22T13:55:00-07:00')\n\nexport function NextJSConf2025WideBanner({\n  className\n}: {\n  className?: string\n}) {\n  return (\n    <div\n      className={cn(\n        'dark:border-b-border flex min-h-11 flex-col items-center justify-center gap-2 border-b border-transparent bg-zinc-50 p-2 shadow-md sm:flex-row sm:gap-4 dark:bg-zinc-900/50',\n        className\n      )}\n    >\n      <p className=\"text-sm sm:text-base\">\n        Watch nuqs at <NextJSConf2025Logo />\n      </p>\n      <span\n        aria-hidden\n        className=\"text-muted-foreground hidden text-xs sm:block\"\n      >\n        •\n      </span>\n      <a\n        href=\"https://nextjs.org/conf\"\n        className=\"-ml-1.5 text-sm hover:underline sm:text-base\"\n      >\n        <PlayIcon className=\"mr-1 mb-[2px] inline-block size-4 fill-current stroke-none\" />{' '}\n        Livestream\n      </a>\n      <span\n        aria-hidden\n        className=\"text-muted-foreground hidden text-xs sm:block\"\n      >\n        •\n      </span>\n      <Suspense\n        fallback={\n          <div className=\"h-6 w-[150px] animate-pulse rounded-md bg-zinc-500/20\" />\n        }\n      >\n        <Countdown\n          targetDate={NEXTJS_CONF_2025_TALK_TIME}\n          className=\"min-w-[150px] text-lg\"\n          expiredMessage={\n            <span className=\"flex items-center gap-2 font-mono text-sm font-semibold text-red-700 dark:text-red-400\">\n              <div className=\"size-4 rounded-full bg-red-500\" />\n              LIVE\n            </span>\n          }\n        />\n      </Suspense>\n    </div>\n  )\n}\n\nexport function NextJSConf2025SideBanner({\n  className\n}: {\n  className?: string\n}) {\n  return (\n    <div\n      className={cn(\n        'mt-2 flex flex-col items-center gap-3 rounded-lg border border-gray-500/40 bg-gray-100/50 px-2 py-3 dark:bg-gray-700/10',\n        className\n      )}\n    >\n      <p>\n        Watch nuqs at <NextJSConf2025Logo />\n      </p>\n      <a\n        href=\"https://nextjs.org/conf\"\n        className=\"-ml-1.5 text-sm hover:underline\"\n      >\n        <PlayIcon className=\"mr-1 mb-[2px] inline-block size-4 fill-current stroke-none\" />{' '}\n        Livestream\n      </a>\n      <Suspense>\n        <Countdown\n          targetDate={NEXTJS_CONF_2025_TALK_TIME}\n          expiredMessage={\n            <span className=\"flex items-center gap-2 font-mono text-sm font-semibold text-red-700 dark:text-red-400\">\n              <div className=\"size-4 rounded-full bg-red-500\" />\n              LIVE\n            </span>\n          }\n        />\n      </Suspense>\n    </div>\n  )\n}\n\nexport const NextJSConf2025Logo = ({\n  className = 'align-center mb-[1px] ml-0.25 inline-block h-[1em] w-auto'\n}: ComponentProps<'svg'>) => (\n  <svg\n    viewBox=\"0 0 144 28\"\n    className={className}\n    fill=\"none\"\n    aria-label=\"Next.js Conf 25\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M0 4.00015H4.19672L12.6437 18.9013V4.00015H16.2341V23.8142H11.9264L3.58845 9.3297V23.8142H0V4.00015Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M25.5595 24.1485C24.1051 24.1485 22.831 23.826 21.7353 23.1851C20.6415 22.5442 19.7994 21.6369 19.2089 20.4652C18.6204 19.2916 18.3252 17.9257 18.3252 16.3614C18.3252 14.8172 18.6204 13.4553 19.2089 12.2736C19.7994 11.092 20.6316 10.1807 21.7075 9.53978C22.7854 8.89687 24.0417 8.5764 25.4762 8.5764C26.8752 8.5764 28.1037 8.89285 29.1618 9.52575C30.2199 10.1586 31.0402 11.0739 31.6188 12.2736C32.1993 13.4733 32.4887 14.9014 32.4887 16.5577V17.3669H21.9968C22.0721 18.6507 22.4208 19.6281 23.047 20.297C23.6731 20.966 24.5192 21.3024 25.5872 21.3024C26.36 21.3024 27.0138 21.1202 27.5469 20.7577C28.0799 20.3952 28.4484 19.8884 28.6506 19.2375L32.2687 19.4598C31.8625 20.9119 31.0659 22.0555 29.879 22.8927C28.6922 23.7299 27.2517 24.1485 25.5595 24.1485ZM28.817 14.9675C28.7437 13.7758 28.4128 12.8825 27.8223 12.2877C27.2338 11.6928 26.4511 11.3944 25.4762 11.3944C24.5192 11.3944 23.7326 11.7028 23.1163 12.3157C22.5001 12.9306 22.1276 13.8138 21.9968 14.9675H28.817Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M37.8523 16.2231L32.7164 8.91075H36.5545L39.9507 13.9899L43.236 8.91075H47.1573L42.0768 16.2512L47.3217 23.8139H43.5114L39.9785 18.4543L36.4435 23.8139H32.5519L37.8523 16.2231Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M54.9393 23.8141C53.4671 23.8141 52.3851 23.4697 51.6956 22.7807C51.0041 22.0917 50.6593 21.0142 50.6593 19.5441V11.6749H48.341V8.91103H50.6593V5.42408H54.1942V8.91103H58.0858V11.6749H54.1942V19.2096C54.1942 19.8966 54.3369 20.3753 54.6202 20.6456C54.9056 20.916 55.3712 21.0502 56.0152 21.0502H58.0858V23.8141H54.9393Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M64.1833 19.9064H60.1249V23.8131H64.1833V19.9064Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M65.0831 25.2361H66.5474C67.0982 25.2361 67.5084 25.1159 67.7759 24.8736C68.0434 24.6312 68.1761 24.2046 68.1761 23.5897V8.91096H71.7091V23.7019C71.7091 25.2461 71.3643 26.3477 70.6748 27.0086C69.9833 27.6695 68.8201 28 67.1814 28H65.0831V25.2361ZM68.0929 4H71.7646V7.18051H68.0929V4Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M80.6772 24.1485C78.561 24.1485 76.9124 23.6918 75.7354 22.7805C74.5584 21.8692 73.9124 20.6515 73.8034 19.1254L77.4196 18.9571C77.5484 19.7582 77.8754 20.3671 78.3985 20.7857C78.9236 21.2043 79.6825 21.4126 80.6772 21.4126C82.4447 21.4126 83.3284 20.8558 83.3284 19.7382C83.3284 19.3857 83.2451 19.0973 83.0787 18.873C82.9142 18.6507 82.6012 18.4544 82.1395 18.2882C81.6798 18.1199 80.9982 17.9617 80.0966 17.8135C78.5887 17.5531 77.4058 17.2327 76.5498 16.8501C75.6938 16.4696 75.0815 15.9949 74.713 15.4281C74.3464 14.8593 74.1621 14.1383 74.1621 13.2651C74.1621 11.8511 74.7051 10.7154 75.7909 9.86023C76.8767 9.00502 78.4124 8.5764 80.4018 8.5764C82.3159 8.5764 83.8099 9.03304 84.8878 9.94433C85.9638 10.8556 86.6136 12.0734 86.8336 13.5995L83.2452 13.7678C83.1164 13.0047 82.8033 12.4038 82.3059 11.9672C81.8086 11.5306 81.1646 11.3103 80.374 11.3103C79.5458 11.3103 78.9157 11.4785 78.4817 11.813C78.0498 12.1495 77.8338 12.5941 77.8338 13.1529C77.8338 13.7477 78.0537 14.1904 78.4956 14.4788C78.9374 14.7672 79.7201 15.0035 80.8436 15.1898C82.3892 15.4321 83.6078 15.7446 84.5014 16.1251C85.3931 16.5057 86.0331 16.9803 86.4195 17.5491C86.8059 18.1159 87 18.8269 87 19.6821C87 21.0961 86.4234 22.1937 85.2741 22.9768C84.1229 23.7579 82.5913 24.1485 80.6772 24.1485Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M140.192 4.75C141.881 4.75004 143.25 6.11896 143.25 7.80762V20.1924C143.25 21.881 141.881 23.25 140.192 23.25H95.8076C94.119 23.25 92.75 21.881 92.75 20.1924V7.80762C92.75 6.11896 94.119 4.75004 95.8076 4.75H140.192Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    ></path>\n    <path\n      d=\"M103.49 15.4677C103.164 17.3477 102.027 18.5 100.384 18.5C98.2327 18.5 97 16.7655 97 14.0121C97 11.2345 98.2327 9.5 100.384 9.5C101.943 9.5 103.055 10.5795 103.441 12.3747L102.052 12.4596C101.774 11.3315 101.182 10.7372 100.36 10.7372C99.0787 10.7372 98.3777 11.9016 98.3777 14.0121C98.3777 16.1105 99.0787 17.2628 100.36 17.2628C101.254 17.2628 101.858 16.6078 102.1 15.3827L103.49 15.4677Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M107.842 18.5C105.811 18.5 104.639 16.7291 104.639 14.0121C104.639 11.2709 105.811 9.5 107.842 9.5C109.872 9.5 111.056 11.2709 111.056 14.0121C111.056 16.7291 109.872 18.5 107.842 18.5ZM106.017 14.0121C106.017 16.0377 106.657 17.2628 107.842 17.2628C109.026 17.2628 109.679 16.0377 109.679 14.0121C109.679 11.9623 109.026 10.7372 107.842 10.7372C106.657 10.7372 106.017 11.9623 106.017 14.0121Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M112.36 9.68761H114.003L116.614 16.2375V9.68761H117.931V18.2995H116.179L113.665 12.0043V18.2995H112.36C112.36 14.9363 112.36 13.0507 112.36 9.68761Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M119.588 9.68761H124.99V10.9248H120.929V13.472H124.772V14.6849H120.929V18.2995H119.588V9.68761Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M126.204 12.3383C126.385 10.7008 127.545 9.5 129.31 9.5C130.965 9.5 132.162 10.5795 132.162 12.035C132.162 13.2722 131.219 14.1334 129.998 14.8612C129.37 15.2129 127.787 16.2561 127.69 17.0687H132.21V18.3059H126.046C126.046 15.7345 127.593 14.8127 129.261 13.7695C130.107 13.2358 130.784 12.6779 130.784 12.0229C130.784 11.283 130.18 10.7372 129.31 10.7372C128.367 10.7372 127.751 11.3679 127.581 12.4232L126.204 12.3383Z\"\n      fill=\"currentColor\"\n    ></path>\n    <path\n      d=\"M138.541 9.65545V10.8926H134.891L134.589 13.1487C134.976 12.7363 135.616 12.518 136.22 12.518C137.804 12.518 139 13.7552 139 15.4897C139 17.2363 137.743 18.4614 135.955 18.4614C134.359 18.4614 133.066 17.3819 132.945 15.987L134.323 15.9142C134.432 16.7147 135.096 17.2242 136.051 17.2242C136.958 17.2242 137.622 16.5085 137.622 15.4897C137.622 14.4466 136.897 13.7309 135.918 13.7309C135.278 13.7309 134.661 14.0948 134.456 14.5921H133.078L133.755 9.65545H138.541Z\"\n      fill=\"currentColor\"\n    ></path>\n  </svg>\n)\n\n// --\n\nconst REACT_ADVANCED_LONDON_2025_TALK_TIME = new Date(\n  '2025-11-28T14:00:00+00:00'\n)\n\nexport function ReactAdvancedLondon2025WideBanner({\n  className\n}: {\n  className?: string\n}) {\n  return (\n    <div\n      className={cn(\n        'dark:border-b-border flex min-h-11 flex-col items-center justify-center gap-2 border-b border-transparent bg-zinc-50 p-2 shadow-md sm:flex-row sm:gap-4 dark:bg-zinc-900/50',\n        className\n      )}\n    >\n      <p className=\"text-sm sm:text-base\">\n        Watch nuqs at React Advanced London <ReactAdvancedLondonLogo />\n      </p>\n      <span\n        aria-hidden\n        className=\"text-muted-foreground hidden text-xs sm:block\"\n      >\n        •\n      </span>\n      <a\n        href=\"https://reactadvanced.com\"\n        className=\"-ml-1.5 text-sm hover:underline sm:text-base\"\n      >\n        <PlayIcon className=\"mr-1 mb-[2px] inline-block size-4 fill-current stroke-none\" />{' '}\n        Livestream\n      </a>\n      <span\n        aria-hidden\n        className=\"text-muted-foreground hidden text-xs sm:block\"\n      >\n        •\n      </span>\n      <Suspense\n        fallback={\n          <div className=\"h-6 w-[150px] animate-pulse rounded-md bg-zinc-500/20\" />\n        }\n      >\n        <Countdown\n          targetDate={REACT_ADVANCED_LONDON_2025_TALK_TIME}\n          className=\"min-w-[150px] text-lg\"\n          expiredMessage={\n            <span className=\"flex items-center gap-2 font-mono text-sm font-semibold text-red-700 dark:text-red-400\">\n              <div className=\"size-4 rounded-full bg-red-500\" />\n              LIVE\n            </span>\n          }\n        />\n      </Suspense>\n    </div>\n  )\n}\n\nexport function ReactAdvancedLondon2025SideBanner({\n  className\n}: {\n  className?: string\n}) {\n  return (\n    <div\n      className={cn(\n        'bg-background z-0 flex flex-col items-center gap-3 border-b px-2 py-3 xl:mt-2 xl:rounded-lg xl:border',\n        'md:rounded-br-lg md:border-r',\n        'relative before:absolute before:inset-0 before:-z-1 before:rounded-lg before:bg-zinc-50 dark:before:bg-zinc-700/10',\n        className\n      )}\n    >\n      <p>\n        <ReactAdvancedLondonLogo className=\"mx-auto h-16\" />\n        <br />\n        Watch nuqs at React Advanced\n      </p>\n      <a\n        href=\"https://reactadvanced.com\"\n        className=\"-ml-1.5 text-sm hover:underline\"\n      >\n        <PlayIcon className=\"mr-1 mb-[2px] inline-block size-4 fill-current stroke-none\" />{' '}\n        Livestream\n      </a>\n      <Suspense>\n        <Countdown\n          targetDate={REACT_ADVANCED_LONDON_2025_TALK_TIME}\n          expiredMessage={\n            <span className=\"flex items-center gap-2 font-mono text-sm font-semibold text-red-700 dark:text-red-400\">\n              <div className=\"size-4 rounded-full bg-red-500\" />\n              LIVE\n            </span>\n          }\n        />\n      </Suspense>\n    </div>\n  )\n}\n\nexport function ReactAdvancedLondonLogo({\n  className = 'align-center mb-[1px] ml-0.25 inline-block h-[1em] w-auto'\n}: ComponentProps<'svg'>) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 966 560\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M260.299 390.306C268.813 390.306 268.898 416.871 253.657 417.467C247.016 417.638 242.588 406.654 242.588 397.714V335.302C242.588 326.277 247.186 313.079 254.679 313.079C268.983 312.994 253.402 342.284 263.279 352.672C264.3 353.779 265.663 354.545 267.195 354.886C268.728 355.141 270.261 354.886 271.623 354.204C272.9 353.694 274.007 352.757 274.773 351.565C275.54 350.458 276.05 349.096 276.136 347.648C276.136 347.137 276.136 346.541 276.05 346.031C275.88 345.52 275.625 345.009 275.284 344.583C274.944 344.157 274.433 343.817 274.007 343.561C273.496 343.306 272.985 343.221 272.389 343.221C271.793 343.221 271.282 343.391 270.771 343.561C270.261 343.732 269.835 344.157 269.494 344.583C269.154 345.009 268.898 345.52 268.728 346.031C268.558 346.541 268.558 347.137 268.728 347.648V347.989C267.876 347.648 267.451 346.456 267.451 344.924C268.047 336.579 275.965 314.867 275.965 305.842C268.558 305.842 261.065 305.587 253.742 305.842C246.25 306.183 239.183 309.503 234.074 314.953C228.965 320.402 226.155 327.724 226.326 335.302V398.139C226.326 414.317 238.587 426.663 253.572 425.982C269.069 425.301 278.009 413.551 278.009 397.629C278.009 378.471 260.469 381.877 260.299 389.965V390.306Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M211.435 425.555C208.625 425.64 206.411 411.421 206.241 411.166C205.901 407.93 205.305 404.78 204.794 401.63C215.607 397.628 213.138 380.343 202.835 380.769C202.835 380.769 206.922 386.133 203.006 389.539C201.303 378.64 199.515 367.827 197.982 356.843C196.449 345.945 194.661 335.046 192.958 323.977C192.958 323.637 191.085 309.247 193.725 309.247C196.364 309.247 195.513 306.437 195.513 306.437H168.181C168.181 306.437 167.33 309.332 169.884 309.332C172.438 309.332 169.969 323.466 169.884 324.233C167.074 339.133 163.924 353.948 161.114 368.934C158.304 383.919 155.495 398.905 152.685 413.89C152.515 414.657 149.534 429.131 146.725 429.302C144.255 429.387 144 432.367 144 432.367L164.946 430.919C164.946 430.919 165.712 428.025 163.073 428.195C160.688 428.28 162.476 416.7 163.073 413.805C164.265 407.334 169.629 393.456 188.02 399.927C188.616 403.928 189.127 407.845 189.808 411.762C189.893 412.528 191.766 426.322 189.042 426.577C186.317 426.832 187.339 429.557 187.339 429.557C196.194 429.046 205.134 428.535 213.904 428.025C213.904 428.025 213.734 425.215 211.35 425.385L211.435 425.555ZM176.185 390.986C172.779 391.668 169.544 393.03 166.649 395.073C168.522 385.026 170.48 375.149 172.353 365.273C174.227 355.396 176.185 345.519 178.058 335.642C179.506 344.923 180.783 354.204 182.145 363.399C183.422 372.595 184.955 381.791 186.487 390.986C183.082 390.305 179.591 390.305 176.185 390.986Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M965.214 344.412C965.214 310.269 946.567 296.135 923.493 296.135H901.781C901.781 296.135 901.525 299.2 903.995 299.2C906.464 299.2 906.975 314.441 906.975 315.293V433.389C906.975 434.411 906.805 449.481 904.165 449.226C901.526 448.971 901.951 452.206 901.951 452.206C913.446 453.058 925.026 453.994 936.52 454.846C953.89 456.293 965.384 436.454 965.384 410.911C965.384 402.141 965.299 388.347 965.299 375.32C965.299 362.293 965.214 350.117 965.214 344.412ZM927.324 445.905C926.047 445.82 924.77 445.565 923.578 445.224V306.778C944.098 307.544 948.781 317.847 948.781 343.987V374.554C948.781 387.921 948.866 402.311 948.866 411.081C948.866 428.365 940.777 446.927 927.324 445.905Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M888.837 392.434V392.605H888.752C888.752 392.605 881.259 389.795 881.259 398.905C881.259 405.461 878.705 431.26 866.274 430.92C863.294 430.835 860.228 430.579 857.333 430.324V395.67C859.036 395.755 870.02 396.691 870.02 400.012C870.02 403.077 872.404 402.737 872.404 402.737V382.217C872.404 382.217 870.02 381.706 870.02 384.771C870.02 388.092 859.036 388.007 857.333 388.007V314.612C860.399 314.612 863.549 314.612 866.699 314.697C882.536 315.122 865.422 346.456 875.384 358.546C876.321 359.738 877.598 360.59 879.045 360.931C880.493 361.271 882.026 361.186 883.473 360.59C884.835 359.994 886.027 359.057 886.879 357.78C887.73 356.503 888.156 355.055 888.241 353.608C888.241 350.883 886.368 348.499 884.495 348.499C882.196 348.329 880.578 350.713 880.578 353.353V353.693C880.152 353.267 879.812 352.757 879.641 352.161C879.471 351.565 879.386 350.969 879.641 350.373C880.237 341.092 887.986 317.251 887.986 307.119C870.701 307.034 853.332 307.034 836.132 306.948C836.132 306.948 835.792 309.929 838.346 309.929C840.9 309.929 841.071 324.318 841.071 325.51V418.148C841.071 419.34 840.9 433.73 838.346 433.474C835.792 433.304 836.132 436.369 836.132 436.369C853.842 437.476 871.468 438.668 889.178 439.945C886.879 428.451 886.112 416.615 886.964 404.865C887.219 400.608 887.986 396.521 889.178 392.434H888.837Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M805.651 396.606C814.08 396.946 814.25 425.725 799.009 424.959C792.283 424.448 787.94 412.443 787.94 402.906V337.259C787.94 327.808 792.538 314.015 800.031 314.1C814.25 314.185 798.584 345.093 808.631 356.672C809.567 357.864 810.93 358.716 812.462 359.142C813.995 359.482 815.527 359.312 816.975 358.716C818.337 358.12 819.444 357.183 820.296 355.991C821.147 354.799 821.573 353.352 821.573 351.904C821.658 350.797 821.403 349.605 820.636 348.669C819.955 347.732 818.933 347.136 817.741 346.966C816.634 347.051 815.613 347.647 814.931 348.499C814.25 349.35 813.91 350.542 813.995 351.649V351.989C813.143 351.649 812.803 350.287 812.888 348.754C813.399 339.814 821.147 316.654 821.147 306.863C813.739 306.863 806.417 306.522 799.094 306.777C784.109 307.373 771.678 319.805 771.678 337.004V402.14C771.678 418.999 783.939 433.303 799.009 434.24C814.165 435.176 823.361 423.341 823.361 405.971C823.361 385.111 805.736 387.836 805.736 396.606H805.651Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M81.8381 462.594C79.6243 462.849 78.0917 465.233 78.0917 467.873V468.043C71.9613 468.639 66.6823 454.164 60.5518 438.583C54.3362 423.001 47.0137 405.887 36.626 401.63C45.9069 398.65 53.8254 392.434 58.9341 384.09C64.5536 375.32 68.2149 363.655 68.2149 349.606C68.2149 332.747 63.021 319.379 54.7619 310.269C50.5047 305.671 45.396 302.095 39.6913 299.626C33.9865 297.157 27.7709 295.965 21.5553 296.05H0.0136232C0.0136232 296.05 -0.241812 299.285 2.39769 299.285C5.03719 299.285 5.12233 314.356 5.12233 315.633V436.028C5.12233 437.305 5.12233 452.376 2.39769 452.717C-0.326957 453.057 0.0136232 456.208 0.0136232 456.208C8.95386 455.271 17.8941 454.42 26.9195 453.483C26.9195 453.483 27.1749 450.248 24.5354 450.588C21.8959 450.844 21.6405 435.943 21.6405 434.666V402.566C26.4086 403.843 33.2202 417.041 40.0318 431.686C46.8434 446.246 53.7402 461.912 58.5935 469.065C66.1714 480.389 78.5174 477.579 82.349 474.088C83.3707 473.237 84.2222 472.13 84.8182 470.938C85.4142 469.746 85.6696 468.383 85.6696 467.021C85.6696 464.382 83.9667 462.423 81.8381 462.594ZM23.1731 394.648C22.6622 394.648 22.1514 394.733 21.6405 394.733V304.649C35.8597 304.905 52.3779 318.698 52.3779 349.436C52.3779 375.405 37.4775 393.711 23.1731 394.563V394.648Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M125.853 394.818C125.853 401.119 123.299 426.152 110.697 427.599C107.802 427.94 104.737 428.11 101.842 428.28V394.222C103.545 394.137 114.443 393.967 114.443 397.117C114.443 400.267 116.913 399.586 116.913 399.586V379.662C116.913 379.662 114.443 379.492 114.443 382.387C114.443 385.622 103.46 386.559 101.842 386.644V314.441C104.907 314.356 108.058 314.271 111.123 314.356C127.045 314.526 109.675 345.434 119.808 356.503C120.829 357.61 122.192 358.376 123.639 358.631C125.087 358.887 126.619 358.631 127.982 357.95C129.344 357.269 130.451 356.332 131.302 355.055C132.154 353.778 132.579 352.331 132.579 350.883C132.579 348.329 130.791 346.2 128.833 346.285C127.726 346.456 126.619 347.052 125.938 347.988C125.172 348.925 124.831 350.032 124.916 351.224V351.564C124.916 351.564 124.235 350.713 124.065 350.117C123.895 349.606 123.895 349.01 124.065 348.414C124.576 339.474 132.239 316.399 132.239 306.693L80.3854 306.948C80.3854 306.948 80.13 310.014 82.6843 309.928C85.2387 309.843 85.4941 324.318 85.4941 325.51V418.233C85.4941 419.425 85.3238 433.815 82.6843 434.07C80.0448 434.24 80.3854 437.306 80.3854 437.306C98.0956 436.028 115.721 434.581 133.431 433.133C131.132 422.32 130.366 411.166 131.132 400.097C131.387 396.01 132.154 392.008 133.346 388.092C133.346 388.092 125.938 386.048 125.938 394.818H125.853Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M681.592 425.044H681.507C678.782 424.874 676.483 410.569 676.483 410.229C676.143 406.993 675.547 403.928 675.036 400.693C685.849 397.457 683.38 380.173 672.992 379.832C672.992 379.832 677.164 385.452 673.163 388.602C671.46 377.703 669.842 366.89 668.139 356.076C666.436 345.178 664.818 334.364 663.286 323.636C663.201 323.296 661.242 309.162 663.797 309.162C666.351 309.162 665.585 306.352 665.585 306.352C656.474 306.352 647.364 306.267 638.338 306.267C638.338 306.267 637.487 309.076 640.041 309.076C642.851 309.076 640.211 322.7 640.041 323.466C637.146 337.685 634.081 351.819 631.442 366.124C628.717 380.343 625.737 394.307 622.842 408.441C622.757 409.207 619.777 422.66 617.052 422.575C614.583 422.49 614.242 425.214 614.242 425.214L635.188 426.066C635.188 426.066 636.039 423.256 633.4 423.171C631.186 423.171 632.804 412.187 633.4 409.463C634.677 403.332 639.871 390.39 658.262 398.053C658.858 401.885 659.454 405.801 660.135 409.718C660.221 410.484 662.009 424.278 659.369 424.193C656.73 424.107 657.581 426.917 657.581 426.917C666.436 427.258 675.376 427.684 684.232 428.109C684.232 428.109 684.061 425.214 681.592 425.129V425.044ZM646.597 388.091C643.192 388.517 639.871 389.624 636.891 391.412C638.764 381.875 640.637 372.424 642.51 362.973C644.384 353.522 646.342 344.071 648.215 334.62C649.663 343.645 651.025 352.671 652.472 361.696C653.75 370.807 655.197 379.832 656.73 388.857C653.409 387.921 649.918 387.665 646.597 388.091Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M532.762 314.27C530.208 311.63 527.142 309.502 523.737 308.054C520.331 306.607 516.755 305.841 513.008 305.841H479.206C479.206 305.841 478.865 308.565 481.505 308.565C484.144 308.565 484.229 321.763 484.229 322.529V405.546C484.229 406.312 484.059 419.594 481.505 419.594C478.95 419.594 479.206 422.234 479.206 422.234C490.36 422.234 501.684 422.489 512.923 422.745C516.584 422.745 520.246 422.064 523.566 420.701C526.972 419.339 530.037 417.296 532.592 414.656C535.146 412.017 537.19 408.951 538.552 405.546C539.914 402.14 540.595 398.564 540.51 394.902V334.364C540.51 330.703 539.914 327.042 538.552 323.636C537.19 320.23 535.146 317.08 532.592 314.44L532.762 314.27ZM524.418 394.562C524.418 406.482 515.648 416.274 505.09 416.104C503.642 416.104 502.28 415.933 500.918 415.422V312.737C502.28 312.397 503.642 312.141 505.09 312.141C515.733 312.226 524.418 322.188 524.418 334.109V394.562Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M310.96 421.383C310.96 421.383 310.279 421.383 309.938 421.638C309.597 421.808 309.342 421.979 309.086 422.319C308.831 422.575 308.746 422.915 308.576 423.256C308.49 423.596 308.49 423.937 308.576 424.278C317.431 424.022 326.456 423.852 335.396 423.596C335.396 423.596 335.737 420.872 333.183 420.872C330.628 420.872 330.288 407.589 330.288 406.823V312.823H333.949C349.871 313.078 332.501 341.346 342.634 351.649C343.655 352.756 345.018 353.522 346.465 353.863C347.913 354.203 349.53 353.948 350.893 353.352C352.17 352.756 353.192 351.904 354.043 350.712C354.809 349.605 355.32 348.243 355.491 346.881C355.491 346.37 355.491 345.774 355.32 345.348C355.15 344.837 354.895 344.412 354.554 343.986C354.213 343.56 353.788 343.219 353.277 343.049C352.766 342.879 352.255 342.709 351.744 342.623C351.233 342.623 350.637 342.794 350.126 343.049C349.616 343.305 349.19 343.645 348.849 344.071C348.509 344.497 348.168 344.922 347.998 345.518C347.827 346.029 347.742 346.625 347.742 347.136V347.392C347.061 347.136 346.721 345.944 346.806 344.497C347.402 336.323 354.895 315.036 354.895 306.096C333.012 306.181 311.13 306.181 289.077 306.266C289.077 315.377 296.826 336.748 297.422 345.008C297.507 346.54 297.166 347.562 296.23 347.988C296.315 347.817 296.4 347.732 296.4 347.647C296.4 347.136 296.4 346.54 296.23 346.029C296.059 345.518 295.804 345.008 295.378 344.582C295.037 344.156 294.527 343.815 294.016 343.645C293.505 343.39 292.994 343.305 292.398 343.305C291.802 343.305 291.291 343.475 290.78 343.645C290.269 343.901 289.844 344.241 289.503 344.667C289.162 345.093 288.907 345.604 288.737 346.114C288.566 346.625 288.566 347.221 288.652 347.732C288.822 349.094 289.333 350.457 290.099 351.564C290.951 352.671 292.057 353.607 293.335 354.118C294.697 354.714 296.23 354.799 297.677 354.544C299.124 354.203 300.487 353.437 301.423 352.33C311.385 341.687 294.271 313.334 310.023 312.993C311.215 312.908 312.407 312.908 313.599 312.908V407.334C313.599 408.356 313.514 421.468 310.96 421.553V421.383Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M441.24 394.221C441.75 397.968 442.346 401.629 443.028 405.375C443.113 406.142 444.986 419.254 442.261 419.254C439.537 419.254 440.558 421.979 440.558 421.979C449.414 421.979 458.354 422.064 467.209 422.064C467.209 422.064 467.039 419.339 464.655 419.339H464.569C461.76 419.339 459.461 405.716 459.376 405.375C459.035 402.31 458.439 399.33 457.928 396.35C468.741 393.029 466.272 376.511 455.97 376.426C455.97 376.426 459.972 381.705 456.14 384.77C454.437 374.382 452.649 363.995 451.116 353.607C449.584 343.219 447.796 332.832 446.263 322.444C446.178 322.103 444.305 308.395 446.944 308.395C449.584 308.395 448.732 305.755 448.732 305.755H421.401C421.401 305.755 420.464 308.48 423.104 308.48C425.743 308.48 423.274 321.678 423.104 322.359C420.294 336.237 417.144 350.031 414.504 363.824C411.779 377.618 408.799 391.582 405.904 405.46C405.819 406.227 402.669 419.509 400.114 419.509C397.56 419.509 397.305 422.234 397.305 422.234C404.287 422.234 411.183 422.149 418.25 422.064C418.25 422.064 419.017 419.339 416.377 419.339C414.163 419.424 415.866 408.696 416.377 405.971C417.654 399.926 422.933 387.239 441.325 394.136L441.24 394.221ZM425.573 360.759C427.446 351.478 429.404 342.283 431.278 333.087C432.725 341.857 434.002 350.542 435.45 359.227C436.897 367.826 438.174 376.766 439.707 385.366C432.98 383.748 425.828 384.855 419.868 388.346C421.741 379.15 423.7 370.04 425.573 360.674V360.759Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M756.434 428.791C753.795 428.62 753.539 414.401 753.539 413.635V324.573C753.539 323.551 753.795 309.502 756.434 309.502C759.074 309.502 758.648 306.607 758.648 306.607C749.708 306.607 740.682 306.607 731.827 306.607C731.827 306.607 731.487 309.417 734.126 309.417C736.766 309.417 736.851 323.126 736.851 324.488V372.935C733.615 361.867 730.465 350.713 727.485 339.644C724.505 328.575 721.184 317.591 718.119 306.607C710.882 306.607 703.644 306.522 696.407 306.522C696.407 306.522 696.151 309.332 698.706 309.332C701.26 309.332 701.516 322.955 701.516 324.147V411.506C701.516 412.613 701.345 426.151 698.706 426.066C696.066 425.981 696.407 428.791 696.407 428.791C703.389 429.046 710.286 429.472 717.353 429.727C717.353 429.727 717.523 426.917 714.969 426.832C712.159 426.662 712.074 411.676 712.074 411.676V342.879C716.161 357.354 720.418 371.914 724.505 386.559C728.592 401.118 732.849 416.019 736.936 430.834C744.173 431.175 751.411 431.6 758.733 432.026C758.733 432.026 758.989 429.131 756.519 428.961L756.434 428.791Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M591.431 421.553C589.047 421.468 591.345 408.1 591.431 407.334C594.326 393.285 597.22 379.321 600.03 365.272C602.84 351.223 605.82 337.089 608.715 323.04C608.885 322.274 611.78 308.821 614.505 308.821C617.23 308.821 617.23 306.011 617.23 306.011H596.454C596.454 306.011 595.688 308.736 598.157 308.736C600.967 308.736 598.242 322.529 598.157 322.955C595.518 335.216 593.134 347.477 590.664 359.653C588.195 371.828 585.641 384.004 583.171 396.265C581.213 383.919 579.255 371.743 577.382 359.567C575.508 347.392 573.72 335.046 571.847 322.87C571.762 322.104 569.548 308.651 572.358 308.651C574.912 308.736 574.146 305.926 574.146 305.926H547.24C547.24 305.926 547.411 308.651 549.88 308.651C552.604 308.651 554.988 322.359 555.074 322.785C557.287 336.663 559.416 350.712 561.545 364.761C563.673 378.81 565.972 392.774 568.186 406.823C568.356 407.164 570.229 421.042 567.505 420.957C565.036 420.957 565.717 423.682 565.717 423.682C574.827 423.937 583.853 424.193 593.048 424.448C593.048 424.448 593.815 421.723 591.345 421.638L591.431 421.553Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M563.422 479.197C551.927 479.368 542.816 487.882 542.816 498.696V540.331C542.816 550.804 551.927 559.319 563.251 559.83C574.405 558.808 583.516 551.145 583.686 540.331V498.696C583.686 488.052 574.576 479.538 563.422 479.197ZM571.085 540.161C571.085 545.781 567.934 550.804 563.251 553.274C558.568 550.634 555.418 545.781 555.418 540.161V498.525C555.418 492.735 558.568 487.882 563.251 485.243C567.934 487.882 571.085 492.735 571.085 498.525V540.161Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M510.037 479.197H484.068C484.068 479.197 483.728 481.07 485.771 481.07C487.815 481.07 487.985 490.266 487.985 490.777V548.25C487.985 548.761 487.815 557.957 485.771 557.957C483.728 557.957 484.068 559.83 484.068 559.83H510.037C521.702 559.83 531.153 551.145 531.153 540.331V498.696C531.153 487.882 521.702 479.368 510.037 479.368V479.197ZM518.467 540.161C518.467 548.505 511.826 555.147 503.652 555.147C502.46 555.147 501.608 554.976 500.501 554.806V483.625C501.523 483.455 502.545 483.284 503.652 483.284C511.826 483.284 518.467 490.096 518.467 498.44V540.076V540.161Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M362.818 531.051C362.818 531.051 357.028 529.518 357.028 535.052C357.028 539.054 355.155 554.721 345.533 555.232H338.722V490.777C338.892 489.244 339.062 481.241 341.02 481.241C342.894 481.241 342.894 479.367 342.894 479.367H322.459C322.459 479.367 322.118 481.241 324.162 481.241C326.375 481.241 326.375 491.117 326.375 491.117V548.42C326.375 548.42 326.375 558.127 324.162 558.127C322.118 558.127 322.459 560 322.459 560H363.073C358.56 541.694 362.903 531.136 362.903 531.136L362.818 531.051Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M641.245 480.985C643.289 480.985 643.118 479.112 643.118 479.112H622.513C622.513 479.112 622.343 480.985 624.386 480.985C626.43 480.985 626.515 490.01 626.515 490.691V522.195L612.04 479.197H595.437C595.437 479.197 595.267 481.07 597.31 481.07C599.354 481.07 599.524 490.095 599.524 490.776V548.249C599.524 549.101 599.354 557.956 597.31 557.956C595.267 557.956 595.437 559.829 595.437 559.829H611.444C611.444 559.829 611.615 557.956 609.741 557.956C607.528 557.956 607.443 548.079 607.443 548.079V503.122L626.43 559.744H643.033C643.033 559.744 643.374 557.871 641.33 557.871C639.287 557.871 639.031 548.76 639.031 548.249V490.776C639.031 490.095 639.202 481.07 641.33 481.07L641.245 480.985Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M393.131 479.197H392.96C381.636 479.368 372.525 487.882 372.525 498.696V540.331C372.525 550.804 381.636 559.319 392.96 559.83C404.114 558.808 413.225 551.145 413.395 540.331V498.696C413.225 488.052 404.285 479.538 393.131 479.197ZM400.623 540.161C400.623 545.781 397.473 550.804 392.79 553.274C388.107 550.634 384.957 545.781 384.957 540.161V498.525C384.957 492.735 388.107 487.882 392.79 485.243C397.473 487.882 400.623 492.735 400.623 498.525V540.161Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M470.782 480.985C472.826 480.985 472.656 479.112 472.656 479.112H452.221C452.221 479.112 451.88 480.985 454.094 480.985C456.138 480.985 456.137 490.01 456.137 490.691V522.195L441.833 479.197H425.23C425.23 479.197 424.889 481.07 427.103 481.07C429.146 481.07 429.146 490.095 429.146 490.776V548.249C429.146 549.101 428.976 557.956 427.103 557.956C425.06 557.956 425.23 559.829 425.23 559.829H441.237C441.237 559.829 441.407 557.956 439.364 557.956C437.235 557.956 437.065 548.079 437.065 548.079V503.122L456.052 559.744H472.656C472.656 559.744 473.167 557.871 470.953 557.871C468.909 557.871 468.654 548.76 468.654 548.249V490.776C468.654 490.095 468.994 481.07 470.953 481.07L470.782 480.985Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M419.979 110.234C419.979 110.808 420.027 111.369 420.063 111.942C415.477 110.545 410.903 109.112 406.412 107.643L411.679 92.1526C415.059 93.2633 418.451 94.3501 421.819 95.4011C420.648 100.166 419.979 105.123 419.979 110.234Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M392.102 85.2257L386.823 100.764C384.841 99.9993 382.918 99.223 380.983 98.4467L380.553 98.2795L380.529 80.5798L392.114 85.2257H392.102Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M501.526 95.2219C500.678 95.0667 499.734 94.9233 498.838 94.8278C499.292 95.5802 499.782 96.3684 500.236 97.1686C500.737 97.9688 501.227 98.7571 501.681 99.5572C502.135 100.357 502.624 101.205 503.03 101.994C502.278 103.881 501.442 105.875 500.487 107.87C499.543 105.923 498.492 103.988 497.346 102.041C496.199 100.095 495.053 98.2077 493.858 96.4162C493.416 95.616 492.915 94.8756 492.413 94.1709C491.171 92.3317 489.929 90.588 488.627 88.9399C488.078 88.1874 487.481 87.447 486.931 86.7543C486.131 85.763 485.343 84.8076 484.495 83.9118C484.053 83.3624 483.599 82.8131 483.098 82.2637C482.596 82.7653 482.106 83.3147 481.7 83.8521C480.852 84.7956 480.052 85.7391 479.216 86.7423C478.667 87.435 478.069 88.1874 477.568 88.9279C478.464 88.8801 479.359 88.8801 480.255 88.8801C481.199 88.8324 482.142 88.8324 483.098 88.8324C484.053 88.8324 484.985 88.8324 485.94 88.8801C487.182 90.4686 488.484 92.2123 489.726 94.0037C487.588 93.8485 485.391 93.8007 483.109 93.8007C480.828 93.8007 478.631 93.8485 476.493 94.0037C475.597 94.0037 474.702 94.0515 473.806 94.147C471.62 94.3023 469.423 94.4934 467.38 94.792C466.437 94.8875 465.541 95.0428 464.645 95.1861C463.403 95.3891 462.161 95.5802 460.967 95.831C460.274 95.9265 459.57 96.0818 458.877 96.2251C459.08 96.8701 459.271 97.515 459.522 98.2196C459.916 99.4139 460.37 100.608 460.871 101.803C461.17 102.651 461.516 103.498 461.863 104.394C462.317 103.594 462.711 102.806 463.153 102.006C463.606 101.158 464.048 100.357 464.55 99.5214C464.992 98.7212 465.493 97.933 465.995 97.1328C468.037 96.7865 470.175 96.5356 472.373 96.3804C471.131 98.1718 469.984 100.059 468.885 102.006C467.739 103.952 466.7 105.935 465.696 107.882C465.302 108.682 464.896 109.422 464.502 110.222C463.559 112.169 462.663 114.152 461.863 116.051C461.516 116.946 461.17 117.794 460.812 118.642C460.358 119.837 459.916 121.079 459.522 122.225C459.271 122.918 459.068 123.623 458.877 124.267C459.57 124.423 460.274 124.566 460.967 124.662C462.161 124.912 463.403 125.163 464.705 125.354C465.601 125.498 466.497 125.605 467.44 125.701C466.939 124.948 466.449 124.16 466.043 123.36C465.493 122.56 464.992 121.771 464.55 120.923C464.096 120.123 463.606 119.275 463.2 118.487C463.893 116.54 464.741 114.606 465.685 112.611C466.676 114.498 467.727 116.445 468.873 118.439C469.972 120.386 471.166 122.273 472.361 124.112C472.815 124.865 473.304 125.605 473.806 126.358C475.048 128.197 476.29 129.94 477.592 131.589H477.64C478.141 132.341 478.691 133.034 479.228 133.726C480.028 134.718 480.876 135.673 481.664 136.617C482.106 137.166 482.608 137.715 483.109 138.205C483.611 137.703 484.053 137.214 484.507 136.664C485.355 135.721 486.155 134.718 486.991 133.726C487.54 133.034 488.09 132.329 488.579 131.589C487.684 131.636 486.788 131.636 485.892 131.636C484.949 131.684 484.053 131.684 483.109 131.684C482.166 131.684 481.222 131.684 480.267 131.636C479.025 129.988 477.723 128.304 476.481 126.513C478.619 126.668 480.816 126.716 483.098 126.716C485.379 126.716 487.528 126.668 489.666 126.513C490.61 126.513 491.505 126.465 492.401 126.369C494.587 126.214 496.736 126.023 498.719 125.725C499.663 125.629 500.606 125.521 501.502 125.378C502.792 125.175 504.046 124.924 505.24 124.685C505.933 124.59 506.637 124.435 507.33 124.291C507.127 123.646 506.936 122.942 506.685 122.249C506.291 121.103 505.837 119.86 505.395 118.666C505.049 117.818 504.703 116.97 504.344 116.075C503.89 116.875 503.496 117.723 502.995 118.511C502.601 119.311 502.099 120.159 501.645 120.947C501.144 121.795 500.702 122.595 500.152 123.384C498.11 123.682 496.02 123.933 493.834 124.136C495.029 122.297 496.223 120.398 497.322 118.463C498.468 116.516 499.46 114.629 500.463 112.742V112.695C500.917 111.847 501.311 111.046 501.705 110.258C502.648 108.264 503.544 106.329 504.344 104.43C504.691 103.534 505.037 102.686 505.336 101.838C505.837 100.644 506.279 99.4498 506.685 98.2554C506.936 97.5627 507.139 96.9059 507.33 96.261C506.637 96.1057 505.933 95.9624 505.24 95.8668C504.046 95.616 502.804 95.413 501.502 95.2219H501.526ZM483.098 118.475C478.619 118.475 474.857 114.713 474.857 110.234C474.857 105.756 478.619 101.994 483.098 101.994C487.576 101.994 491.338 105.756 491.338 110.234C491.338 114.713 487.576 118.475 483.098 118.475Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M585.544 122.321V138.707C570.436 138.098 555.4 137.023 540.865 135.542C543.075 130.514 544.627 125.152 545.463 119.55C558.541 120.804 572.001 121.736 585.556 122.333L585.544 122.321Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M483.1 52.0959C451.045 52.0959 424.961 78.1796 424.961 110.235C424.961 142.29 451.045 168.374 483.1 168.374C515.155 168.374 541.239 142.29 541.239 110.235C541.239 78.1796 515.155 52.0959 483.1 52.0959ZM535.375 110.259C535.375 117.377 527.755 123.456 515.561 127.29C515.215 125.701 514.761 124.101 514.212 122.465C524.363 119.228 530.394 114.594 530.394 110.271C530.394 105.947 524.375 101.361 514.271 98.1246C513.626 97.9215 512.922 97.7304 512.229 97.5274C512.026 98.1723 511.835 98.877 511.584 99.5219C510.688 102.161 509.637 104.896 508.347 107.691C508.049 108.539 507.655 109.435 507.201 110.283C507.595 111.131 508.001 112.026 508.395 112.874C509.59 115.657 510.641 118.404 511.536 121.091C511.787 121.784 511.978 122.441 512.181 123.086C512.731 124.781 513.172 126.37 513.579 127.97C513.782 128.663 513.925 129.32 514.08 129.965C514.331 131.255 514.582 132.509 514.773 133.703C516.517 144.703 514.57 152.466 509.243 155.559C507.655 156.455 505.863 156.908 503.917 156.908C499.235 156.908 493.61 154.364 487.543 149.444C486.599 148.644 485.596 147.856 484.652 146.9C485.799 145.801 486.993 144.607 488.14 143.317C488.988 144.117 489.836 144.858 490.684 145.562C497.408 151.044 503.427 153.182 506.759 151.235C510.091 149.348 511.238 143.066 509.84 134.503C509.685 133.404 509.446 132.318 509.243 131.219C509.088 130.526 508.897 129.869 508.742 129.177C508.049 129.32 507.344 129.475 506.652 129.63C503.917 130.18 500.979 130.622 497.945 130.92C497.002 131.076 496.058 131.171 495.103 131.219C494.601 131.971 494.052 132.712 493.514 133.464C491.723 135.901 489.836 138.194 487.985 140.331C487.543 140.881 487.041 141.382 486.587 141.872C485.441 143.114 484.294 144.308 483.1 145.407C482.598 145.957 482.109 146.458 481.559 146.9C480.616 147.796 479.612 148.644 478.669 149.444C472.59 154.376 466.965 156.908 462.295 156.908C460.348 156.908 458.557 156.455 456.968 155.559C451.642 152.477 449.647 144.703 451.439 133.703C451.642 132.509 451.881 131.267 452.131 129.965C453.672 130.466 455.272 130.861 456.956 131.207C456.753 132.306 456.503 133.392 456.359 134.491C454.962 143.054 456.108 149.325 459.441 151.223C462.773 153.17 468.804 151.02 475.516 145.551C476.364 144.858 477.212 144.105 478.06 143.305C478.561 142.851 479.111 142.409 479.601 141.86C479.099 141.358 478.657 140.869 478.155 140.319C476.316 138.182 474.477 135.889 472.626 133.452C472.076 132.7 471.575 131.959 471.037 131.207C470.094 131.159 469.15 131.064 468.255 130.908C465.221 130.61 462.283 130.156 459.548 129.618C458.855 129.475 458.151 129.32 457.458 129.165C455.762 128.818 454.174 128.412 452.633 127.97C451.94 127.767 451.283 127.576 450.639 127.325C438.445 123.492 430.825 117.425 430.825 110.295C430.825 103.165 438.397 97.1572 450.543 93.3712C450.937 94.9119 451.391 96.5122 451.94 98.1484C441.837 101.385 435.817 105.959 435.817 110.295C435.817 114.63 441.837 119.252 452 122.488C452.645 122.691 453.35 122.883 454.042 123.086C454.245 122.441 454.436 121.796 454.687 121.091C455.583 118.404 456.634 115.669 457.828 112.874C458.222 112.026 458.628 111.131 459.023 110.283C458.581 109.435 458.175 108.539 457.876 107.691C456.586 104.908 455.535 102.161 454.639 99.5219C454.389 98.877 454.198 98.1723 453.995 97.5274C453.445 95.8315 453.003 94.2431 452.645 92.6427C452.442 91.95 452.251 91.2453 452.143 90.6004C449.361 78.1557 450.854 68.5416 456.968 65.0184C463.095 61.4354 472.196 65.0184 481.655 73.6293C480.46 74.7281 479.266 75.9702 478.072 77.26C471.897 71.6348 466.272 68.649 462.343 68.649C461.244 68.649 460.3 68.8521 459.453 69.3417C455.714 71.4796 454.723 79.0037 456.968 89.3583C457.112 90.0033 457.267 90.7079 457.47 91.4484C458.163 91.3051 458.867 91.1498 459.56 91.0543C462.295 90.4571 465.185 90.0033 468.219 89.7047C469.162 89.5494 470.106 89.4539 471.061 89.4061C471.563 88.6059 472.112 87.8655 472.65 87.113C474.441 84.7244 476.28 82.4314 478.179 80.2936C478.621 79.7442 479.123 79.2426 479.624 78.7529C480.771 77.463 481.965 76.209 483.16 75.1222C483.661 74.5728 484.211 74.0712 484.7 73.6293C485.644 72.7814 486.587 71.9334 487.543 71.1929C496.201 64.1226 503.917 61.937 509.243 65.0184C515.37 68.5535 516.863 78.1557 514.068 90.6004C512.528 90.0988 510.927 89.7047 509.243 89.3583C511.489 79.0037 510.485 71.4915 506.759 69.3417C505.959 68.8401 504.968 68.649 503.869 68.649C500.489 68.649 495.759 70.8943 490.672 75.0267C489.824 75.7193 489.024 76.424 488.188 77.2122C487.686 77.6661 487.137 78.108 486.647 78.6573C487.089 79.159 487.59 79.7083 488.044 80.2458C489.883 82.3836 491.723 84.6767 493.574 87.113C494.123 87.8655 494.673 88.6059 495.222 89.4061C496.166 89.4539 497.061 89.5494 497.957 89.7047C501.038 90.0033 503.929 90.4571 506.723 91.0543C507.416 91.1498 508.121 91.3051 508.765 91.4484C510.461 91.7947 512.05 92.2008 513.59 92.6427C514.283 92.8457 514.988 93.0368 515.681 93.3354C527.827 97.1213 535.399 103.188 535.399 110.259H535.375Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M393.202 81.9653C393.512 81.0457 394.169 80.2933 395.041 79.8633C395.925 79.4334 396.928 79.3617 397.836 79.6842L410.579 84.0195C411.499 84.33 412.251 84.9869 412.681 85.8707C413.111 86.7545 413.171 87.7458 412.86 88.6654L412.645 89.2984C415.846 90.3732 419.142 91.4242 422.558 92.4752C430.249 66.296 454.458 47.1154 483.086 47.1154C517.888 47.1154 546.205 75.4325 546.205 110.235C546.205 112.349 546.097 114.439 545.882 116.505C558.745 117.771 571.99 118.702 585.533 119.264V79.1468C585.545 21.7125 532.148 0 483.038 0C433.928 0 381.689 21.2826 380.578 77.3075C384.507 78.9915 388.64 80.6755 393.071 82.3714L393.214 81.9653H393.202ZM547.351 98.7573C547.244 98.196 547.136 97.6346 547.017 97.0614L553.072 95.0908L553.597 96.715L547.339 98.7453L547.351 98.7573ZM556.691 86.3245C557.527 88.8804 556.822 91.5556 555.126 93.4068L551.257 81.5115C553.717 82.0131 555.855 83.7687 556.679 86.3245H556.691ZM552.26 92.5827L546.384 94.4936C546.061 93.0246 545.631 91.5437 545.154 90.0747C544.676 88.6057 544.15 87.1606 543.553 85.7752L549.429 83.8643L552.26 92.5708V92.5827ZM548.092 79.732L548.617 81.3562L542.562 83.3268C542.311 82.7894 542.072 82.2758 541.834 81.7623L548.092 79.732ZM506.649 42.4218L505.276 48.8591C504.738 48.68 504.201 48.4889 503.64 48.2978L504.965 42.0635L506.637 42.4218H506.649ZM499.185 34.5513C501.812 35.1126 503.783 37.0474 504.535 39.448L492.306 36.8444C493.978 34.9693 496.557 33.99 499.185 34.5513ZM493.428 39.6152L502.386 41.5141L501.108 47.5573C499.674 47.1035 498.182 46.7332 496.665 46.4108C495.16 46.0883 493.631 45.8136 492.138 45.6584L493.416 39.6152H493.428ZM489.177 38.7075L490.849 39.0658L489.523 45.3001C488.938 45.2404 488.376 45.1926 487.803 45.1448L489.177 38.7075ZM425.592 58.4613C427.395 56.4549 430.058 55.7383 432.507 56.2758L424.135 65.5675C423.346 63.1789 423.788 60.4558 425.592 58.4613ZM429.079 73.6171L424.194 69.2101L425.341 67.9322L430.07 72.1959C429.736 72.6736 429.401 73.1394 429.079 73.6052V73.6171ZM434.609 66.5707C433.581 67.7172 432.578 68.8996 431.694 70.1059L427.108 65.9735L433.235 59.166L437.821 63.2983C436.711 64.3135 435.636 65.4242 434.609 66.5707ZM439.732 61.471L434.991 57.2073L436.137 55.9294L441.022 60.3364C440.592 60.7066 440.162 61.0888 439.72 61.471H439.732ZM426.189 22.1305C437.893 15.0124 451.329 11.2265 465.063 11.2265C467.798 11.2265 470.486 11.4295 473.232 11.6803C458.543 11.9311 444.056 15.6574 431.121 22.6321C418.127 29.6069 407.032 39.5555 398.72 51.7016C404.99 39.5077 414.496 29.2964 426.201 22.1305H426.189Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M626.796 136.748C626.796 136.748 626.713 136.689 626.677 136.653C626.414 136.39 626.092 136.187 625.805 135.936C625.208 135.411 624.611 134.861 623.954 134.419C620.908 132.365 617.302 131.243 613.563 131.243C605.968 131.243 598.575 135.65 593.583 141.251C591.612 143.305 589.534 145.371 587.384 147.354C586.835 147.903 586.226 148.429 585.581 148.954V141.585C585.33 141.585 585.139 141.585 584.888 141.538C584.387 141.538 583.897 141.538 583.443 141.49C572.097 140.988 560.99 140.295 550.086 139.352C549.943 139.352 549.787 139.304 549.644 139.304C547.16 139.053 544.616 138.803 542.18 138.552C541.284 138.504 540.436 138.397 539.54 138.301C539.493 138.397 539.397 138.552 539.337 138.647C538.74 139.842 538.143 140.988 537.45 142.087C535.014 146.315 532.028 150.256 528.636 153.731C527.788 154.627 526.94 155.475 526.045 156.275C524.898 157.374 523.752 158.365 522.557 159.309C511.761 168.015 498.062 173.246 483.133 173.246C449.979 173.246 422.749 147.509 420.265 114.952C415.189 113.459 410.257 111.919 405.527 110.318L405.324 110.772C405.025 111.716 404.381 112.468 403.485 112.91C402.983 113.161 402.434 113.256 401.896 113.256C401.502 113.256 401.096 113.209 400.702 113.053L387.959 108.718C386.072 108.073 385.021 105.983 385.666 104.084L385.917 103.439C384.077 102.746 382.381 102.042 380.638 101.349L380.685 148.739L377.019 145.634C375.526 144.177 374.045 142.72 372.576 141.251C367.572 135.674 360.311 131.243 352.595 131.243C350.171 131.243 347.758 131.72 345.525 132.652C343.292 133.583 341.238 134.933 339.518 136.653C339.494 136.677 339.482 136.701 339.47 136.713C339.458 136.713 339.446 136.736 339.434 136.748C339.076 137.119 338.801 137.549 338.479 137.943C338.049 138.468 337.571 138.958 337.201 139.519C337.189 139.543 337.165 139.567 337.141 139.591C336.795 140.092 336.496 140.582 336.198 141.132C335.899 141.681 335.648 142.23 335.397 142.78C335.147 143.329 334.944 143.926 334.8 144.476C334.299 146.219 334 148.011 334 149.85C334 169.616 384.973 203.116 400.654 210.831C426.296 223.479 454.613 230.048 483.193 230C511.773 230.048 539.994 223.527 565.636 210.831C581.317 203.116 632.29 169.616 632.29 149.85C632.29 144.917 630.296 140.248 626.82 136.76L626.796 136.748ZM542.371 155.666C542.765 157.804 542.227 160.145 540.627 161.889C539.528 163.178 538.035 163.883 536.495 164.134C535.551 164.337 534.608 164.277 533.712 164.086L542.12 154.782C542.216 155.081 542.323 155.38 542.371 155.678V155.666ZM537.14 146.757L542.072 151.14L540.926 152.43L536.148 148.154C536.495 147.652 536.841 147.211 537.14 146.757ZM531.622 153.767C532.673 152.621 533.664 151.426 534.56 150.232L534.763 150.435L539.146 154.364L533.82 160.288L533.019 161.184L528.445 157.052C529.544 156.06 530.631 154.914 531.634 153.767H531.622ZM526.487 158.903L526.69 159.106L531.264 163.143L530.32 164.194L530.117 164.444L525.185 160.013C525.639 159.667 526.08 159.261 526.475 158.915L526.487 158.903ZM476.66 175.229L476.708 175.074C477.305 175.121 477.854 175.169 478.451 175.229L477.054 181.654L476.003 181.404L475.406 181.308L476.648 175.229H476.66ZM465.111 172.984L465.159 172.781C466.556 173.234 468.049 173.629 469.59 173.927C471.083 174.274 472.623 174.524 474.068 174.68L472.826 180.759L472.229 180.615L463.869 178.824L465.111 172.996V172.984ZM473.925 183.494C472.874 184.688 471.536 185.488 470.044 185.787C469.1 185.99 468.049 186.037 467.058 185.787C464.812 185.333 463.081 183.84 462.185 181.953C461.982 181.607 461.839 181.26 461.731 180.902L473.925 183.494ZM460.931 171.694L460.979 171.491C461.528 171.694 462.03 171.837 462.627 172.04L461.277 178.263L459.832 177.964L459.581 177.916L460.931 171.694ZM423.692 136.999C423.943 137.549 424.194 138.098 424.385 138.588H424.337L418.163 140.63L417.613 138.982L423.585 137.035L423.681 136.987L423.692 136.999ZM419.859 125.844C420.157 127.337 420.611 128.83 421.053 130.275C421.555 131.768 422.104 133.213 422.701 134.551L416.825 136.498L416.622 135.9L413.983 127.791L419.703 125.904L419.859 125.856V125.844ZM418.82 121.664L418.915 121.617C419.011 122.166 419.118 122.715 419.214 123.312L413.195 125.259L412.753 123.91L412.657 123.659L418.832 121.664H418.82ZM410.746 127.385C410.842 127.23 410.949 127.086 411.093 126.931L414.974 138.826C414.377 138.731 413.827 138.528 413.278 138.277C413.23 138.277 413.183 138.229 413.183 138.229C411.534 137.381 410.197 135.936 409.552 134.001C408.752 131.661 409.301 129.176 410.746 127.385ZM533.796 190.6C517.876 197.001 500.618 200.321 483.157 200.321C465.696 200.321 448.45 197.013 432.518 190.6C416.562 184.162 402.267 174.727 390.634 162.987C415.726 184.258 448.797 196.058 483.157 196.022C517.481 196.082 550.552 184.246 575.608 162.987C563.964 174.763 549.716 184.162 533.796 190.6Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M396.114 83.5777L388.924 104.681C388.745 105.195 389.02 105.756 389.533 105.923L401.142 109.876C401.655 110.056 402.217 109.781 402.384 109.267L409.574 88.1758C409.753 87.6623 409.478 87.1009 408.964 86.9337L397.344 82.9686C396.83 82.7895 396.269 83.0642 396.102 83.5777H396.114Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/docs/[[...slug]]/page.tsx",
    "content": "import { useMDXComponents } from '@/mdx-components'\nimport { AsideSponsors } from '@/src/app/(pages)/_landing/sponsors'\nimport { source } from '@/src/app/source'\nimport {\n  CopyAsMarkdownButton,\n  CopyMarkdownUrlButton,\n  ViewOptions\n} from '@/src/components/ai/page-actions'\nimport { getBaseUrl } from '@/src/lib/url'\nimport { github } from '@/src/lib/utils'\nimport {\n  DocsBody,\n  DocsDescription,\n  DocsPage,\n  DocsTitle\n} from 'fumadocs-ui/page'\nimport type { Metadata } from 'next'\nimport { notFound } from 'next/navigation'\nimport { stat } from 'node:fs/promises'\n\nexport const dynamic = 'force-static'\n\nexport default async function Page(props: PageProps<'/docs/[[...slug]]'>) {\n  const { slug } = await props.params\n  const page = source.getPage(slug)\n\n  // Return 404 for missing pages or llm-only pages\n  if (!page || !page.data.exposeTo.includes('user')) {\n    notFound()\n  }\n\n  const MDX = page.data.body\n\n  return (\n    <DocsPage\n      toc={page.data.toc}\n      tableOfContent={{\n        footer: <AsideSponsors />\n      }}\n    >\n      <DocsTitle>{page.data.title}</DocsTitle>\n      <DocsDescription>{page.data.description}</DocsDescription>\n      <div className=\"mb-2 flex flex-row flex-wrap items-center gap-2 border-b pb-6\">\n        <CopyAsMarkdownButton markdownUrl={`${page.url}.md`} />\n        <CopyMarkdownUrlButton markdownUrl={`${page.url}.md`} />\n        <ViewOptions\n          markdownUrl={`${page.url}.md`}\n          githubUrl={`https://github.com/${github.owner}/${github.repo}/blob/${github.branch}/packages/docs/content/docs/${page.path}`}\n        />\n      </div>\n      <DocsBody>\n        <MDX components={useMDXComponents()} />\n      </DocsBody>\n    </DocsPage>\n  )\n}\n\nexport async function generateStaticParams() {\n  return source\n    .getPages()\n    .filter(page => page.data.exposeTo.includes('user'))\n    .map(page => ({\n      slug: page.slugs\n    }))\n}\n\nexport async function generateMetadata(\n  props: PageProps<'/docs/[[...slug]]'>\n): Promise<Metadata> {\n  const { slug } = await props.params\n  const page = source.getPage(slug)\n  if (!page || !page.data.exposeTo.includes('user')) notFound()\n\n  return {\n    title: page.data.title,\n    description: page.data.description,\n    ...(await getSocialImages(page.slugs))\n  }\n}\n\n// --\n\nasync function getSocialImages(\n  slug: string[]\n): Promise<Pick<Metadata, 'openGraph' | 'twitter'>> {\n  try {\n    const publicImagePath = `${process.cwd()}/public/og/${slug.join('/')}.jpg`\n    await stat(publicImagePath) // Does it exist?\n    const baseUrl = getBaseUrl()\n    return {\n      openGraph: {\n        type: 'website',\n        images: [\n          {\n            url: `${baseUrl}/og/${slug.join('/')}.jpg`,\n            width: 1200,\n            height: 675\n          }\n        ]\n      },\n      twitter: {\n        card: 'summary_large_image',\n        images: [\n          {\n            url: `${baseUrl}/og/${slug.join('/')}.jpg`,\n            width: 1200,\n            height: 675\n          }\n        ]\n      }\n    }\n  } catch {\n    console.warn(`No og:image found for doc page \\`${slug.join('/')}\\``)\n    return {}\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/docs/layout.tsx",
    "content": "import { source } from '@/src/app/source'\nimport { getSharedLayoutProps } from '@/src/components/shared-layout'\nimport { SidebarFooter } from '@/src/components/sidebar-footer'\nimport { DocsLayout } from 'fumadocs-ui/layouts/notebook'\nimport { Suspense, type ReactNode } from 'react'\nimport { SideBanner } from '../banners'\n\nexport default function RootDocsLayout({ children }: { children: ReactNode }) {\n  const shared = getSharedLayoutProps()\n\n  return (\n    <DocsLayout\n      tree={source.pageTree}\n      {...shared}\n      nav={{ ...shared.nav, mode: 'top' }}\n      sidebar={{\n        collapsible: false,\n        banner: SideBanner,\n        footer: (\n          <Suspense>\n            <SidebarFooter />\n          </Suspense>\n        )\n      }}\n    >\n      {children}\n    </DocsLayout>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/global-error.tsx",
    "content": "'use client'\n\nimport * as Sentry from '@sentry/nextjs'\nimport Error from 'next/error'\nimport { useEffect } from 'react'\n\nexport default function GlobalError({ error }: { error: unknown }) {\n  useEffect(() => {\n    Sentry.captureException(error)\n  }, [error])\n\n  const statusCode =\n    typeof error === 'object' && error !== null && 'statusCode' in error\n      ? Number(error.statusCode)\n      : 500\n\n  return (\n    <html>\n      <body>\n        <Error statusCode={statusCode} />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/globals.css",
    "content": "@import 'tailwindcss';\n@import 'fumadocs-ui/css/black.css';\n@import 'fumadocs-ui/css/preset.css';\n@import './styles/tweaks.css';\n@plugin 'tailwindcss-animate';\n@plugin '@tailwindcss/container-queries';\n@plugin '@headlessui/tailwindcss';\n\n:root {\n  --fd-layout-width: 1600px;\n\n  --radius: 0.5rem;\n  --background: 0 0% 100%;\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary: 240 5.9% 10%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 240 5.9% 10%;\n}\n\n.dark {\n  --background: 240 10% 3.9%;\n  --foreground: 0 0% 98%;\n  --card: 240 10% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 240 10% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary: 0 0% 98%;\n  --primary-foreground: 240 5.9% 10%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 240 3.7% 15.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 240 4.9% 83.9%;\n}\n\n@layer base {\n  html,\n  body {\n    height: 100%;\n  }\n  body {\n    display: flex;\n    flex-direction: column;\n  }\n  body > main {\n    flex: 1 0 auto;\n  }\n\n  * {\n    @apply border-border;\n  }\n\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n@layer components {\n  button:hover .star,\n  button:focus .star {\n    fill: currentColor;\n  }\n\n  /* Fill the left siblings of the hovered/focused star */\n  button:has(~ :hover) .star,\n  button:has(~ :focus) .star {\n    fill: currentColor;\n  }\n}\n\n@layer components {\n  number-flow-react::part(suffix) {\n    @apply text-muted-foreground ml-0.5 text-sm font-medium;\n  }\n}\n\n@theme {\n  --color-border: hsl(var(--border));\n  --color-input: hsl(var(--input));\n  --color-ring: hsl(var(--ring));\n  --color-background: hsl(var(--background));\n  --color-foreground: hsl(var(--foreground));\n\n  --color-primary: hsl(var(--primary));\n  --color-primary-foreground: hsl(var(--primary-foreground));\n\n  --color-secondary: hsl(var(--secondary));\n  --color-secondary-foreground: hsl(var(--secondary-foreground));\n\n  --color-destructive: hsl(var(--destructive));\n  --color-destructive-foreground: hsl(var(--destructive-foreground));\n\n  --color-muted: hsl(var(--muted));\n  --color-muted-foreground: hsl(var(--muted-foreground));\n\n  --color-accent: hsl(var(--accent));\n  --color-accent-foreground: hsl(var(--accent-foreground));\n\n  --color-popover: hsl(var(--popover));\n  --color-popover-foreground: hsl(var(--popover-foreground));\n\n  --color-card: hsl(var(--card));\n  --color-card-foreground: hsl(var(--card-foreground));\n\n  --radius-lg: var(--radius);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-sm: calc(var(--radius) - 4px);\n\n  --font-sans:\n    var(--font-sans), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',\n    'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';\n\n  @keyframes accordion-down {\n    from {\n      height: 0;\n    }\n    to {\n      height: var(--radix-accordion-content-height);\n    }\n  }\n\n  @keyframes accordion-up {\n    from {\n      height: var(--radix-accordion-content-height);\n    }\n    to {\n      height: 0;\n    }\n  }\n\n  --animation-accordion-down: accordion-down 0.2s ease-out;\n  --animation-accordion-up: accordion-up 0.2s ease-out;\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/layout.tsx",
    "content": "import { Databuddy } from '@databuddy/sdk/react'\nimport * as Sentry from '@sentry/nextjs'\nimport { RootProvider } from 'fumadocs-ui/provider/next'\nimport type { Metadata } from 'next'\nimport { Inter } from 'next/font/google'\nimport Script from 'next/script'\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport { type ReactNode } from 'react'\nimport { Favicon } from '../components/favicon'\nimport { ResponsiveHelper } from '../components/responsive-helpers'\nimport { cn } from '../lib/utils'\nimport './globals.css'\n\nconst inter = Inter({\n  subsets: ['latin'],\n  fallback: ['sans-serif']\n})\n\nexport const metadata = {\n  title: {\n    template: '%s | nuqs',\n    default: 'nuqs'\n  },\n  description:\n    'Type-safe search params state management for React. Like useState, but stored in the URL query string.',\n  authors: [\n    {\n      name: 'François Best',\n      url: 'https://francoisbest.com'\n    }\n  ],\n  alternates: {\n    types: {\n      'application/rss+xml': [\n        {\n          url: '/blog/rss.xml',\n          title: 'nuqs blog RSS feed'\n        },\n        {\n          url: '/registry/rss.xml',\n          title: '@nuqs shadcn registry RSS feed'\n        }\n      ] as const\n    }\n  },\n  other: {\n    ...Sentry.getTraceData()\n  }\n} satisfies Metadata\n\nexport default function Layout({ children }: { children: ReactNode }) {\n  const enableAnalytics = process.env.VERCEL_ENV === 'production'\n  return (\n    <html\n      lang=\"en\"\n      className={cn(inter.className, 'antialiased')}\n      // https://github.com/shadcn-ui/ui/issues/5552#issuecomment-2435024526\n      suppressHydrationWarning\n    >\n      <Favicon />\n      <body>\n        {/* Top-level banners go here */}\n        <RootProvider>\n          <NuqsAdapter>{children}</NuqsAdapter>\n        </RootProvider>\n        {enableAnalytics && (\n          <>\n            <Databuddy clientId=\"QhPm9ppqtfj3m3t2LP5iK\" enableBatching />\n            <Script\n              async\n              id=\"chiffre:analytics\"\n              src=\"https://chiffre.io/analytics.js\"\n              data-chiffre-project-id=\"odWoaH0aUUwm42Wf\"\n              data-chiffre-public-key=\"pk.3EPMj_faODyzisb0UNmZnzhIkG9sbj7zR5em6lf7Olk\"\n              referrerPolicy=\"origin\"\n              crossOrigin=\"anonymous\"\n              data-chiffre-ignore-paths=\"/stats\"\n            />\n          </>\n        )}\n        <ResponsiveHelper />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/not-found.tsx",
    "content": "import { Button } from '../components/ui/button'\nimport PageLayout from './(pages)/layout'\n\nexport default function NotFoundPage() {\n  return (\n    <PageLayout>\n      <main className=\"container relative flex h-[75vh] items-center\">\n        <NotFoundComponent />\n      </main>\n    </PageLayout>\n  )\n}\n\n/**\n * v0 by Vercel.\n * @see https://v0.dev/t/4RlmIQ6YYxD\n * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app\n */\nimport Link from 'next/link'\n\nexport function NotFoundComponent() {\n  return (\n    <section className=\"w-full py-12 md:py-24 lg:py-32\">\n      <div className=\"container px-4 md:px-6\">\n        <div className=\"flex flex-col items-center justify-center space-y-4 text-center\">\n          <div className=\"relative space-y-2\">\n            <div className=\"absolute -top-12 left-0 z-0 flex h-full w-full items-end justify-center\">\n              <span className=\"select-none text-9xl font-bold text-zinc-200 opacity-20 dark:text-zinc-800\">\n                404\n              </span>\n            </div>\n            <h1 className=\"relative z-10 text-3xl font-bold tracking-tighter sm:text-5xl\">\n              Page Not Found\n            </h1>\n            <p className=\"relative z-10 max-w-[600px] text-zinc-500 dark:text-zinc-400 md:text-xl\">\n              The page you are looking for does not exist.\n            </p>\n          </div>\n          <Button asChild>\n            <Link prefetch={false} href=\"/\">\n              Return to Home\n            </Link>\n          </Button>\n        </div>\n      </div>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/_components/source-on-github.tsx",
    "content": "import { CodeBlock } from '@/src/components/code-block'\nimport * as Sentry from '@sentry/nextjs'\nimport { FileCode2 } from 'lucide-react'\nimport fs from 'node:fs/promises'\n\ntype SourceOnGitHubProps = {\n  path: string\n}\n\nexport async function SourceOnGitHub({ path }: SourceOnGitHubProps) {\n  const source = await readSourceCode(path)\n  return (\n    <footer className=\"mt-2 space-y-2 border-t py-4\">\n      <div className=\"flex items-baseline\">\n        <span className=\"flex items-center gap-1 text-zinc-500\">\n          <FileCode2 size={16} />\n          {path.split('/').slice(1).join('/')}\n        </span>\n        <a\n          href={`https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/playground/(demos)/${path}`}\n          className=\"ml-auto text-sm\"\n        >\n          Source on GitHub\n        </a>\n      </div>\n      {source === null && (\n        <div className=\"text-sm text-red-500\">\n          Unable to load source code for this demo\n        </div>\n      )}\n      {source !== null && (\n        <div className=\"not-prose overflow-hidden\">\n          <CodeBlock code={source} />\n        </div>\n      )}\n    </footer>\n  )\n}\n\nfunction readSourceCode(demoPath: string) {\n  const demoFilePath = process.cwd() + '/src/app/playground/(demos)/' + demoPath\n  return fs.readFile(demoFilePath, 'utf8').catch(error => {\n    if (error.code !== 'ENOENT') {\n      throw error\n    }\n    console.error(error)\n    Sentry.captureException(error, {\n      extra: {\n        demoPath,\n        demoFilePath\n      }\n    })\n    return null\n  })\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/basic-counter/client.tsx",
    "content": "'use client'\n\nimport { Button } from '@/src/components/ui/button'\nimport { Minus, Plus } from 'lucide-react'\nimport { parseAsInteger, useQueryState } from 'nuqs'\n\nexport default function BasicCounterDemoPage() {\n  const [counter, setCounter] = useQueryState(\n    'counter',\n    parseAsInteger.withDefault(0)\n  )\n  return (\n    <>\n      <nav className=\"my-8 flex flex-wrap items-center gap-4\">\n        <Button onClick={() => setCounter(x => x - 1)}>\n          <Minus />\n        </Button>\n        <Button onClick={() => setCounter(x => x + 1)}>\n          <Plus />\n        </Button>\n        <Button onClick={() => setCounter(null)}>Reset</Button>\n        <span className=\"text-2xl font-semibold tabular-nums\">\n          Counter: {counter}\n        </span>\n      </nav>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/basic-counter/page.tsx",
    "content": "import { Description, H1 } from '@/src/components/typography'\nimport { Suspense } from 'react'\nimport { SourceOnGitHub } from '../_components/source-on-github'\nimport { getMetadata } from '../demos'\nimport Client from './client'\n\nexport const metadata = getMetadata('basic-counter')\n\nexport default function BasicCounterDemoPage() {\n  return (\n    <>\n      <H1 className=\"mb-4 text-3xl font-bold text-foreground sm:text-4xl\">\n        {metadata.title}\n      </H1>\n      <Description>{metadata.description}</Description>\n      <Suspense>\n        <Client />\n      </Suspense>\n      <SourceOnGitHub path=\"basic-counter/client.tsx\" />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/batching/client.tsx",
    "content": "'use client'\n\nimport { Button } from '@/src/components/ui/button'\nimport { parseAsFloat, useQueryState } from 'nuqs'\n\nconst parser = parseAsFloat.withDefault(0)\n\nexport default function BuilderPatternDemoPage() {\n  const [lat, setLat] = useQueryState('lat', parser)\n  const [lng, setLng] = useQueryState('lng', parser)\n  return (\n    <>\n      <Button\n        onClick={async () => {\n          // Call as many state updates as needed in the same event loop tick,\n          // and they will be asynchronously batched into one update.\n          const p1 = setLat(Math.random() * 180 - 90)\n          const p2 = setLng(Math.random() * 360 - 180)\n          // The returned promise is cached until next flush to the URL occurs\n          console.log('Promise cached: ', p1 === p2)\n          p1.then(search => console.log('Awaited: %s', search.toString()))\n        }}\n      >\n        Random coordinates\n      </Button>\n      <ul>\n        <li>Latitude: {lat}</li>\n        <li>Longitude: {lng}</li>\n      </ul>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/batching/page.tsx",
    "content": "import { Description } from '@/src/components/typography'\nimport { Suspense } from 'react'\nimport { SourceOnGitHub } from '../_components/source-on-github'\nimport { getMetadata } from '../demos'\nimport Client from './client'\n\nexport const metadata = getMetadata('batching')\n\nexport default function BuilderPatternDemoPage() {\n  return (\n    <>\n      <h1>{metadata.title}</h1>\n      <Description>{metadata.description}</Description>\n      <Suspense>\n        <Client />\n      </Suspense>\n      <SourceOnGitHub path=\"batching/client.tsx\" />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/demos.ts",
    "content": "import type * as PageTree from 'fumadocs-core/page-tree'\n\ntype DemoMetadata = {\n  title: string\n  description: string\n}\n\nexport const demos = {\n  'basic-counter': {\n    title: 'Basic counter',\n    description: 'State is stored in the URL query string'\n  },\n  batching: {\n    title: 'Batching',\n    description:\n      'State updates are collected and batched into one update on the next tick.'\n  },\n  'hex-colors': {\n    title: 'Hex colors',\n    description: 'Parsing RGB values from a hex color'\n  },\n  pagination: {\n    title: 'Pagination',\n    description: 'Integer page index with server-side rendering'\n  },\n  'tic-tac-toe': {\n    title: 'Tic Tac Toe',\n    description:\n      'A game of tic tac toe stored in the URL. Use the Back/Forward buttons to undo/redo moves.'\n  }\n} as const satisfies Record<string, DemoMetadata>\n\nexport function getMetadata(path: keyof typeof demos): DemoMetadata {\n  return demos[path]\n}\n\n// --\n\nexport function getPlaygroundTree(): PageTree.Root {\n  return {\n    name: 'Playground',\n    children: Object.entries(demos).map(([path, { title, description }]) => ({\n      type: 'page',\n      name: title,\n      description,\n      url: `/playground/${path}`\n    }))\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/hex-colors/client.tsx",
    "content": "'use client'\n\nimport { createParser, parseAsHex, useQueryState } from 'nuqs'\n\nconst hexColorSchema = createParser({\n  parse(query) {\n    if (query.length !== 6) {\n      return null\n    }\n    return {\n      r: parseAsHex.parse(query.slice(0, 2)) ?? 0x00,\n      g: parseAsHex.parse(query.slice(2, 4)) ?? 0x00,\n      b: parseAsHex.parse(query.slice(4)) ?? 0x00\n    }\n  },\n  serialize({ r, g, b }) {\n    return (\n      parseAsHex.serialize(r) +\n      parseAsHex.serialize(g) +\n      parseAsHex.serialize(b)\n    )\n  }\n})\n\nexport default function HexColorsDemo() {\n  const [color, setColor] = useQueryState(\n    'color',\n    hexColorSchema.withDefault({\n      r: 0x00,\n      g: 0x00,\n      b: 0x00\n    })\n  )\n  const asHex = '#' + hexColorSchema.serialize(color)\n  return (\n    <div className=\"flex flex-wrap-reverse items-center gap-4\">\n      <div\n        className=\"h-64 w-64 rounded border\"\n        style={{\n          backgroundColor: `rgb(${color.r} ${color.g} ${color.b})`\n        }}\n      />\n      <section className=\"space-y-2\">\n        <div className=\"flex items-center\">\n          <label>Color</label>\n          <input\n            type=\"color\"\n            value={asHex}\n            onChange={e =>\n              setColor(hexColorSchema.parse(e.target.value.slice(1)))\n            }\n            style={{\n              display: 'inline-block',\n              marginInline: '8px'\n            }}\n          />\n          <span\n            style={{\n              fontFamily: 'monospace'\n            }}\n          >\n            {asHex}\n          </span>\n        </div>\n        <ColorSlider\n          label=\"R\"\n          value={color.r}\n          onChange={r => setColor(color => ({ ...color, r }))}\n          accentColor={\n            '#' + hexColorSchema.serialize({ r: color.r, g: 0, b: 0 })\n          }\n        />\n        <ColorSlider\n          label=\"G\"\n          value={color.g}\n          onChange={g => setColor(color => ({ ...color, g }))}\n          accentColor={\n            '#' + hexColorSchema.serialize({ r: 0, g: color.g, b: 0 })\n          }\n        />\n        <ColorSlider\n          label=\"B\"\n          value={color.b}\n          onChange={b => setColor(color => ({ ...color, b }))}\n          accentColor={\n            '#' + hexColorSchema.serialize({ r: 0, g: 0, b: color.b })\n          }\n        />\n      </section>\n    </div>\n  )\n}\n\ntype ColorSliderProps = {\n  label: string\n  value: number\n  onChange: (value: number) => void\n  accentColor: string\n}\n\nconst ColorSlider = ({\n  label,\n  value,\n  onChange,\n  accentColor\n}: ColorSliderProps) => {\n  return (\n    <div className=\"flex items-center gap-4\" style={{ accentColor }}>\n      <label>{label}</label>\n      <input\n        type=\"range\"\n        value={value}\n        onChange={e => onChange(e.target.valueAsNumber)}\n        min={0}\n        max={255}\n        step={1}\n      />\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/hex-colors/page.tsx",
    "content": "import { Description } from '@/src/components/typography'\nimport { Suspense } from 'react'\nimport { SourceOnGitHub } from '../_components/source-on-github'\nimport { getMetadata } from '../demos'\nimport Client from './client'\n\nexport const metadata = getMetadata('hex-colors')\n\nexport default function HexColorsDemoPage() {\n  return (\n    <>\n      <h1>{metadata.title}</h1>\n      <Description>{metadata.description}</Description>\n      <Suspense>\n        <Client />\n      </Suspense>\n      <SourceOnGitHub path=\"hex-colors/client.tsx\" />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/layout.tsx",
    "content": "import { QuerySpy } from '@/src/components/query-spy'\nimport { QuerystringSkeleton } from '@/src/components/querystring'\nimport React, { Suspense } from 'react'\n\nexport default function PlaygroundDemoLayout({\n  children\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    <>\n      <Suspense fallback={<QuerystringSkeleton>&nbsp;</QuerystringSkeleton>}>\n        <QuerySpy />\n      </Suspense>\n      {children}\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/api.ts",
    "content": "import { faker } from '@faker-js/faker'\n\n// Ensure consistent results\nfaker.seed(47)\n\nexport const pageSize = 5\nexport const pageCount = 5\n\n// Fake an in-memory product API\nconst productDatabase = Array.from(\n  { length: pageSize * pageCount },\n  (_, i) => ({\n    id: i,\n    name: faker.commerce.productName(),\n    price: faker.commerce.price({ symbol: '€' }),\n    description: faker.commerce.productDescription()\n  })\n)\n\nexport type Product = (typeof productDatabase)[number]\n\nexport async function fetchProducts(\n  page: number,\n  delay: number\n): Promise<Product[]> {\n  await new Promise(resolve => setTimeout(resolve, delay))\n  return productDatabase.slice((page - 1) * pageSize, page * pageSize)\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/page.tsx",
    "content": "import { Description } from '@/src/components/typography'\nimport { Separator } from '@/src/components/ui/separator'\nimport type { SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { SourceOnGitHub } from '../_components/source-on-github'\nimport { getMetadata } from '../demos'\nimport { fetchProducts, pageCount } from './api'\nimport { ClientPaginationControls } from './pagination-controls.client'\nimport { ServerPaginationControls } from './pagination-controls.server'\nimport { ProductView } from './product'\nimport { RenderingControls } from './rendering-controls'\nimport { loadPagination, type PaginationSearchParams } from './search-params'\n\nexport const metadata = getMetadata('pagination')\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\ntype PaginatedProps = {\n  pagination: Promise<PaginationSearchParams>\n}\n\nexport default async function PaginationDemoPage({ searchParams }: PageProps) {\n  // Allow nested RSCs to access the search params (in a type-safe way)\n  const pagination = loadPagination(searchParams)\n  return (\n    <>\n      <h1>{metadata.title}</h1>\n      <Description>{metadata.description}</Description>\n      <h2>Rendering controls</h2>\n      <Suspense>\n        <RenderingControls />\n      </Suspense>\n      <Separator className=\"my-8\" />\n      <Suspense>\n        <PaginationRenderer pagination={pagination} />\n      </Suspense>\n      <Suspense>\n        <ProductSection pagination={pagination} />\n      </Suspense>\n      <SourceOnGitHub path=\"pagination/search-params.ts\" />\n      <SourceOnGitHub path=\"pagination/page.tsx\" />\n      <SourceOnGitHub path=\"pagination/pagination-controls.server.tsx\" />\n      <SourceOnGitHub path=\"pagination/pagination-controls.client.tsx\" />\n    </>\n  )\n}\n\nasync function PaginationRenderer({ pagination }: PaginatedProps) {\n  const { renderOn } = await pagination\n  return (\n    <>\n      <h2>\n        Pagination controls{' '}\n        <small className=\"text-sm font-medium text-zinc-500\">\n          ({renderOn}-rendered)\n        </small>\n      </h2>\n      {renderOn === 'server' && (\n        <ServerPaginationControls\n          numPages={pageCount}\n          pagination={pagination}\n        />\n      )}\n      <Suspense key=\"client\">\n        {renderOn === 'client' && (\n          <ClientPaginationControls numPages={pageCount} />\n        )}\n      </Suspense>\n    </>\n  )\n}\n\nasync function ProductSection({ pagination }: PaginatedProps) {\n  const { page, delay } = await pagination\n  const products = await fetchProducts(page, delay)\n  return (\n    <section>\n      <h2>\n        Product list{' '}\n        <small className=\"text-sm font-medium text-zinc-500\">\n          (server-rendered)\n        </small>\n      </h2>\n      {products.map(product => (\n        <ProductView product={product} key={product.id} />\n      ))}\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/pagination-controls.client.tsx",
    "content": "'use client'\n\nimport {\n  Pagination,\n  PaginationButton,\n  PaginationContent,\n  PaginationItem,\n  PaginationNext,\n  PaginationPrevious\n} from '@/src/components/ui/pagination'\nimport { cn } from '@/src/lib/utils'\nimport React from 'react'\nimport { usePage } from './search-params'\n\ntype PaginationControlsProps = {\n  numPages: number\n}\n\n// Use client-side hooks to update the page number\n// and observe the loading state\nexport function ClientPaginationControls({\n  numPages\n}: PaginationControlsProps) {\n  const [isLoading, startTransition] = React.useTransition()\n  const [page, setPage] = usePage({ startTransition })\n  return (\n    <Pagination className=\"not-prose items-center gap-2\">\n      <PaginationContent>\n        <PaginationItem>\n          <PaginationPrevious\n            disabled={page === 1}\n            onClick={() => setPage(p => Math.max(1, p - 1))}\n          />\n        </PaginationItem>\n        {Array.from({ length: numPages }, (_, i) => (\n          <PaginationItem key={i}>\n            <PaginationButton\n              isActive={page === i + 1}\n              onClick={() => setPage(i + 1)}\n            >\n              {i + 1}\n            </PaginationButton>\n          </PaginationItem>\n        ))}\n        <PaginationItem>\n          <PaginationNext\n            disabled={page === numPages}\n            onClick={() => setPage(p => Math.min(numPages, p + 1))}\n          />\n        </PaginationItem>\n      </PaginationContent>\n      <div\n        aria-label={isLoading ? 'Loading' : 'Idle'}\n        aria-live={isLoading ? 'polite' : undefined}\n        className={cn(\n          'h-2 w-2 rounded-full bg-green-500',\n          isLoading && 'animate-pulse bg-amber-500'\n        )}\n      />\n    </Pagination>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/pagination-controls.server.tsx",
    "content": "import {\n  Pagination,\n  PaginationContent,\n  PaginationItem,\n  PaginationLink,\n  PaginationNextLink,\n  PaginationPreviousLink\n} from '@/src/components/ui/pagination'\nimport { cn } from '@/src/lib/utils'\nimport { getPaginatedLink, PaginationSearchParams } from './search-params'\n\ntype PaginationControlsProps = {\n  numPages: number\n  pagination: Promise<PaginationSearchParams>\n}\n\n// Use <Link> components to navigate between pages\nexport async function ServerPaginationControls({\n  numPages,\n  pagination\n}: PaginationControlsProps) {\n  const { page, delay, renderOn } = await pagination\n  function pageURL(page: number) {\n    return getPaginatedLink('/playground/pagination', {\n      page,\n      delay,\n      renderOn\n    })\n  }\n  return (\n    <Pagination className=\"not-prose items-center gap-2\">\n      <PaginationContent>\n        <PaginationItem>\n          <PaginationPreviousLink\n            href={pageURL(page - 1)}\n            disabled={page === 1}\n            scroll={false}\n          />\n        </PaginationItem>\n        {Array.from({ length: numPages }, (_, i) => (\n          <PaginationItem key={i}>\n            <PaginationLink\n              href={pageURL(i + 1)}\n              isActive={page === i + 1}\n              scroll={false}\n            >\n              {i + 1}\n            </PaginationLink>\n          </PaginationItem>\n        ))}\n        <PaginationItem>\n          <PaginationNextLink\n            disabled={page === numPages}\n            href={pageURL(page + 1)}\n            scroll={false}\n          />\n        </PaginationItem>\n      </PaginationContent>\n      <div\n        aria-label={'Loading status unavailable on the server'}\n        className={cn('h-2 w-2 rounded-full bg-zinc-500')}\n      />\n    </Pagination>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/product.tsx",
    "content": "import type { Product } from './api'\n\ntype ProductViewProps = React.ComponentProps<'div'> & {\n  product: Product\n}\n\nexport function ProductView({ product, ...props }: ProductViewProps) {\n  return (\n    <div {...props}>\n      <div className=\"flex items-baseline gap-2\">\n        <h3 className=\"my-1\"> {product.name}</h3>\n        <span className=\"ml-auto\">{product.price}</span>\n        <span className=\"text-xs text-zinc-500\">SKU-{product.id + 1}</span>\n      </div>\n      <p className=\"mt-1\">{product.description}</p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/rendering-controls.tsx",
    "content": "'use client'\n\nimport { Label } from '@/src/components/ui/label'\nimport { ToggleGroup, ToggleGroupItem } from '@/src/components/ui/toggle-group'\nimport { useQueryStates } from 'nuqs'\nimport {\n  RenderingOptions,\n  renderingOptions,\n  usePaginationControls\n} from './search-params'\n\nexport function RenderingControls() {\n  const [{ renderOn, delay }, setControls] = usePaginationControls()\n  return (\n    <nav className=\"not-prose\">\n      <ul className=\"space-y-2\">\n        <li className=\"flex items-center justify-between sm:justify-start\">\n          <Label className=\"w-48\">Pagination controls</Label>\n          <ToggleGroup\n            type=\"single\"\n            className=\"justify-start\"\n            value={renderOn}\n            onValueChange={value =>\n              setControls({\n                renderOn: renderingOptions.includes(value as RenderingOptions)\n                  ? (value as RenderingOptions)\n                  : null\n              })\n            }\n          >\n            <ToggleGroupItem size=\"sm\" value=\"server\">\n              Server\n            </ToggleGroupItem>\n            <ToggleGroupItem size=\"sm\" value=\"client\">\n              Client\n            </ToggleGroupItem>\n          </ToggleGroup>\n        </li>\n        <li className=\"flex items-center justify-between sm:justify-start\">\n          <Label className=\"w-48\">Fake fetch delay</Label>\n          <ToggleGroup\n            type=\"single\"\n            className=\"justify-start\"\n            value={delay.toFixed()}\n            onValueChange={value =>\n              setControls({\n                delay: value && value !== '0' ? parseInt(value) : null\n              })\n            }\n          >\n            <ToggleGroupItem size=\"sm\" value=\"0\">\n              None\n            </ToggleGroupItem>\n            <ToggleGroupItem size=\"sm\" value=\"100\">\n              100ms\n            </ToggleGroupItem>\n            <ToggleGroupItem size=\"sm\" value=\"500\">\n              500ms\n            </ToggleGroupItem>\n            <ToggleGroupItem size=\"sm\" value=\"1000\">\n              1s\n            </ToggleGroupItem>\n          </ToggleGroup>\n        </li>\n      </ul>\n    </nav>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/pagination/search-params.ts",
    "content": "import { useQueryState, useQueryStates } from 'nuqs'\nimport {\n  createLoader,\n  createSerializer,\n  type inferParserType,\n  type Options,\n  parseAsInteger,\n  parseAsStringLiteral\n} from 'nuqs/server'\n\nexport const renderingOptions = ['server', 'client'] as const\nexport type RenderingOptions = (typeof renderingOptions)[number]\n\nconst searchParams = {\n  page: parseAsInteger.withDefault(1),\n  renderOn: parseAsStringLiteral(renderingOptions).withDefault('server'),\n  delay: parseAsInteger.withDefault(0)\n}\nexport type PaginationSearchParams = inferParserType<typeof searchParams>\n\nexport const usePage = (options: Options = {}) =>\n  useQueryState(\n    'page',\n    searchParams.page.withOptions({ ...options, shallow: false })\n  )\n\nexport const usePaginationControls = () =>\n  useQueryStates(searchParams, { shallow: false })\n\nexport const loadPagination = createLoader(searchParams)\nexport const getPaginatedLink = createSerializer(searchParams)\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/tic-tac-toe/client.tsx",
    "content": "'use client'\n\nimport { Button } from '@/src/components/ui/button'\nimport { cn } from '@/src/lib/utils'\nimport { createParser, useQueryState } from 'nuqs'\nimport { useCallback } from 'react'\n\ntype Cell = ' ' | 'x' | 'o'\ntype Board = Cell[][]\ntype GameStatus = 'x-turn' | 'o-turn' | 'x-wins' | 'o-wins' | 'draw'\ntype GameState = {\n  board: Board\n  status: GameStatus\n}\n\nconst defaultBoard = [\n  [' ', ' ', ' '],\n  [' ', ' ', ' '],\n  [' ', ' ', ' ']\n] satisfies Board\n\nconst defaultState = {\n  board: defaultBoard,\n  status: 'x-turn'\n} satisfies GameState\n\nconst gameParser = createParser<GameState>({\n  parse(query) {\n    const board = query\n      .replace(/_/g, ' ')\n      .split('|')\n      .map(row => row.split(''))\n    // Validate the board\n    if (board.length !== 3) {\n      throw new Error('Invalid board length')\n    }\n    for (const row of board) {\n      if (row.length !== 3) {\n        throw new Error('Invalid row length')\n      }\n      for (const cell of row) {\n        if (![' ', 'x', 'o'].includes(cell)) {\n          throw new Error('Invalid cell')\n        }\n      }\n    }\n    return {\n      board: board as Board,\n      status: computeGameStatus(board as Board)\n    }\n  },\n  serialize(state) {\n    return state.board\n      .map(row => row.join(''))\n      .join('|')\n      .replace(/ /g, '_')\n  }\n})\n\nfunction useGameEngine() {\n  const [{ board, status }, setGameState] = useQueryState(\n    'board',\n    gameParser.withDefault(defaultState).withOptions({ history: 'push' })\n  )\n  const play = useCallback(\n    (i: number, j: number) => {\n      if (status !== 'x-turn' && status !== 'o-turn') {\n        return\n      }\n      if (board[i][j] !== ' ') {\n        return\n      }\n      const newBoard = board.map(row => row.slice())\n      newBoard[i][j] = status === 'x-turn' ? 'x' : 'o'\n      setGameState({ board: newBoard, status: computeGameStatus(newBoard) })\n    },\n    [board, status]\n  )\n  const reset = useCallback(() => {\n    setGameState(defaultState)\n  }, [])\n\n  return { board, status, play, reset }\n}\n\nfunction computeGameStatus(board: Board): GameStatus {\n  const xCount = board.flat().filter(cell => cell === 'x').length\n  const oCount = board.flat().filter(cell => cell === 'o').length\n  if (xCount < oCount) {\n    throw new Error(\"Too many o's\")\n  }\n  if (xCount > oCount + 1) {\n    throw new Error(\"Too many x's\")\n  }\n  const lines = [\n    // Rows\n    board[0].join(''),\n    board[1].join(''),\n    board[2].join(''),\n    // Columns\n    board.map(row => row[0]).join(''),\n    board.map(row => row[1]).join(''),\n    board.map(row => row[2]).join(''),\n    // Diagonals\n    [board[0][0], board[1][1], board[2][2]].join(''),\n    [board[0][2], board[1][1], board[2][0]].join('')\n  ]\n  if (lines.some(line => line === 'xxx')) {\n    return 'x-wins'\n  }\n  if (lines.some(line => line === 'ooo')) {\n    return 'o-wins'\n  }\n  if (board.flat().every(cell => cell !== ' ')) {\n    return 'draw'\n  }\n  return xCount === oCount ? 'x-turn' : 'o-turn'\n}\n\nfunction Board() {\n  const { board, play } = useGameEngine()\n  return (\n    <div\n      className=\"grid grid-cols-3 gap-2\"\n      role=\"grid\"\n      aria-label=\"Tic-Tac-Toe Board\"\n    >\n      {board.flatMap((row, i) =>\n        row.map((cell, j) => {\n          return (\n            <Button\n              variant=\"outline\"\n              className={cn(\n                'h-20 w-20 text-4xl font-bold uppercase',\n                cell === 'x' && 'text-blue-500 dark:text-blue-400',\n                cell === 'o' && 'text-red-500, dark:text-red-400',\n                cell !== ' ' && 'pointer-events-none'\n              )}\n              onClick={() => play(i, j)}\n              aria-label={`${i + 1},${j + 1}: ${cell || 'Empty'}`}\n            >\n              {cell}\n            </Button>\n          )\n        })\n      )}\n    </div>\n  )\n}\n\nconst prettyStatus: Record<GameStatus, string> = {\n  'x-turn': 'Next player: X',\n  'o-turn': 'Next player: O',\n  'x-wins': 'X wins',\n  'o-wins': 'O wins',\n  draw: 'Draw'\n}\n\nfunction Status() {\n  const { status, reset } = useGameEngine()\n  return (\n    <section className=\"flex w-full max-w-[256px] items-center justify-between\">\n      <span className=\"text-lg font-semibold\" aria-live=\"polite\">\n        {prettyStatus[status]}\n      </span>\n      <Button onClick={reset}>Reset</Button>\n    </section>\n  )\n}\n\nexport default function Client() {\n  return (\n    <>\n      <Board />\n      <Status />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/(demos)/tic-tac-toe/page.tsx",
    "content": "import { Description } from '@/src/components/typography'\nimport { Suspense } from 'react'\nimport { SourceOnGitHub } from '../_components/source-on-github'\nimport { getMetadata } from '../demos'\nimport Client from './client'\n\nexport const metadata = getMetadata('tic-tac-toe')\n\nexport default function Page() {\n  return (\n    <>\n      <h1>{metadata.title}</h1>\n      <Description>{metadata.description}</Description>\n      <section className=\"my-12 flex flex-col items-start gap-4\">\n        <Suspense>\n          <Client />\n        </Suspense>\n      </section>\n      <SourceOnGitHub path=\"tic-tac-toe/client.tsx\" />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/builder-pattern/page.tsx",
    "content": "'use client'\n\nimport { parseAsInteger, useQueryState } from 'nuqs'\n\nexport default function BuilderPatternDemoPage() {\n  const [counter, setCounter] = useQueryState(\n    'counter',\n    parseAsInteger\n      .withOptions({\n        history: 'push',\n        shallow: false,\n        scroll: true\n      })\n      .withDefault(0)\n  )\n\n  return (\n    <>\n      <h1>Builder pattern</h1>\n      <button onClick={() => setCounter(x => x - 1)}>-</button>\n      <button onClick={() => setCounter(x => x + 1)}>+</button>\n      <button onClick={() => setCounter(null)}>Reset</button>\n      <p>{counter}</p>\n      <p>\n        <em>\n          History is pushed, network requests are sent and scroll restoration is\n          enabled.\n        </em>\n      </p>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/builder-pattern/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/compound-parsers/page.tsx",
    "content": "'use client'\n\nimport { parseAsArrayOf, parseAsJson, useQueryState } from 'nuqs'\n\nconst escaped = '-_.!~*\\'()?#/&,\"`<>{}[]|•@$£%+=:;'\n\nexport default function CompoundParsersDemo() {\n  const [code, setCode] = useQueryState(\n    'code',\n    parseAsJson(x => x).withDefault({})\n  )\n  const [array, setArray] = useQueryState(\n    'array',\n    parseAsArrayOf(\n      parseAsJson(x => x),\n      ';'\n    ).withDefault([])\n  )\n  return (\n    <>\n      <h1>Compound parsers</h1>\n      <section>\n        <h2>JSON</h2>\n        <button onClick={() => setCode({})}>Set to {'{}'}</button>\n        <button onClick={() => setCode([])}>Set to {'[]'}</button>\n        <button onClick={() => setCode([1, 2, 3])}>Set to {'[1,2,3]'}</button>\n        <button onClick={() => setCode({ hello: 'world' })}>\n          Set to {'{hello:\"world\"}'}\n        </button>\n        <button onClick={() => setCode({ escaped })}>\n          Set to escaped chars\n        </button>\n        <pre>\n          <code>{JSON.stringify(code, null, 2)}</code>\n        </pre>\n      </section>\n      <section>\n        <h2>Arrays</h2>\n        <button onClick={() => setArray(null)}>Clear</button>\n        <button onClick={() => setArray(a => [...a, {}])}>Push {'{}'}</button>\n        <button onClick={() => setArray(a => [...a, [1, 2, 3]])}>\n          Push {'[1,2,3]'}\n        </button>\n        <button onClick={() => setArray(a => [...a, { hello: 'world' }])}>\n          Push hello world\n        </button>\n        <button onClick={() => setArray(a => [...a, { escaped }])}>\n          Push escaped chars\n        </button>\n        <pre>\n          <code>{JSON.stringify(array, null, 2)}</code>\n        </pre>\n      </section>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/compound-parsers/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/crosslink/page.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\n\nconst parser = parseAsString.withDefault('')\n\nexport default function CrosslinkDemoPage() {\n  const [foo1, setFoo1] = useQueryState('foo', parser)\n  const [bar1, setBar1] = useQueryState('bar', parser)\n  const [{ foo: foo2, bar: bar2 }, setAll] = useQueryStates({\n    foo: parser,\n    bar: parser\n  })\n\n  return (\n    <>\n      <h1>Crosslink</h1>\n      <p>\n        This demo shows how <code>useQueryState</code> and{' '}\n        <code>useQueryStates</code> keys can overlap and stay in sync.\n      </p>\n      <button onClick={() => setFoo1('a')}>set foo via useQueryState</button>\n      <button onClick={() => setAll({ foo: 'b' })}>\n        set foo via useQueryStates\n      </button>\n      <button\n        onClick={() => {\n          setBar1('a')\n          setAll({ foo: 'c' })\n        }}\n      >\n        set bar via useQueryState and foo via useQueryStates\n      </button>\n      <button\n        onClick={() => {\n          setBar1(x => x.toUpperCase())\n          setAll(x => ({ foo: x.foo.toUpperCase() }))\n        }}\n      >\n        uppercase diff\n      </button>\n      <button\n        onClick={() => {\n          setFoo1(x => x + 'asdf')\n          setAll(x => ({ foo: x.foo.toUpperCase() }))\n        }}\n      >\n        foo should end with ASDF\n      </button>\n      <Link href=\"?foo=from-link&bar=from-link\">Navigate</Link>\n      <pre>\n        <code>\n          {`\n[useQueryState]  foo: ${foo1}\n[useQueryStates] foo: ${foo2}\n[useQueryState]  bar: ${bar1}\n[useQueryStates] bar: ${bar2}\n`}\n        </code>\n      </pre>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/crosslink/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/custom-parser/page.tsx",
    "content": "'use client'\n\nimport { createParser, useQueryState } from 'nuqs'\n\ntype SortingState = Record<string, 'asc' | 'desc'>\n\nconst parser = createParser({\n  parse(value) {\n    if (value === '') {\n      return null\n    }\n    const keys = value.split('|')\n    return keys.reduce<SortingState>((acc, key) => {\n      const [id, desc] = key.split(':')\n      acc[id] = desc === 'desc' ? 'desc' : 'asc'\n      return acc\n    }, {})\n  },\n  serialize(value: SortingState) {\n    return Object.entries(value)\n      .map(([id, dir]) => `${id}:${dir}`)\n      .join('|')\n  }\n})\n\nexport default function BasicCounterDemoPage() {\n  const [sort, setSort] = useQueryState('sort', parser.withDefault({}))\n  return (\n    <section>\n      <h1>Custom parser</h1>\n      <nav style={{ display: 'flex', gap: '4px' }}>\n        <span>Foo</span>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() =>\n            setSort(state => ({\n              ...state,\n              foo: 'asc'\n            }))\n          }\n        >\n          🔼\n        </button>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() =>\n            setSort(state => ({\n              ...state,\n              foo: 'desc'\n            }))\n          }\n        >\n          🔽\n        </button>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() =>\n            setSort(({ foo: _, ...state }) =>\n              Object.keys(state).length === 0 ? null : state\n            )\n          }\n        >\n          Clear\n        </button>\n        <span>{sort.foo}</span>\n      </nav>\n      <nav style={{ display: 'flex', gap: '4px' }}>\n        <span>Bar</span>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() =>\n            setSort(state => ({\n              ...state,\n              bar: 'asc'\n            }))\n          }\n        >\n          🔼\n        </button>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() =>\n            setSort(state => ({\n              ...state,\n              bar: 'desc'\n            }))\n          }\n        >\n          🔽\n        </button>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() =>\n            setSort(({ bar: _, ...state }) =>\n              Object.keys(state).length === 0 ? null : state\n            )\n          }\n        >\n          Clear\n        </button>\n        <span>{sort.bar}</span>\n      </nav>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/custom-parser/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/parsers/page.tsx",
    "content": "'use client'\n\nimport {\n  parseAsArrayOf,\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsHex,\n  parseAsInteger,\n  parseAsIsoDateTime,\n  parseAsJson,\n  parseAsString,\n  parseAsTimestamp,\n  useQueryState\n} from 'nuqs'\n\nexport default function BasicCounterDemoPage() {\n  const [str, setStr] = useQueryState('string', parseAsString.withDefault(''))\n  const [int, setInt] = useQueryState('integer', parseAsInteger.withDefault(0))\n  const [bool, setBool] = useQueryState(\n    'boolean',\n    parseAsBoolean.withDefault(false)\n  )\n  const [float, setFloat] = useQueryState('float', parseAsFloat.withDefault(0))\n  const [iso, setIso] = useQueryState(\n    'iso',\n    parseAsIsoDateTime.withDefault(new Date(0))\n  )\n  const [ts, setTs] = useQueryState(\n    'ts',\n    parseAsTimestamp.withDefault(new Date(0))\n  )\n  const [hex, setHex] = useQueryState('hex', parseAsHex.withDefault(0))\n  const [arr, setArr] = useQueryState(\n    'array',\n    parseAsArrayOf(parseAsIsoDateTime).withDefault([])\n  )\n  const [obj, setObj] = useQueryState(\n    'json',\n    parseAsJson(x => x).withDefault({})\n  )\n\n  return (\n    <>\n      <h1>Parsers</h1>\n      <ul>\n        <li>\n          String: <input value={str} onChange={e => setStr(e.target.value)} />{' '}\n          {str}\n        </li>\n        <li>\n          Integer:{' '}\n          <input\n            type=\"number\"\n            value={int}\n            onChange={e =>\n              setInt(\n                Number.isNaN(e.target.valueAsNumber)\n                  ? null\n                  : e.target.valueAsNumber\n              )\n            }\n          />{' '}\n          {int}\n        </li>\n        <li>\n          Boolean:{' '}\n          <input\n            type=\"checkbox\"\n            checked={bool}\n            onChange={e => setBool(e.target.checked)}\n          />{' '}\n          {bool ? 'true' : 'false'}\n        </li>\n        <li>\n          Float:{' '}\n          <input\n            type=\"range\"\n            min={-1}\n            max={1}\n            step={0.01}\n            value={float}\n            onChange={e =>\n              setFloat(\n                Number.isNaN(e.target.valueAsNumber)\n                  ? null\n                  : e.target.valueAsNumber\n              )\n            }\n          />{' '}\n          {float}\n        </li>\n        <li>\n          ISO Date:{' '}\n          <input\n            type=\"datetime-local\"\n            value={iso.toISOString().slice(0, -1)}\n            onChange={e => setIso(e.target.valueAsDate)}\n          />{' '}\n          {iso.toISOString()}\n        </li>\n        <li>\n          Timestamp:{' '}\n          <input\n            type=\"datetime-local\"\n            value={ts.toISOString().slice(0, -1)}\n            onChange={e => setTs(e.target.valueAsDate)}\n          />{' '}\n          {ts.toISOString()}\n        </li>\n        <li>\n          Hex:{' '}\n          <input\n            type=\"number\"\n            value={hex}\n            onChange={e => setHex(e.target.valueAsNumber)}\n          />{' '}\n          {hex}\n        </li>\n        <li>\n          Array:{' '}\n          <button onClick={() => setArr(vals => [...vals, new Date()])}>\n            Push\n          </button>\n          <button onClick={() => setArr(null)}>Clear</button>\n          <br />\n          {arr.map((d, i) => (\n            <div key={i}>{d.toISOString()}</div>\n          ))}\n        </li>\n        <li>\n          JSON: <button onClick={() => setObj({ foo: 'bar' })}>Set</button>\n          <button onClick={() => setObj(null)}>Clear</button>\n          <br />\n          {JSON.stringify(obj)}\n        </li>\n      </ul>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/parsers/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/pretty-urls/page.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\n\nconst testValues = [\n  '/home/user/.ssh/id.pub',\n  '192.168.0.0',\n  '2001:0db8:85a3:0000:0000:8a2e:0370:7334',\n  'C:\\\\> cd Program Files',\n  'proto://user:password@domain.tld:port/path?query=value&foo=bar#hash',\n  'Non\\nprintable\\tchars',\n  \"Hey! What's that sound?\",\n  '(<{[-_-]}>)',\n  JSON.stringify({ hello: 'world' }),\n  '👋🌍'\n]\n\nexport default function PrettyURLsDemoPage() {\n  const [q, setQ] = useQueryState('q')\n  return (\n    <>\n      <h1>Pretty URLs</h1>\n      <p>Value: {q}</p>\n      <ul>\n        {testValues.map(value => (\n          <li key={value}>\n            <button onClick={() => setQ(value)}>{value}</button>\n          </li>\n        ))}\n        <li>\n          <button onClick={() => setQ(decodeURIComponent(location.toString()))}>\n            Recurse\n          </button>\n        </li>\n      </ul>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/pretty-urls/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/repro-359/page.tsx",
    "content": "// https://github.com/47ng/nuqs/issues/359\n\n'use client'\n\nimport {\n  parseAsString,\n  parseAsStringEnum,\n  useQueryState,\n  useQueryStates\n} from 'nuqs'\n\nconst Component1 = () => {\n  const [param] = useQueryState('param', parseAsString)\n  console.dir({ _: 'Component1.render', param })\n  return param ? param : 'null'\n}\n\nconst Component2 = () => {\n  const [param] = useQueryState('param', parseAsString)\n  console.dir({ _: 'Component2.render', param })\n  return param ? param : 'null'\n}\n\nenum TargetComponent {\n  Comp1 = 'comp1',\n  Comp2 = 'comp2'\n}\n\nexport default function Home() {\n  const [_param, setParam] = useQueryState('param', parseAsString)\n  const [component, seComponent] = useQueryState(\n    'component',\n    parseAsStringEnum(Object.values(TargetComponent))\n  )\n  const [multiple, setMultiple] = useQueryStates({\n    param: parseAsString,\n    component: parseAsStringEnum(Object.values(TargetComponent))\n  })\n  console.dir({ _: 'Home.render', _param, component, multiple })\n  return (\n    <>\n      <h1>\n        Repro for issue{' '}\n        <a href=\"https://github.com/47ng/nuqs/issues/359\">#359</a>\n      </h1>\n      <div className=\"border p-5\">\n        {component === TargetComponent.Comp1 ? <Component1 /> : null}\n        {component === TargetComponent.Comp2 ? <Component2 /> : null}\n      </div>\n      <div className=\"flex gap-2\">\n        <button\n          onClick={() => {\n            setParam('Component1')\n            seComponent(TargetComponent.Comp1)\n          }}\n          className=\"border p-2\"\n        >\n          Component 1 (nuqs)\n        </button>\n        <button\n          onClick={() => {\n            console.log('aaa')\n            setParam('Component2')\n            seComponent(TargetComponent.Comp2)\n          }}\n          className=\"border p-2\"\n        >\n          Component 2 (nuqs)\n        </button>\n        <br />\n        <button\n          onClick={() => {\n            setMultiple({\n              param: 'Component1',\n              component: TargetComponent.Comp1\n            })\n          }}\n          className=\"border p-2\"\n        >\n          Component 1 (nuq+)\n        </button>\n        <button\n          onClick={() => {\n            setMultiple({\n              param: 'Component2',\n              component: TargetComponent.Comp2\n            })\n          }}\n          className=\"border p-2\"\n        >\n          Component 2 (nuq+)\n        </button>\n      </div>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/repro-359/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/repro-376/page.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\n\nexport default function ReproPage() {\n  const [searchQueryUrl, setSearchQueryUrl] = useQueryState('search', {\n    defaultValue: '',\n    scroll: true,\n    history: 'push'\n  })\n\n  return (\n    <>\n      <h1>Basic counter</h1>\n      <p>\n        <em>State is stored in the URL query string</em>\n      </p>\n      <div style={{ height: '120vh' }} />\n      <nav style={{ display: 'flex', gap: '4px' }}>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() => setSearchQueryUrl('https://example.com')}\n        >\n          example.com\n        </button>\n        <button\n          style={{ padding: '2px 12px' }}\n          onClick={() => setSearchQueryUrl('https://francoisbest.com')}\n        >\n          francoisbest.com\n        </button>\n        <button\n          style={{ padding: '2px 6px' }}\n          onClick={() => setSearchQueryUrl(null)}\n        >\n          Reset\n        </button>\n      </nav>\n      <p>Query: {searchQueryUrl}</p>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/repro-376/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/repro-907/page.tsx",
    "content": "// https://github.com/47ng/nuqs/issues/907\n\n'use client'\n\nimport { parseAsString, useQueryStates } from 'nuqs'\nimport { useState } from 'react'\n\nexport default function Home() {\n  const [nuqsConfig, setNuqsConfig] = useState<\n    Record<string, typeof parseAsString>\n  >({\n    p1: parseAsString,\n    p2: parseAsString\n  })\n\n  const [values] = useQueryStates(nuqsConfig)\n\n  return (\n    <>\n      <div className=\"flex gap-2\">\n        <button\n          onClick={() => setNuqsConfig({ p1: parseAsString })}\n          className=\"border p-2\"\n        >\n          Update config (remove one of the keys)\n        </button>\n\n        <button\n          onClick={() =>\n            setNuqsConfig({\n              p1: parseAsString,\n              p2: parseAsString,\n              p3: parseAsString\n            })\n          }\n          className=\"border p-2\"\n        >\n          Update config (add a new key)\n        </button>\n\n        <button\n          onClick={() =>\n            setNuqsConfig({\n              p1: parseAsString,\n              p5: parseAsString\n            })\n          }\n          className=\"border p-2\"\n        >\n          Update config (replace a key)\n        </button>\n      </div>\n      <div>Config keys: {JSON.stringify(Object.keys(nuqsConfig))}</div>\n      <div>Result: {JSON.stringify(values)}</div>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/server-side-parsing/client.tsx",
    "content": "'use client'\n\nimport { parseAsBoolean, useQueryState } from 'nuqs'\nimport { counterParser } from './parser'\n\ntype Props = {\n  serverSideCounter: number\n  children: React.ReactNode\n}\n\nexport function ServerSideParsingDemoClient({\n  serverSideCounter,\n  children\n}: Props) {\n  const [shallow, setShallow] = useQueryState(\n    'shallow',\n    parseAsBoolean.withDefault(true)\n  )\n  const [counter, setCounter] = useQueryState(\n    'counter',\n    counterParser.withOptions({ shallow })\n  )\n\n  return (\n    <>\n      <div style={{ marginBottom: '1rem' }}>\n        <input\n          type=\"checkbox\"\n          checked={shallow}\n          onChange={e => setShallow(e.target.checked)}\n          id=\"shallow\"\n        />\n        <label htmlFor=\"shallow\">Shallow?</label>\n      </div>\n      <button onClick={() => setCounter(x => x - 1)}>-</button>\n      <button onClick={() => setCounter(x => x + 1)}>+</button>\n      <button\n        onClick={async () => {\n          for (let i = 0; i < 10; ++i) {\n            // await to avoid batching all those changes together\n            await setCounter(i + 1)\n          }\n        }}\n      >\n        Burst of 10 (async)\n      </button>\n      <button\n        onClick={() => {\n          for (let i = 0; i < 10; ++i) {\n            setCounter(x => x + 1)\n          }\n        }}\n      >\n        Burst of 10 (sync)\n      </button>\n      <button onClick={() => setCounter(null)}>Reset</button>\n      <p>Client side counter: {counter}</p>\n      <p>\n        Server side counter: {serverSideCounter} <em>(client-rendered)</em>\n      </p>\n      {children}\n      <p>\n        <em>\n          Check the server console, play with the \"shallow\" switch and try\n          refreshing the page.\n        </em>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/server-side-parsing/page.tsx",
    "content": "import { Suspense } from 'react'\nimport { ServerSideParsingDemoClient } from './client'\nimport { counterParser } from './parser'\n\ntype PageProps = {\n  searchParams: {\n    counter?: string | string[]\n  }\n}\n\nexport default function ServerSideParsingDemo({ searchParams }: PageProps) {\n  const counter = counterParser.parseServerSide(searchParams.counter)\n  console.log('Server side counter: %d', counter)\n  return (\n    <>\n      <h1>Server-side parsing</h1>\n      <Suspense>\n        <ServerSideParsingDemoClient serverSideCounter={counter}>\n          <p>Server rendered counter: {counter}</p>\n        </ServerSideParsingDemoClient>\n      </Suspense>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/server-side-parsing/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/server-side-parsing/parser.ts",
    "content": "import { parseAsInteger } from 'nuqs/server'\n\nexport const counterParser = parseAsInteger.withDefault(0)\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/throttling/client.tsx",
    "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useQueryState } from 'nuqs'\nimport React from 'react'\nimport { delayParser, queryParser } from './parsers'\n\nconst autoFillMessage = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor.`\n\nexport function Client() {\n  const [isQueryLoading, startQueryTransition] = React.useTransition()\n  const [isDelayLoading, startDelayTransition] = React.useTransition()\n  const [serverDelay, setServerDelay] = useQueryState(\n    'serverDelay',\n    delayParser.withOptions({\n      shallow: false,\n      startTransition: startDelayTransition\n    })\n  )\n  const [clientDelay, setClientDelay] = useQueryState(\n    'clientDelay',\n    delayParser\n  )\n  const [q, setQ] = useQueryState(\n    'q',\n    queryParser.withOptions({\n      shallow: false,\n      throttleMs: clientDelay,\n      startTransition: startQueryTransition\n    })\n  )\n  const router = useRouter()\n\n  const timeoutRef = React.useRef<number | undefined>(undefined)\n  const [index, setIndex] = React.useState(0)\n\n  React.useEffect(() => {\n    if (index === 0) {\n      return\n    }\n    setQ(autoFillMessage.slice(0, index))\n    clearTimeout(timeoutRef.current)\n    if (index === autoFillMessage.length) {\n      return\n    }\n    timeoutRef.current = window.setTimeout(() => {\n      setIndex(i => Math.min(i + 1, autoFillMessage.length))\n    }, 80)\n  }, [index])\n\n  return (\n    <>\n      <h2>Client</h2>\n      <div>\n        <label>Server latency simulation </label>\n        <select\n          value={serverDelay}\n          onChange={e =>\n            setServerDelay(parseInt(e.target.value)).then(() =>\n              router.refresh()\n            )\n          }\n        >\n          <option value=\"0\">No delay</option>\n          <option value=\"100\">100ms</option>\n          <option value=\"200\">200ms</option>\n          <option value=\"500\">500ms</option>\n          <option value=\"1000\">1s</option>\n        </select>\n      </div>\n      <div>\n        <label>Throttle URL updates at </label>\n        <select\n          value={clientDelay}\n          onChange={e => setClientDelay(parseInt(e.target.value))}\n        >\n          <option value=\"50\">Default (50ms)</option>\n          <option value=\"100\">100ms</option>\n          <option value=\"200\">200ms</option>\n          <option value=\"500\">500ms</option>\n          <option value=\"1000\">1s</option>\n        </select>\n      </div>\n      <br />\n      <div>\n        <label>Query </label>\n        <input\n          value={q}\n          onChange={e => setQ(e.target.value)}\n          placeholder=\"Search\"\n        />\n        {timeoutRef.current ? (\n          <button\n            onClick={() => {\n              setIndex(0)\n              clearTimeout(timeoutRef.current)\n              timeoutRef.current = undefined\n              setQ(null)\n            }}\n          >\n            Cancel\n          </button>\n        ) : (\n          <button onClick={() => setIndex(1)}>Simulate typing</button>\n        )}\n        <button\n          onClick={() => {\n            setQ('foo')\n            setServerDelay(500)\n          }}\n        >\n          Set both\n        </button>\n        <p>Client state: {q || <em>empty</em>}</p>\n        <p>Query status: {isQueryLoading ? 'loading' : 'idle'}</p>\n        <p>Delay status: {isDelayLoading ? 'loading' : 'idle'}</p>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/throttling/page.tsx",
    "content": "import { setTimeout } from 'node:timers/promises'\nimport { Suspense } from 'react'\nimport { Client } from './client'\nimport { delayParser, queryParser } from './parsers'\n\ntype PageParams = {\n  searchParams: {\n    q?: string | string[]\n    serverDelay?: string | string[]\n  }\n}\n\nexport default async function ThottlingDemoPage({ searchParams }: PageParams) {\n  const serverDelay = delayParser.parseServerSide(searchParams.serverDelay)\n  const query = queryParser.parseServerSide(searchParams.q)\n  await setTimeout(serverDelay)\n  console.debug('Server query: %s', query)\n  return (\n    <>\n      <h1>Throttling</h1>\n      <p>\n        Play with the various delays, and try throttling your network connection\n        in devtools.\n      </p>\n      <p>\n        When the client is faster to update the URL than the network is to\n        re-render the server components, the server may hang under the waterfall\n        of heavy load.\n      </p>\n      <h2>Server</h2>\n      <p>Server delay: {serverDelay} ms</p>\n      <p>Server query: {query}</p>\n      <Suspense>\n        <Client />\n      </Suspense>\n      <p>\n        <a href=\"https://github.com/47ng/nuqs/tree/next/packages/docs/src/app/(pages)/playground/throttling/page.tsx\">\n          Source on GitHub\n        </a>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/_demos/throttling/parsers.ts",
    "content": "import { parseAsInteger, parseAsString } from 'nuqs/server'\n\nexport const delayParser = parseAsInteger.withDefault(0)\nexport const queryParser = parseAsString.withDefault('')\n"
  },
  {
    "path": "packages/docs/src/app/playground/debug-control.tsx",
    "content": "'use client'\n\nimport React from 'react'\n\nexport function DebugControl() {\n  const [checked, setChecked] = React.useState(() => {\n    if (typeof localStorage === 'undefined') {\n      return false\n    }\n    return localStorage.getItem('debug')?.includes('nuqs') ?? false\n  })\n  const update = React.useCallback(() => {\n    setChecked(c => {\n      const checked = !c\n      if (typeof localStorage !== 'undefined') {\n        if (checked) {\n          localStorage.setItem('debug', 'nuqs')\n        } else {\n          localStorage.removeItem('debug')\n        }\n      }\n      return checked\n    })\n  }, [])\n\n  return (\n    <label className=\"mr-auto space-x-2 text-zinc-500\">\n      <input type=\"checkbox\" checked={checked} onChange={update} />\n      <span className=\"select-none\">Console debugging</span>\n    </label>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/layout.tsx",
    "content": "import { getSharedLayoutProps } from '@/src/components/shared-layout'\nimport { DocsLayout } from 'fumadocs-ui/layouts/notebook'\nimport React, { Suspense } from 'react'\nimport { SideBanner } from '../banners'\nimport { getPlaygroundTree } from './(demos)/demos'\nimport { DebugControl } from './debug-control'\n\nconst DebugControlsSkeleton = () => (\n  <label className=\"pointer-events-none mr-auto space-x-2 text-zinc-500 opacity-50\">\n    <input type=\"checkbox\" disabled />\n    <span>Console debugging</span>\n  </label>\n)\n\nexport default function PlaygroundLayout({\n  children\n}: {\n  children: React.ReactNode\n}) {\n  const shared = getSharedLayoutProps()\n\n  return (\n    <>\n      <DocsLayout\n        tree={getPlaygroundTree()}\n        {...shared}\n        nav={{ ...shared.nav, mode: 'top' }}\n        sidebar={{\n          collapsible: false,\n          banner: SideBanner,\n          footer: (\n            <Suspense fallback={<DebugControlsSkeleton />}>\n              <DebugControl />\n            </Suspense>\n          )\n        }}\n      >\n        {children}\n      </DocsLayout>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/playground/page.tsx",
    "content": "import { Card } from 'fumadocs-ui/components/card'\nimport {\n  DocsBody,\n  DocsDescription,\n  DocsPage,\n  DocsTitle\n} from 'fumadocs-ui/page'\nimport { demos } from './(demos)/demos'\n\nexport const metadata = {\n  title: 'Playground',\n  description: 'Examples and demos of nuqs in action.'\n}\n\nexport default function PlaygroundIndexPage() {\n  return (\n    <DocsPage>\n      <DocsTitle>{metadata.title}</DocsTitle>\n      <DocsDescription>{metadata.description}</DocsDescription>\n      <DocsBody>\n        <ul className=\"not-prose my-8 space-y-2\">\n          {Object.entries(demos).map(([path, { title, description }]) => (\n            <li key={path}>\n              <Card\n                title={title}\n                description={description}\n                href={`/playground/${path}`}\n              />\n            </li>\n          ))}\n        </ul>\n      </DocsBody>\n    </DocsPage>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/registry/[name]/author.tsx",
    "content": "import { authorRegex } from '@/src/registry/schemas'\n\nexport function Author({ author }: { author: string }) {\n  const [, name, githubUser] = author.match(authorRegex)!\n  return (\n    <a\n      href={`https://github.com/${githubUser}`}\n      className=\"hover:bg-foreground/5 rounded-lg\"\n      aria-label={`Author: @${githubUser} on GitHub`}\n    >\n      <div className=\"flex items-center gap-3 rounded-lg py-1 pr-3 pl-2\">\n        <img\n          src={`https://github.com/${githubUser}.png`}\n          role=\"presentation\"\n          alt={name}\n          className=\"size-9 rounded-full\"\n        />\n        <div>\n          <p className=\"font-semibold\">{name}</p>\n          <p className=\"text-fd-muted-foreground text-xs\">@{githubUser}</p>\n        </div>\n      </div>\n    </a>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/registry/[name]/opengraph-image.tsx",
    "content": "import { generateOpengraphImage } from '@/src/components/og-image'\nimport { readRegistry, readRegistryItem } from '@/src/registry/read'\nimport { notFound } from 'next/navigation'\n\n// Image metadata\nexport { contentType, size } from '@/src/components/og-image'\nexport const dynamic = 'force-static'\n\nexport async function generateStaticParams(): Promise<{ name: string }[]> {\n  const [registry, error] = await readRegistry()\n  if (error || !registry) {\n    notFound()\n  }\n  return registry.items.map(item => ({ name: item.name }))\n}\n\n// Image generation\nexport default async function Image({ params }: PageProps<'/registry/[name]'>) {\n  const { name } = await params\n  const [item, error] = await readRegistryItem(name).catch(notFound)\n  if (error || !item) {\n    notFound()\n  }\n  return generateOpengraphImage({\n    title: item.title,\n    description: item.description\n  })\n}\n"
  },
  {
    "path": "packages/docs/src/app/registry/[name]/page.tsx",
    "content": "import { useMDXComponents } from '@/mdx-components'\nimport { rehypeCodeOptions } from '@/rehype-code.config'\nimport { CodeBlock } from '@/src/components/code-block'\nimport { H2 } from '@/src/components/typography'\nimport { readRegistry, readRegistryItem, readUsage } from '@/src/registry/read'\nimport type {\n  RegistryBuiltFile,\n  RegistryBuiltItem\n} from '@/src/registry/schemas'\nimport { SiTypescript } from '@icons-pack/react-simple-icons'\nimport { Markdown } from 'fumadocs-core/content'\nimport { rehypeCode, remarkHeading } from 'fumadocs-core/mdx-plugins'\nimport { Callout } from 'fumadocs-ui/components/callout'\nimport { Tab, Tabs } from 'fumadocs-ui/components/tabs'\nimport {\n  DocsBody,\n  DocsDescription,\n  DocsPage,\n  DocsTitle\n} from 'fumadocs-ui/page'\nimport type { Metadata } from 'next'\nimport { notFound } from 'next/navigation'\nimport remarkSmartypants from 'remark-smartypants'\nimport { Author } from './author'\n\nexport default async function Page({ params }: PageProps<'/registry/[name]'>) {\n  const { name } = await params\n  const [item, error] = await readRegistryItem(name)\n  if (error || !item) {\n    notFound()\n  }\n  const { title, description, files, author } = item\n  const isAdapter = item.categories?.includes('adapter')\n  const usage = await readUsage(name)\n  return (\n    <DocsPage\n      toc={[\n        {\n          url: '#installation',\n          title: 'Installation',\n          depth: 0\n        },\n        ...(usage\n          ? [\n              {\n                url: '#usage',\n                title: 'Usage',\n                depth: 0\n              }\n            ]\n          : [])\n      ]}\n    >\n      <DocsTitle>{title}</DocsTitle>\n      {description && <DocsDescription>{description}</DocsDescription>}\n      {author && (\n        <section className=\"flex justify-end\">\n          <Author author={author} />\n        </section>\n      )}\n      <DocsBody>\n        <H2 id=\"installation\">Installation</H2>\n        <Installation name={name} files={files} />\n        {usage && (\n          <>\n            <H2 id=\"usage\">Usage</H2>\n            <Markdown\n              components={useMDXComponents()}\n              remarkPlugins={[remarkSmartypants, remarkHeading]}\n              rehypePlugins={[[rehypeCode, rehypeCodeOptions]]}\n            >\n              {usage}\n            </Markdown>\n          </>\n        )}\n        {isAdapter && (\n          <>\n            <br />\n            <Callout type=\"warn\">\n              <p>\n                The custom adapters APIs are not yet stable and may change in\n                the future in a minor or patch release (not following SemVer).\n              </p>\n              <p>\n                Use the registry's <a href=\"/registry/rss.xml\">RSS feed</a> to\n                stay updated on any changes.\n              </p>\n            </Callout>\n          </>\n        )}\n      </DocsBody>\n    </DocsPage>\n  )\n}\n\nexport async function generateStaticParams() {\n  const [registry, error] = await readRegistry()\n  if (error || !registry) {\n    notFound()\n  }\n  return registry.items.map(item => ({ name: item.name }))\n}\n\nexport async function generateMetadata({\n  params\n}: PageProps<'/registry/[name]'>) {\n  const { name } = await params\n  const [item, error] = await readRegistryItem(name)\n  if (error || !item) {\n    notFound()\n  }\n  return {\n    title: item.title,\n    description: item.description,\n    category: item.categories?.join(', ')\n  } satisfies Metadata\n}\n\n// --\n\nfunction Installation({\n  name,\n  files\n}: Pick<RegistryBuiltItem, 'name' | 'files'>) {\n  return (\n    <Tabs items={['CLI', 'Manual']} defaultIndex={0} persist>\n      <Tab value=\"CLI\">\n        <CodeBlock\n          preHighlighted\n          code={`<pre><code><span class=\"line\">npx shadcn<span style=\"color:var(--color-muted-foreground);\">@latest</span> add @nuqs/${name}<span/></code></pre>`}\n        />\n      </Tab>\n      <Tab value=\"Manual\">\n        {files.map(file => (\n          <RegistryBuiltFile key={file.target} {...file} />\n        ))}\n      </Tab>\n    </Tabs>\n  )\n}\n\nfunction RegistryBuiltFile({\n  target,\n  content\n}: RegistryBuiltFile & { showTitle?: boolean }) {\n  return (\n    <CodeBlock\n      lang=\"ts\"\n      icon={<SiTypescript size={14} />}\n      code={content.trim()}\n      title={target.replace(/\\~\\//, '')}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/registry/[name]/twitter-image.tsx",
    "content": "import Image from './opengraph-image'\nexport default Image\nexport { contentType, size } from './opengraph-image'\n\nexport const dynamic = 'force-static'\n"
  },
  {
    "path": "packages/docs/src/app/registry/layout.tsx",
    "content": "import { getSharedLayoutProps } from '@/src/components/shared-layout'\nimport { SidebarFooter } from '@/src/components/sidebar-footer'\nimport { categorizeRegistryItems, readRegistry } from '@/src/registry/read'\nimport { DocsLayout } from 'fumadocs-ui/layouts/notebook'\nimport type { Metadata } from 'next'\nimport { notFound } from 'next/navigation'\nimport { Suspense, type ReactNode } from 'react'\nimport { SideBanner } from '../banners'\n\nexport const metadata = {\n  alternates: {\n    types: {\n      'application/rss+xml': [\n        {\n          url: '/registry/rss.xml',\n          title: '@nuqs shadcn registry RSS feed'\n        }\n      ]\n    }\n  }\n} satisfies Metadata\n\nexport default async function RegistryLayout({\n  children\n}: {\n  children: ReactNode\n}) {\n  const sharedLayoutProps = getSharedLayoutProps()\n  const [registry, error] = await readRegistry()\n  if (error || !registry) {\n    notFound()\n  }\n  const categories = categorizeRegistryItems(registry)\n  return (\n    <>\n      <DocsLayout\n        tree={{\n          $id: 'root',\n          name: 'Registry',\n          children: [\n            {\n              $id: '#introduction-heading',\n              type: 'page',\n              name: 'Shadcn Registry',\n              url: '/registry',\n              description:\n                'Use the shadcn CLI to install custom parsers, adapters and utilities from the community.'\n            },\n            {\n              $id: '#adapters-heading',\n              type: 'separator',\n              name: 'Adapters'\n            },\n            ...categories.adapter.map(item => ({\n              $id: `#${item.name}`,\n              type: 'page' as const,\n              name: item.title.replace(/adapter/gi, '').trim(),\n              url: `/registry/${item.name}`,\n              description: item.description\n            })),\n            // todo: Enable this when we have parsers\n            // {\n            //   $id: '#parsers-heading',\n            //   type: 'separator',\n            //   name: 'Parsers'\n            // },\n            // ...categories.Parsers.map(item => ({\n            //   $id: `#${item.name}`,\n            //   type: 'page' as const,\n            //   name: item.title,\n            //   url: `/registry/${item.name}`,\n            //   description: item.description\n            // })),\n            {\n              $id: '#utilities-heading',\n              type: 'separator',\n              name: 'Utilities'\n            },\n            ...categories.utility.map(item => ({\n              $id: `#${item.name}`,\n              type: 'page' as const,\n              name: item.title,\n              url: `/registry/${item.name}`,\n              description: item.description\n            }))\n          ]\n        }}\n        {...sharedLayoutProps}\n        nav={{ ...sharedLayoutProps.nav, mode: 'top' }}\n        sidebar={{\n          collapsible: false,\n          banner: SideBanner,\n          footer: (\n            <Suspense>\n              <SidebarFooter />\n            </Suspense>\n          )\n        }}\n      >\n        {children}\n      </DocsLayout>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/app/registry/page.tsx",
    "content": "import { H2 } from '@/src/components/typography'\nimport { Card, Cards } from 'fumadocs-ui/components/card'\nimport {\n  DocsBody,\n  DocsDescription,\n  DocsPage,\n  DocsTitle\n} from 'fumadocs-ui/page'\nimport { RssIcon } from 'lucide-react'\nimport type { Metadata } from 'next'\n\nexport const metadata = {\n  title: 'Shadcn Registry',\n  description:\n    'Use the shadcn CLI to install custom parsers, adapters and utilities from the community.'\n} satisfies Metadata\n\nexport default function Page() {\n  return (\n    <DocsPage>\n      <nav className=\"mb-4 flex items-center justify-between\">\n        <DocsTitle>Shadcn Registry</DocsTitle>\n        <RssFeedLink />\n      </nav>\n      <DocsDescription>\n        Use the{' '}\n        <a href=\"https://ui.shadcn.com/docs/cli\" className=\"underline\">\n          shadcn CLI\n        </a>{' '}\n        to install custom parsers, adapters, and utilities from the community.\n      </DocsDescription>\n\n      <DocsBody>\n        <H2 id=\"using-the-registry\">Using the registry</H2>\n        <p>\n          Follow the CLI instructions for each item to add it to your project,\n          or copy-paste the code snippets directly.\n        </p>\n        <H2 id=\"community-adapters\">Community Adapters</H2>\n        <Cards>\n          <InertiaCard />\n          <OneJsCard />\n          <WakuCard />\n          <Card\n            href=\"https://github.com/47ng/nuqs/issues/837\"\n            title=\"Expo Router\"\n            className=\"border-dashed\"\n            icon={\n              <svg\n                viewBox=\"0 0 24 22\"\n                fill=\"none\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n              >\n                <path\n                  d=\"M11.39 8.269c.19-.277.397-.312.565-.312.168 0 .447.035.637.312 1.49 2.03 3.95 6.075 5.765 9.06 1.184 1.945 2.093 3.44 2.28 3.63.7.714 1.66.269 2.218-.541.549-.797.701-1.357.701-1.954 0-.407-7.958-15.087-8.759-16.309C14.027.98 13.775.683 12.457.683h-.988c-1.315 0-1.505.297-2.276 1.472C8.392 3.377.433 18.057.433 18.463c0 .598.153 1.158.703 1.955.558.81 1.518 1.255 2.218.54.186-.19 1.095-1.684 2.279-3.63 1.815-2.984 4.267-7.029 5.758-9.06z\"\n                  fill=\"currentColor\"\n                />\n              </svg>\n            }\n          >\n            🚧 Coming soon to the registry, discussion on GitHub.\n          </Card>\n        </Cards>\n        {/* <H2 id=\"mcp-server\">MCP Server</H2>\n        <p>\n          Shadcn registries come with an{' '}\n          <a href=\"https://ui.shadcn.com/docs/mcp\">MCP server</a> that you can\n          use\n        </p> */}\n        <H2 id=\"rss-feed\">Staying up to date</H2>\n        <p>\n          Subscribe to the registry's{' '}\n          <a href=\"/registry/rss.xml\">\n            <RssIcon\n              className=\"mr-1 inline-block size-4 stroke-[2.5] text-orange-700 dark:text-orange-400\"\n              role=\"presentation\"\n            />\n            RSS feed\n          </a>{' '}\n          to stay updated on the latest changes and additions to the registry.\n        </p>\n      </DocsBody>\n    </DocsPage>\n  )\n}\n\nfunction RssFeedLink() {\n  return (\n    <a\n      href=\"/registry/rss.xml\"\n      className=\"text-muted-foreground flex items-center gap-1 text-sm font-medium hover:underline\"\n    >\n      <RssIcon\n        className=\"size-4 stroke-[2.5] text-orange-700 dark:text-orange-400\"\n        role=\"presentation\"\n      />\n      RSS\n    </a>\n  )\n}\n\n// --\n\nconst InertiaCard = () => (\n  <Card\n    href=\"/registry/adapter-inertia\"\n    title=\"Inertia.js\"\n    icon={\n      <svg viewBox=\"0 0 500 500\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <rect width=\"500\" height=\"500\" rx=\"250\" fill=\"url(#paint0_linear)\" />\n        <path\n          d=\"M184 165H95L181 251L95 337H184L270 251L184 165Z\"\n          fill=\"white\"\n        />\n        <path\n          d=\"M318.5 165H229.5L315.5 251L229.5 337H318.5L404.5 251L318.5 165Z\"\n          fill=\"white\"\n        />\n        <defs>\n          <linearGradient\n            id=\"paint0_linear\"\n            x1=\"35\"\n            y1=\"377.5\"\n            x2=\"632.5\"\n            y2=\"65\"\n            gradientUnits=\"userSpaceOnUse\"\n          >\n            <stop stopColor=\"#934EE7\" />\n            <stop offset=\"1\" stopColor=\"#7270EC\" />\n          </linearGradient>\n        </defs>\n      </svg>\n    }\n  >\n    The modern monolith. Usually paired with non-JS backends (Laravel, Phoenix,\n    Django, Rails).\n  </Card>\n)\n\nconst OneJsCard = () => (\n  <Card\n    href=\"/registry/adapter-onejs\"\n    title=\"One.js\"\n    icon={\n      <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"250 250 632 632\">\n        <title>One.js</title>\n        <defs>\n          <filter\n            id=\"a\"\n            width=\"252.7%\"\n            height=\"232.9%\"\n            x=\"-76.4%\"\n            y=\"-66.4%\"\n            filterUnits=\"objectBoundingBox\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"20\" />\n          </filter>\n          <filter\n            id=\"b\"\n            width=\"117.5%\"\n            height=\"160.9%\"\n            x=\"-8.7%\"\n            y=\"-30.5%\"\n            filterUnits=\"objectBoundingBox\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"20\" />\n          </filter>\n          <filter\n            id=\"c\"\n            width=\"137.3%\"\n            height=\"135.2%\"\n            x=\"-18.6%\"\n            y=\"-17.6%\"\n            filterUnits=\"objectBoundingBox\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"45\" />\n          </filter>\n        </defs>\n        <g fill=\"none\" fillRule=\"evenodd\" transform=\"translate(271 271)\">\n          <circle cx=\"295\" cy=\"295\" r=\"295\" fill=\"#f5ca05\" />\n          <g transform=\"translate(202 97)\">\n            <circle cx=\"108.5\" cy=\"113.5\" r=\"94.5\" fill=\"#000\" />\n            <path\n              fill=\"#fff\"\n              d=\"M108.5 0c57.47 0 106.01 50.592 108.414 113 2.406 62.408-46.133 113-108.414 113S-2.32 175.408.086 113 51.03 0 108.5 0m1.919 53.418c-5.071 0-7.102 1.627-8.628 3.253-1.528 1.627-1.546 7.59-2.058 8.675-.513 1.084-10.22 4.88-13.803 6.506-3.589 1.627-3.686 11.928-.115 14.096 1.496.908 3.54.771 5.636.503l.7-.092c2.682-.365 5.358-.776 7 .674l5.56 64.518q-11.8 2.07-13.677 3.795c-1.878 1.725-2.652 3.253-2.163 8.675q.49 5.422 7.885 6.506l40.655-4.88q4.185-3.253 4.122-8.674-.064-5.421-5.33-7.048h-9.447l-8.2-90c-1.035-5.422-3.067-6.507-8.137-6.507\"\n            />\n          </g>\n          <ellipse\n            cx=\"200.009\"\n            cy=\"137.737\"\n            fill=\"#fff\"\n            filter=\"url(#a)\"\n            opacity=\".453\"\n            rx=\"35.358\"\n            ry=\"40.635\"\n            transform=\"rotate(46 200.009 137.737)\"\n          />\n          <path\n            fill=\"#fff\"\n            d=\"M521 138q-57.745-59.67-101.34-81.12c-43.594-21.448-72.116-28.396-124.563-30.024s-69.499 11.086-111.368 30.025Q141.859 75.82 75 126.41q41.697-55.308 91.84-79.538C216.98 22.643 259.733 10 295.096 10s78.644 10.009 133.536 36.873Q483.525 73.737 521 138\"\n            filter=\"url(#b)\"\n            opacity=\".773\"\n          />\n          <path\n            fill=\"#000\"\n            d=\"M361.057 44Q467.012 163.91 469.78 197.133c2.767 33.224 12.874 57.686-10.028 124.091s-44.333 86.6-76.528 119.339Q351.03 473.302 75 488.087 274.326 590 309.862 590q35.538 0 105.557-24.818 98.666-45.24 134.056-105.081c35.39-59.842 42.48-119.234 40.111-167.92s-7.22-95.866-34.305-119.866q-27.086-24-194.224-128.315\"\n            filter=\"url(#c)\"\n            opacity=\".096\"\n          />\n        </g>\n        <script />\n      </svg>\n    }\n  >\n    One aims to make web + native with React and React Native much simpler, and\n    faster.\n  </Card>\n)\n\nconst WakuCard = () => (\n  <Card\n    href=\"/registry/adapter-waku\"\n    title=\"Waku\"\n    icon={\n      <div\n        role=\"presentation\"\n        className=\"flex size-4 items-center justify-center\"\n      >\n        ⛩️\n      </div>\n    }\n  >\n    The minimal React framework.\n  </Card>\n)\n"
  },
  {
    "path": "packages/docs/src/app/registry/rss.xml/route.ts",
    "content": "import { getLastModified } from '@/src/lib/get-last-modified'\nimport { readRegistry } from '@/src/registry/read'\nimport { notFound } from 'next/navigation'\n\nexport const dynamic = 'force-static'\n\nexport async function GET() {\n  const rssXml = await generateRssXml()\n  return new Response(rssXml, {\n    headers: {\n      'Content-Type': 'application/rss+xml'\n    }\n  })\n}\n\nasync function generateRssXml() {\n  const baseUrl = 'https://nuqs.dev/registry'\n  const [registry, error] = await readRegistry()\n  if (error || !registry) {\n    notFound()\n  }\n  const items = await Promise.all(\n    registry.items.map(async item => {\n      return `<item>\n      <title>${item.title}</title>\n      <link>${baseUrl}/${item.name}</link>\n      <guid>${baseUrl}/${item.name}</guid>\n      <description>${item.description}</description>\n      <pubDate>${(\n        await getLastModified(`/src/registry/items/${item.name}.json`)\n      ).toUTCString()}</pubDate>\n    </item>`\n    })\n  )\n  return `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n  <channel>\n    <title>@nuqs shadcn registry</title>\n    <link>${baseUrl}/registry</link>\n    <description>Use the shadcn CLI to install custom parsers, adapters and utilities from the community.</description>\n    <atom:link href=\"http://${baseUrl}/registry/rss.xml\" rel=\"self\" type=\"application/rss+xml\" />\n  ${items.join('')}\n  </channel>\n</rss>\n`\n}\n"
  },
  {
    "path": "packages/docs/src/app/robots.ts",
    "content": "import { getBaseUrl } from '@/src/lib/url'\nimport type { MetadataRoute } from 'next'\n\nexport default function robots(): MetadataRoute.Robots {\n  const baseUrl = getBaseUrl()\n  if (process.env.VERCEL_ENV === 'production') {\n    return {\n      rules: [\n        {\n          userAgent: '*',\n          allow: '/',\n          disallow: ['/api']\n        }\n      ],\n      sitemap: `${baseUrl}/sitemap.xml`,\n      host: baseUrl\n    }\n  }\n  // No crawling on preview environments\n  return {\n    rules: {\n      userAgent: '*',\n      disallow: ['/']\n    }\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/app/sitemap.ts",
    "content": "import { getLastModified } from '@/src/lib/get-last-modified'\nimport { getBaseUrl } from '@/src/lib/url'\nimport type { MetadataRoute } from 'next'\nimport { demos } from './playground/(demos)/demos'\nimport { blog, source } from './source'\n\nexport const revalidate = false // Disable ISR\nexport const dynamic = 'force-static'\n\ntype SitemapEntry = MetadataRoute.Sitemap[number]\n\nconst staticPages = [\n  '', // home page\n  '/blog',\n  '/react-paris',\n  '/stats',\n  '/users',\n  '/playground'\n] as const\n\nconst staticPagesChangeFrequency: Record<\n  (typeof staticPages)[number],\n  SitemapEntry['changeFrequency']\n> = {\n  '': 'weekly',\n  '/stats': 'hourly',\n  '/users': 'weekly',\n  '/blog': 'monthly',\n  '/playground': 'monthly',\n  // Archived\n  '/react-paris': 'never'\n}\n\nexport default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n  const baseUrl = getBaseUrl()\n\n  // todo: Automate retrieval of static pages\n  // Static pages in app/(pages)\n\n  // Get all docs pages from fumadocs (excluding llm-only pages)\n  const docsPages = source\n    .getPages()\n    .filter(page => page.data.exposeTo.includes('user'))\n    .map<Promise<SitemapEntry>>(async page => ({\n      url: `${baseUrl}${page.url}`,\n      lastModified: await getLastModified(`/content/docs/${page.path}`),\n      changeFrequency: 'weekly' as const,\n      priority: 0.8\n    }))\n\n  // Get all blog posts from fumadocs\n  const blogPages = blog.getPages().map<SitemapEntry>(page => ({\n    url: `${baseUrl}${page.url}`,\n    lastModified: page.data.date ? new Date(page.data.date) : new Date(),\n    changeFrequency: 'monthly',\n    priority: 0.6\n  }))\n\n  // Get all playground demo pages\n  const playgroundPages = Object.keys(demos).map<Promise<SitemapEntry>>(\n    async demoPath => ({\n      url: `${baseUrl}/playground/${demoPath}`,\n      lastModified: await getLastModified(\n        `/src/app/playground/(demos)/${demoPath}/page.tsx`\n      ),\n      changeFrequency: 'monthly',\n      priority: 0.4\n    })\n  )\n\n  // Static pages\n  const staticPageEntries = staticPages.map<SitemapEntry>(path => ({\n    url: `${baseUrl}${path}`,\n    lastModified: new Date(),\n    changeFrequency: staticPagesChangeFrequency[path],\n    priority: path === '' ? 1.0 : 0.5\n  }))\n\n  return Promise.all([\n    ...staticPageEntries,\n    ...docsPages,\n    ...blogPages,\n    ...playgroundPages\n  ])\n}\n"
  },
  {
    "path": "packages/docs/src/app/source.ts",
    "content": "import { blog as blogPosts, docs, meta } from '@/.source'\nimport type { Item } from 'fumadocs-core/page-tree'\nimport { type InferPageType, loader } from 'fumadocs-core/source'\nimport { createMDXSource } from 'fumadocs-mdx/runtime/next'\n\nconst mdxSource = createMDXSource(docs, meta)\n\nexport const source = loader({\n  baseUrl: '/docs',\n  source: mdxSource,\n  pageTree: {\n    // Filter out llm-only pages from the sidebar\n    transformers: [\n      {\n        file(node, filePath): Item {\n          if (!filePath) return node\n          const file = this.storage.read(filePath)\n          if (\n            file?.format === 'page' &&\n            !file.data.exposeTo?.includes('user')\n          ) {\n            // @ts-expect-error not an Item, but works at runtime\n            return undefined\n          }\n          return node\n        }\n      }\n    ]\n  }\n})\n\n// Full source without filtering for llm-full.txt\nexport const fullSource = loader({\n  baseUrl: '/docs',\n  source: mdxSource\n})\n\nexport const blog = loader({\n  baseUrl: '/blog',\n  source: createMDXSource(blogPosts, [])\n})\n\nexport type Page = InferPageType<typeof source>\n"
  },
  {
    "path": "packages/docs/src/app/styles/tweaks.css",
    "content": "@layer utilities {\n  /* Background & line spacing for code blocks */\n  /* eg: The demo code block on the landing page */\n  figure.bg-fd-card:has(pre),\n  figure.bg-fd-secondary:has(pre) {\n    @apply bg-zinc-50/50 leading-normal dark:bg-zinc-900/50;\n  }\n}\n\n@layer components {\n  /* Line up the Callout left coloured border with the outer border */\n  .bg-\\(--callout-color\\)\\/50[role='none'] {\n    margin-left: -5.5px;\n    margin-right: 5.5px;\n  }\n}\n\n/* Center-align copy button in code blocks with single line */\n@layer utilities {\n  figure:has(pre code > .line:only-child) > div:has(button) {\n    @apply top-2 bottom-2 flex items-center;\n  }\n}\n\n/* Highlighted words in code blocks */\n.shiki:not(.not-fumadocs-codeblock *) code span.highlighted-word {\n  @apply -mx-1 rounded-sm border-none px-1;\n}\n\n/* Highlighted lines in code blocks */\n@supports (color: color-mix(in lab, red, red)) {\n  .shiki:not(.not-fumadocs-codeblock *) code .line.highlighted {\n    background-color: var(--color-fd-secondary);\n  }\n}\n\n.shiki:not(.not-fumadocs-codeblock *):has(> code .line) {\n  & code {\n    /*\n      Reduce vertical padding to allow two inline code blocks\n      on adjacent lines to avoid overlapping.\n    */\n    padding-block: 1.5px;\n  }\n}\n\n/* Avoid a flash of black (in dark mode) in the navbar background when scrolling */\nheader#nd-nav.bg-fd-background\\/80,\nheader#nd-subnav.bg-fd-background\\/80 {\n  background-color: hsla(var(--background) / 0.8);\n}\n\n/* Tabs with code blocks (eg: loaders) */\n@layer utilities {\n  div:has(> [role='tablist']) {\n    @apply bg-fd-card;\n    & [role='tablist'] {\n      @apply border-border border-b;\n    }\n    & > [role='tabpanel'] {\n      @apply rounded-t-none;\n      & figure:has(pre) {\n        @apply rounded-t-none border-t-0;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/components/47ng.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport React from 'react'\n\nconst sizes = {\n  6: 'w-6 h-6',\n  8: 'w-8 h-8',\n  16: 'w-16 h-16'\n} as const\n\ntype Logo47ngProps = React.ComponentProps<'svg'> & {\n  size?: keyof typeof sizes\n  background?: boolean\n}\n\nexport function Logo47ng({\n  size = 8,\n  background = true,\n  className,\n  ...props\n}: Logo47ngProps) {\n  const wh = sizes[size]\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      aria-label=\"Logo\"\n      viewBox=\"0 0 148 144\"\n      width={size * 4 + 'px'}\n      height={size * 4 + 'px'}\n      className={cn(wh, 'overflow-visible', className)}\n      {...props}\n    >\n      {background && <circle fill=\"#fff\" cx=\"74\" cy=\"72\" r=\"85\" />}\n      <path\n        d=\"M99.4622 61.7822C99.6547 64.3862 97.4912 66.0106 96.2381 67.6166C92.354 72.5825 88.3737 77.5347 83.9525 82.205C74.3494 82.705 62.9984 82.7484 53.5484 83.1259C51.744 83.1972 49.2684 83.8309 48.6347 82.0513C48.3722 79.4031 50.6115 77.81 51.8587 76.2166C55.7303 71.2644 59.6778 66.3819 63.9903 61.6284C73.8815 61.0584 85.4056 61.1609 95.1631 60.7072C96.769 60.63 98.925 60.0991 99.4622 61.7822ZM109.291 19.5531C110.909 18.3309 112.694 17.3069 113.744 16.0213C114.556 15.0294 116.073 12.6878 116.047 10.9538C116.003 8.06813 111.959 2.36688 110.058 1.27938C106.219 -0.921559 103.014 3.44188 100.384 5.88594C86.9544 18.3628 75.13 31.6013 62.1475 44.5831C61.124 45.6069 60.1006 47.1678 59.0772 47.5006C57.285 48.0831 54.1687 47.8653 51.859 47.9616C43.9512 48.2944 36.4394 48.1344 28.979 47.8078C18.8184 47.36 10.584 45.1975 0.5703 45.3509C-0.30595 50.4375 2.48967 55.825 5.17717 58.5569C9.06092 62.5047 16.6044 63.0103 24.6794 63.0103C31.66 63.0103 39.1525 62.5878 45.1028 62.3959C42.4922 65.5178 39.114 69.0181 35.7359 72.8369C32.9715 75.9663 28.2497 80.0675 26.8294 84.0478C25.249 88.4753 27.8725 93.5491 29.5937 96.9469C30.9181 99.5634 32.9784 104.183 36.3506 104.778C37.4765 104.976 39.044 104.414 40.4965 104.164C44.9628 103.403 48.7694 102.302 52.1672 101.399C57.7081 99.9287 62.8906 99.5888 68.444 98.4816C60.4528 107.248 49.46 115.949 39.5753 123.512C38.0715 124.665 36.3375 125.707 35.1215 126.89C33.9125 128.068 32.089 130.641 32.0506 132.727C32.0122 134.966 33.7972 137.417 34.9684 139.176C36.184 141.006 38.0844 143.354 40.3428 143.322C42.5119 143.29 45.5125 140.04 47.2531 138.408C53.594 132.464 59.2625 126.398 65.2197 120.441C71.1831 114.478 77.2612 108.573 83.0322 102.321C84.4656 100.766 87.2362 96.89 89.0209 96.3325C90.9278 95.7378 93.8909 95.9678 96.239 95.8725C104.019 95.545 111.953 95.6928 119.273 96.025C129.395 96.4863 137.808 98.8091 147.528 98.3294C148.02 92.7109 145.724 88.1431 142.921 85.2763C135.94 78.1231 115.153 81.6284 102.996 81.4375C105.548 78.3022 108.888 74.8728 112.208 71.1491C115.235 67.7575 119.509 64.085 121.115 60.0925C122.964 55.505 120.13 50.3288 118.352 46.7331C117.001 44.0009 115.017 39.49 111.441 39.055C109.036 38.7606 105.849 39.8997 103.302 40.4369C95.439 42.0944 88.0997 44.4869 79.6544 45.3509C87.8362 36.5534 99.0975 27.2247 109.291 19.5531Z\"\n        fill=\"#2f2f2f\"\n        className={background ? undefined : 'dark:fill-white'}\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/ai/page-actions.tsx",
    "content": "// Full credits to fumadocs (https://www.fumadocs.dev/) for this component!\n\n'use client'\n\nimport { cn } from '@/src/lib/utils'\nimport { cva } from 'class-variance-authority'\nimport { buttonVariants } from 'fumadocs-ui/components/ui/button'\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from 'fumadocs-ui/components/ui/popover'\nimport { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'\nimport {\n  Check,\n  ChevronDown,\n  Copy,\n  ExternalLinkIcon,\n  Link,\n  MessageCircleIcon\n} from 'lucide-react'\nimport { useMemo, useState } from 'react'\n\nconst cache = new Map<string, string>()\n\ntype MarkdownButtonsProps = {\n  markdownUrl: string\n}\n\nexport function CopyAsMarkdownButton({\n  /**\n   * A URL to fetch the raw Markdown/MDX content of page\n   */\n  markdownUrl\n}: MarkdownButtonsProps) {\n  const [isLoading, setLoading] = useState(false)\n  const [checked, onClick] = useCopyButton(async () => {\n    const cached = cache.get(markdownUrl)\n    if (cached) return navigator.clipboard.writeText(cached)\n\n    setLoading(true)\n    try {\n      await navigator.clipboard.write([\n        new ClipboardItem({\n          'text/plain': fetch(markdownUrl).then(async res => {\n            const content = await res.text()\n            cache.set(markdownUrl, content)\n            return content\n          })\n        })\n      ])\n    } finally {\n      setLoading(false)\n    }\n  })\n\n  return (\n    <button\n      disabled={isLoading}\n      className={cn(\n        buttonVariants({\n          color: 'secondary',\n          size: 'sm',\n          className: '[&_svg]:text-fd-muted-foreground gap-2 [&_svg]:size-3.5'\n        })\n      )}\n      onClick={onClick}\n    >\n      {checked ? <Check /> : <Copy />}\n      Copy as Markdown\n    </button>\n  )\n}\n\nexport function CopyMarkdownUrlButton({ markdownUrl }: MarkdownButtonsProps) {\n  const [checked, triggerCopy] = useCopyButton(() => {\n    const fullUrl = new URL(markdownUrl, location.origin)\n    return navigator.clipboard.writeText(fullUrl.toString())\n  })\n  const blockMiddleMouseScroll = (e: React.MouseEvent<HTMLButtonElement>) => {\n    if (e.button === 1) {\n      e.preventDefault() // block middle mouse default behavior (scroll)\n    }\n  }\n  const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n    // If cmd+click or ctrl+click or middle mouse click (open in new tab),\n    // open the link instead of copying\n    if (\n      event.metaKey ||\n      event.ctrlKey ||\n      event.button === 1 // middle mouse\n    ) {\n      event.preventDefault()\n      const fullUrl = new URL(markdownUrl, location.origin)\n      window.open(fullUrl, '_blank', 'noreferrer noopener')\n      return\n    }\n    triggerCopy(event)\n  }\n\n  return (\n    <button\n      className={cn(\n        buttonVariants({\n          color: 'secondary',\n          size: 'sm',\n          className: '[&_svg]:text-fd-muted-foreground gap-2 [&_svg]:size-3.5'\n        })\n      )}\n      onClick={onClick}\n      onAuxClick={onClick}\n      onMouseDown={blockMiddleMouseScroll}\n    >\n      {checked ? <Check /> : <Link />}\n      Copy Markdown URL\n    </button>\n  )\n}\n\nconst optionVariants = cva(\n  'text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4'\n)\n\nexport function ViewOptions({\n  markdownUrl,\n  githubUrl\n}: {\n  /**\n   * A URL to the raw Markdown/MDX content of page\n   */\n  markdownUrl: string\n\n  /**\n   * Source file URL on GitHub\n   */\n  githubUrl: string\n}) {\n  const items = useMemo(() => {\n    const fullMarkdownUrl =\n      typeof window !== 'undefined'\n        ? new URL(markdownUrl, window.location.origin)\n        : 'loading'\n    const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`\n\n    return [\n      {\n        title: 'Open on GitHub',\n        href: githubUrl,\n        icon: (\n          <svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\">\n            <title>GitHub</title>\n            <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n          </svg>\n        )\n      },\n      {\n        title: 'Open in ChatGPT',\n        href: `https://chatgpt.com/?${new URLSearchParams({\n          hints: 'search',\n          q\n        })}`,\n        icon: (\n          <svg\n            role=\"img\"\n            viewBox=\"0 0 24 24\"\n            fill=\"currentColor\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <title>OpenAI</title>\n            <path d=\"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z\" />\n          </svg>\n        )\n      },\n      {\n        title: 'Open in Claude',\n        href: `https://claude.ai/new?${new URLSearchParams({\n          q\n        })}`,\n        icon: (\n          <svg\n            fill=\"currentColor\"\n            role=\"img\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <title>Anthropic</title>\n            <path d=\"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z\" />\n          </svg>\n        )\n      },\n      {\n        title: 'Open in T3 Chat',\n        href: `https://t3.chat/new?${new URLSearchParams({\n          q\n        })}`,\n        icon: <MessageCircleIcon />\n      },\n      {\n        title: 'Open in Scira AI',\n        href: `https://scira.ai/?${new URLSearchParams({\n          q\n        })}`,\n        icon: (\n          <svg\n            viewBox=\"0 0 910 934\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <title>Scira AI</title>\n            <path\n              d=\"M647.664 197.775C569.13 189.049 525.5 145.419 516.774 66.8849C508.048 145.419 464.418 189.049 385.884 197.775C464.418 206.501 508.048 250.131 516.774 328.665C525.5 250.131 569.13 206.501 647.664 197.775Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M516.774 304.217C510.299 275.491 498.208 252.087 480.335 234.214C462.462 216.341 439.058 204.251 410.333 197.775C439.059 191.3 462.462 179.209 480.335 161.336C498.208 143.463 510.299 120.06 516.774 91.334C523.25 120.059 535.34 143.463 553.213 161.336C571.086 179.209 594.49 191.3 623.216 197.775C594.49 204.251 571.086 216.341 553.213 234.214C535.34 252.087 523.25 275.491 516.774 304.217Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M857.5 508.116C763.259 497.644 710.903 445.288 700.432 351.047C689.961 445.288 637.605 497.644 543.364 508.116C637.605 518.587 689.961 570.943 700.432 665.184C710.903 570.943 763.259 518.587 857.5 508.116Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"20\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M700.432 615.957C691.848 589.05 678.575 566.357 660.383 548.165C642.191 529.973 619.499 516.7 592.593 508.116C619.499 499.533 642.191 486.258 660.383 468.066C678.575 449.874 691.848 427.181 700.432 400.274C709.015 427.181 722.289 449.874 740.481 468.066C758.673 486.258 781.365 499.533 808.271 508.116C781.365 516.7 758.673 529.973 740.481 548.165C722.289 566.357 709.015 589.05 700.432 615.957Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"20\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M889.949 121.237C831.049 114.692 798.326 81.9698 791.782 23.0692C785.237 81.9698 752.515 114.692 693.614 121.237C752.515 127.781 785.237 160.504 791.782 219.404C798.326 160.504 831.049 127.781 889.949 121.237Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M791.782 196.795C786.697 176.937 777.869 160.567 765.16 147.858C752.452 135.15 736.082 126.322 716.226 121.237C736.082 116.152 752.452 107.324 765.16 94.6152C777.869 81.9065 786.697 65.5368 791.782 45.6797C796.867 65.5367 805.695 81.9066 818.403 94.6152C831.112 107.324 847.481 116.152 867.338 121.237C847.481 126.322 831.112 135.15 818.403 147.858C805.694 160.567 796.867 176.937 791.782 196.795Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M760.632 764.337C720.719 814.616 669.835 855.1 611.872 882.692C553.91 910.285 490.404 924.255 426.213 923.533C362.022 922.812 298.846 907.419 241.518 878.531C184.19 849.643 134.228 808.026 95.4548 756.863C56.6815 705.7 30.1238 646.346 17.8129 583.343C5.50207 520.339 7.76433 455.354 24.4266 393.359C41.089 331.364 71.7099 274.001 113.947 225.658C156.184 177.315 208.919 139.273 268.117 114.442\"\n              stroke=\"currentColor\"\n              strokeWidth=\"30\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        )\n      }\n    ]\n  }, [githubUrl, markdownUrl])\n\n  return (\n    <Popover>\n      <PopoverTrigger\n        className={cn(\n          buttonVariants({\n            color: 'secondary',\n            size: 'sm',\n            className: 'gap-2'\n          })\n        )}\n      >\n        Open…\n        <ChevronDown className=\"text-fd-muted-foreground size-3.5\" />\n      </PopoverTrigger>\n      <PopoverContent className=\"flex flex-col\">\n        {items.map(item => (\n          <a\n            key={item.href}\n            href={item.href}\n            rel=\"noreferrer noopener\"\n            target=\"_blank\"\n            className={cn(optionVariants())}\n          >\n            {item.icon}\n            {item.title}\n            <ExternalLinkIcon className=\"text-fd-muted-foreground ms-auto size-3.5\" />\n          </a>\n        ))}\n      </PopoverContent>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/audience.tsx",
    "content": "import type { ReactNode } from 'react'\n\ntype AudienceProps = {\n  children?: ReactNode\n}\n\n/**\n * Content that is only shown to humans viewing the HTML docs.\n * Hidden from LLMs (llms-full.txt and .md endpoints) via the remark-audience plugin.\n */\nexport function HumanContent({ children }: AudienceProps) {\n  return <>{children}</>\n}\n\n/**\n * Content that is only shown to LLMs (llms-full.txt and .mdx endpoints).\n * Hidden from humans viewing the HTML docs.\n */\nexport function LLMContent(_props: AudienceProps) {\n  return null\n}\n"
  },
  {
    "path": "packages/docs/src/components/code-block-defs.ts",
    "content": "import type { CodeBlockProps as FumaDocsCodeBlockProps } from 'fumadocs-ui/components/codeblock'\nimport type { BundledLanguage } from 'shiki/bundle/web'\n\nexport type { BundledLanguage }\n\nexport type CodeBlockProps = Omit<FumaDocsCodeBlockProps, 'children'> & {\n  code: string\n  preHighlighted?: boolean\n  compact?: boolean\n  lang?: BundledLanguage\n}\n"
  },
  {
    "path": "packages/docs/src/components/code-block-highlighter.skeleton.ts",
    "content": "export function renderCodeSkeleton(code: string) {\n  return `<pre><code>${code\n    .split('\\n')\n    .map(line => `<span class=\"line\">${line}</span>`)\n    .join('')}</code></pre>`\n}\n"
  },
  {
    "path": "packages/docs/src/components/code-block-highlighter.ts",
    "content": "import { rehypeCodeOptions } from '@/rehype-code.config'\nimport {\n  transformerNotationHighlight,\n  transformerNotationWordHighlight\n} from '@shikijs/transformers'\nimport type { BundledLanguage } from 'shiki/bundle/web'\nimport { codeToHtml } from 'shiki/bundle/web'\n\nexport async function highlight(code: string, lang: BundledLanguage) {\n  return await codeToHtml(code, {\n    ...rehypeCodeOptions,\n    lang,\n    transformers: [\n      transformerNotationHighlight({\n        matchAlgorithm: 'v3'\n      }),\n      transformerNotationWordHighlight({\n        matchAlgorithm: 'v3'\n      })\n    ]\n  })\n}\n\nexport function renderCodeSkeleton(code: string) {\n  return `<pre><code>${code\n    .split('\\n')\n    .map(line => `<span class=\"line\">${line}</span>`)\n    .join('')}</code></pre>`\n}\n"
  },
  {
    "path": "packages/docs/src/components/code-block.client.tsx",
    "content": "'use client'\n\nimport {\n  CodeBlock as FumaDocsCodeBlock,\n  Pre\n} from 'fumadocs-ui/components/codeblock'\nimport { use, useEffect, useState } from 'react'\nimport type { CodeBlockProps } from './code-block-defs'\n\n// For some reason, importing this directly causes a build error:\n// Error: Element type is invalid: expected a string (for built-in components)\n// or a class/function (for composite components) but got: undefined.\nconst importHighlight = import('./code-block-highlighter')\n\nexport function CodeBlock({\n  code,\n  lang = 'tsx',\n  compact = false,\n  preHighlighted = false,\n  ...props\n}: CodeBlockProps) {\n  const [html, setHtml] = useState<string | null>(null)\n  const { highlight, renderCodeSkeleton } = use(importHighlight)\n  useEffect(() => {\n    ;(async () => {\n      const x = preHighlighted ? code : await highlight(code, lang)\n      setHtml(x)\n    })()\n  }, [code, lang, highlight, preHighlighted])\n  return (\n    <FumaDocsCodeBlock\n      // @ts-expect-error\n      custom={compact ? 'compact' : undefined}\n      {...props}\n    >\n      <Pre\n        dangerouslySetInnerHTML={{\n          __html: html ?? renderCodeSkeleton(code)\n        }}\n      />\n    </FumaDocsCodeBlock>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/code-block.tsx",
    "content": "import {\n  CodeBlock as FumaDocsCodeBlock,\n  Pre\n} from 'fumadocs-ui/components/codeblock'\nimport type { CodeBlockProps } from './code-block-defs'\nimport { highlight } from './code-block-highlighter'\n\nexport async function CodeBlock({\n  code,\n  lang = 'tsx',\n  compact = false,\n  preHighlighted = false,\n  ...props\n}: CodeBlockProps) {\n  const highlighted = preHighlighted ? code : await highlight(code, lang)\n  return (\n    <FumaDocsCodeBlock\n      // @ts-expect-error\n      custom={compact ? 'compact' : undefined}\n      {...props}\n    >\n      <Pre dangerouslySetInnerHTML={{ __html: highlighted }} />\n    </FumaDocsCodeBlock>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/countdown.tsx",
    "content": "'use client'\n\nimport { cn } from '@/src/lib/utils'\nimport NumberFlow, { NumberFlowGroup } from '@number-flow/react'\nimport { ComponentProps, ReactNode, useEffect, useState } from 'react'\n\ntype CountdownProps = ComponentProps<'div'> & {\n  targetDate: Date\n  expiredMessage?: ReactNode\n}\n\nexport function Countdown({\n  targetDate,\n  expiredMessage = null,\n  className,\n  ...props\n}: CountdownProps) {\n  const remaining = targetDate.getTime() - Date.now()\n  const [days, setDays] = useState(() =>\n    Math.floor(remaining / 1000 / 60 / 60 / 24)\n  )\n  const [hours, setHours] = useState(\n    () => Math.floor(remaining / 1000 / 60 / 60) % 24\n  )\n  const [minutes, setMinutes] = useState(\n    () => Math.floor(remaining / 1000 / 60) % 60\n  )\n  const [seconds, setSeconds] = useState(() =>\n    Math.floor((remaining / 1000) % 60)\n  )\n\n  useEffect(() => {\n    const timer = setInterval(() => {\n      const remaining = targetDate.getTime() - Date.now()\n      setDays(Math.floor(remaining / 1000 / 60 / 60 / 24))\n      setHours(Math.floor(remaining / 1000 / 60 / 60) % 24)\n      setMinutes(Math.floor(remaining / 1000 / 60) % 60)\n      setSeconds(Math.floor((remaining / 1000) % 60))\n      if (remaining <= 0) {\n        clearInterval(timer)\n      }\n    }, 500)\n\n    return () => clearInterval(timer)\n  }, [targetDate])\n\n  if (days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0) {\n    return expiredMessage\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-baseline justify-center font-bold',\n        days > 0 ? 'gap-1 text-xl' : 'text-3xl',\n        className\n      )}\n      title={targetDate.toISOString()}\n      {...props}\n    >\n      <NumberFlowGroup>\n        {days > 0 && (\n          <NumberFlow\n            trend={-1}\n            value={days}\n            className=\"&::part(suffix):text-gray-400 tabular-nums\"\n            suffix=\"d\"\n          />\n        )}\n        <NumberFlow\n          trend={-1}\n          value={hours}\n          className={'tabular-nums'}\n          suffix={days > 0 ? 'h' : undefined}\n          digits={{ 1: { max: 2 } }}\n          format={{ minimumIntegerDigits: 2 }}\n        />\n        <NumberFlow\n          trend={-1}\n          value={minutes}\n          className=\"tabular-nums\"\n          prefix={days > 0 ? undefined : ':'}\n          suffix={days > 0 ? 'm' : undefined}\n          digits={{ 1: { max: 5 } }}\n          format={{ minimumIntegerDigits: 2 }}\n        />\n        <NumberFlow\n          trend={-1}\n          value={seconds}\n          className=\"tabular-nums\"\n          prefix={days > 0 ? undefined : ':'}\n          suffix={days > 0 ? 's' : undefined}\n          digits={{ 1: { max: 5 } }}\n          format={{ minimumIntegerDigits: 2 }}\n        />\n      </NumberFlowGroup>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/favicon.tsx",
    "content": "export function Favicon() {\n  const env = process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? 'development'\n  const isDev = env === 'development'\n  const filePath = isDev ? '/icon.dev.svg' : '/icon.svg'\n  return (\n    <head>\n      <link rel=\"icon\" href={filePath} sizes=\"any\" type=\"image/svg+xml\" />\n    </head>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/feature-support-matrix.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { Callout } from 'fumadocs-ui/components/callout'\nimport { CheckCircle, XCircle } from 'lucide-react'\nimport { FRAMEWORK_ICONS, FRAMEWORKS, type Frameworks } from './frameworks'\nimport {\n  TooltipPopover,\n  TooltipPopoverContent,\n  TooltipPopoverTrigger\n} from './ui/tooltip-popover'\n\nexport type FeatureSupportMatrixProps = {\n  introducedInVersion: string\n  deprecatedInVersion?: string\n  highlightUnsupported?: boolean\n  hideFrameworks?: boolean\n  support?: Support\n}\n\ntype Support = {\n  supported: boolean\n  frameworks: 'all' | Frameworks[]\n}\n\nexport function FeatureSupportMatrix({\n  introducedInVersion,\n  deprecatedInVersion,\n  highlightUnsupported = false,\n  hideFrameworks = false,\n  support = { supported: true, frameworks: 'all' }\n}: FeatureSupportMatrixProps) {\n  const supportedIn = support.supported\n    ? resolveFrameworks(support.frameworks)\n    : FRAMEWORKS.filter(fw => support.frameworks.includes(fw) === false)\n  const notSupportedIn = FRAMEWORKS.filter(\n    fw => supportedIn.includes(fw) === false\n  )\n  return (\n    <Callout\n      type={deprecatedInVersion ? 'warning' : 'success'}\n      className=\"pr-1\"\n    >\n      <div className=\"flex flex-wrap gap-4\">\n        <span className=\"text-balance\">\n          Introduced in version <strong>{introducedInVersion}</strong>\n          {deprecatedInVersion ? (\n            <>\n              , and deprecated in version <strong>{deprecatedInVersion}</strong>\n            </>\n          ) : (\n            '.'\n          )}\n        </span>\n        {!hideFrameworks && (\n          <div className=\"not-prose ml-auto flex items-center gap-1 text-xl\">\n            <TooltipPopover delayDuration={100}>\n              <TooltipPopoverTrigger\n                className={cn(\n                  '-my-2 flex flex-shrink-0 items-center gap-2 rounded-lg py-2 pr-2.5 pl-2',\n                  // Outline variant\n                  'bg-green-50/25 outline -outline-offset-1 outline-green-500/25 dark:bg-green-950/30 dark:outline-green-500/10'\n                )}\n              >\n                {dedupeFrameworks(supportedIn).map(framework => {\n                  const Icon = FRAMEWORK_ICONS[framework]\n                  return (\n                    <span\n                      key={framework}\n                      className=\"pointer-events-none flex-shrink-0 select-none\"\n                    >\n                      <Icon />\n                    </span>\n                  )\n                })}\n                <CheckCircle\n                  size={16}\n                  className=\"flex-shrink-0 stroke-[2.25] text-green-500\"\n                  role=\"presentation\"\n                />\n              </TooltipPopoverTrigger>\n              <TooltipPopoverContent className=\"py-2 text-xs\">\n                {supportedIn.length === FRAMEWORKS.length ? (\n                  'This feature is supported in all frameworks.'\n                ) : supportedIn.length === 1 ? (\n                  <>Only supported in {supportedIn[0]}.</>\n                ) : (\n                  <>\n                    Supported frameworks:\n                    <ul className=\"mt-1 ml-3 list-disc\">\n                      {supportedIn.map(fw => (\n                        <li key={fw}>{fw}</li>\n                      ))}\n                    </ul>\n                  </>\n                )}\n              </TooltipPopoverContent>\n            </TooltipPopover>\n            {notSupportedIn.length > 0 && (\n              <TooltipPopover delayDuration={100}>\n                <TooltipPopoverTrigger\n                  className={cn(\n                    '-my-2 flex flex-shrink-0 items-center gap-2 rounded-lg py-2 pr-2.5 pl-2',\n                    // Gray-out effect\n                    !highlightUnsupported &&\n                      'opacity-50 grayscale transition-all will-change-transform hover:opacity-100 hover:grayscale-0 focus:opacity-100 focus:grayscale-0 data-[state=open]:opacity-100 data-[state=open]:grayscale-0',\n                    // Outline variant\n                    'bg-amber-50/25 outline -outline-offset-1 outline-amber-500/25 dark:bg-amber-950/30 dark:outline-amber-500/10'\n                  )}\n                >\n                  {dedupeFrameworks(notSupportedIn).map(framework => {\n                    const Icon = FRAMEWORK_ICONS[framework]\n                    return (\n                      <span\n                        key={framework}\n                        className=\"pointer-events-none flex-shrink-0 select-none\"\n                      >\n                        <Icon />\n                      </span>\n                    )\n                  })}\n                  <XCircle\n                    size={18}\n                    className=\"flex-shrink-0 stroke-2 text-amber-500\"\n                    role=\"presentation\"\n                  />\n                </TooltipPopoverTrigger>\n                <TooltipPopoverContent className=\"py-2 text-xs\">\n                  {notSupportedIn.length === 1 ? (\n                    <>Not supported in {notSupportedIn[0]}.</>\n                  ) : (\n                    <>\n                      Not supported in:\n                      <ul className=\"mt-1 ml-3 list-disc\">\n                        {notSupportedIn.map(fw => (\n                          <li key={fw}>{fw}</li>\n                        ))}\n                      </ul>\n                    </>\n                  )}\n                </TooltipPopoverContent>\n              </TooltipPopover>\n            )}\n          </div>\n        )}\n      </div>\n    </Callout>\n  )\n}\n\nfunction dedupeFrameworks(frameworks: readonly Frameworks[]) {\n  // If both Next.js app router and pages router are included, only keep Next.js app router\n  if (\n    frameworks.includes('Next.js (app router)') &&\n    frameworks.includes('Next.js (pages router)')\n  ) {\n    frameworks = frameworks.filter(fw => fw !== 'Next.js (pages router)')\n  }\n  return frameworks\n}\n\nfunction resolveFrameworks(\n  frameworks: 'all' | Frameworks[]\n): readonly Frameworks[] {\n  if (frameworks === 'all') {\n    return FRAMEWORKS\n  }\n  return frameworks\n}\n"
  },
  {
    "path": "packages/docs/src/components/frameworks.client.tsx",
    "content": "// For actual isolation of masks & gradient IDs with useId,\n// this needs to be a client component.\n// See https://github.com/47ng/nuqs/pull/1117\n\n'use client'\n\nimport { useId } from 'react'\nimport type { IconProps } from './frameworks'\n\nexport function NextJS({ className, ...props }: IconProps) {\n  const id = useId()\n  return (\n    <svg\n      aria-label=\"Next.js (app & pages routers)\"\n      width=\"1em\"\n      height=\"1em\"\n      viewBox=\"0 0 180 180\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      {...props}\n    >\n      <mask\n        id={'mask0_408_139' + id}\n        style={{\n          maskType: 'alpha'\n        }}\n        maskUnits=\"userSpaceOnUse\"\n        x={0}\n        y={0}\n        width={180}\n        height={180}\n      >\n        <circle cx={90} cy={90} r={90} fill=\"black\" />\n      </mask>\n      <g mask={`url(#mask0_408_139${id})`}>\n        <circle\n          cx={90}\n          cy={90}\n          r={87}\n          fill=\"black\"\n          stroke=\"white\"\n          strokeWidth={6}\n        />\n        <path\n          d=\"M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z\"\n          fill={`url(#paint0_linear_408_139${id})`}\n        />\n        <rect\n          x={115}\n          y={54}\n          width={12}\n          height={72}\n          fill={`url(#paint1_linear_408_139${id})`}\n        />\n      </g>\n      <defs>\n        <linearGradient\n          id={'paint0_linear_408_139' + id}\n          x1={109}\n          y1={116.5}\n          x2={144.5}\n          y2={160.5}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stopColor=\"white\" />\n          <stop offset={1} stopColor=\"white\" stopOpacity={0} />\n        </linearGradient>\n        <linearGradient\n          id={'paint1_linear_408_139' + id}\n          x1={121}\n          y1={54}\n          x2={120.799}\n          y2={106.875}\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stopColor=\"white\" />\n          <stop offset={1} stopColor=\"white\" stopOpacity={0} />\n        </linearGradient>\n      </defs>\n    </svg>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/frameworks.tsx",
    "content": "import { type ComponentProps, type JSX } from 'react'\nimport { NextJS } from './frameworks.client'\nexport { NextJS }\n\nexport const FRAMEWORKS = [\n  'Next.js (app router)',\n  'Next.js (pages router)',\n  'React SPA',\n  'React Router (v6)',\n  'React Router (v7)',\n  'Remix',\n  'TanStack Router',\n  'Testing adapter'\n] as const\n\nexport type Frameworks = (typeof FRAMEWORKS)[number]\nexport type IconProps = ComponentProps<'svg'> & ComponentProps<'img'> & {}\n\nexport const FRAMEWORK_ICONS: Record<\n  Frameworks,\n  (props: IconProps) => JSX.Element\n> = {\n  'Next.js (app router)': NextJS,\n  'Next.js (pages router)': NextJS,\n  'React SPA': ReactSPA,\n  'React Router (v6)': ReactRouter,\n  'React Router (v7)': ReactRouterV7,\n  Remix: Remix,\n  'TanStack Router': TanStackRouter,\n  'Testing adapter': Vitest\n}\n\nexport function Vite({ className, ...props }: IconProps) {\n  return (\n    <svg\n      aria-label=\"Vite\"\n      viewBox=\"0 0 256 257\"\n      width=\"0.9em\"\n      height=\"0.9em\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      preserveAspectRatio=\"xMidYMid\"\n      className={className}\n      {...props}\n    >\n      <defs>\n        <linearGradient\n          x1=\"-.828%\"\n          y1=\"7.652%\"\n          x2=\"57.636%\"\n          y2=\"78.411%\"\n          id=\"a\"\n        >\n          <stop stopColor=\"#41D1FF\" offset=\"0%\" />\n          <stop stopColor=\"#BD34FE\" offset=\"100%\" />\n        </linearGradient>\n        <linearGradient\n          x1=\"43.376%\"\n          y1=\"2.242%\"\n          x2=\"50.316%\"\n          y2=\"89.03%\"\n          id=\"b\"\n        >\n          <stop stopColor=\"#FFEA83\" offset=\"0%\" />\n          <stop stopColor=\"#FFDD35\" offset=\"8.333%\" />\n          <stop stopColor=\"#FFA800\" offset=\"100%\" />\n        </linearGradient>\n      </defs>\n      <path\n        d=\"M255.153 37.938 134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z\"\n        fill=\"url(#a)\"\n      />\n      <path\n        d=\"M185.432.063 96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028 72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z\"\n        fill=\"url(#b)\"\n      />\n    </svg>\n  )\n}\n\nexport function ReactSPA({ className, ...props }: IconProps) {\n  return (\n    <svg\n      aria-label=\"React (Single Page Application)\"\n      width=\"1em\"\n      height=\"1em\"\n      viewBox=\"0 0 569 512\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      {...props}\n    >\n      <g fill=\"none\" fillRule=\"evenodd\">\n        <g\n          transform=\"translate(-227, -256)\"\n          fillRule=\"nonzero\"\n          className=\"fill-[#087EA4] dark:fill-[#58C4DC]\"\n        >\n          <g transform=\"translate(227, 256)\">\n            <path d=\"M285.5,201 C255.400481,201 231,225.400481 231,255.5 C231,285.599519 255.400481,310 285.5,310 C315.599519,310 340,285.599519 340,255.5 C340,225.400481 315.599519,201 285.5,201\" />\n            <path d=\"M568.959856,255.99437 C568.959856,213.207656 529.337802,175.68144 466.251623,150.985214 C467.094645,145.423543 467.85738,139.922107 468.399323,134.521063 C474.621631,73.0415145 459.808523,28.6686204 426.709856,9.5541429 C389.677085,-11.8291748 337.36955,3.69129898 284.479928,46.0162134 C231.590306,3.69129898 179.282771,-11.8291748 142.25,9.5541429 C109.151333,28.6686204 94.3382249,73.0415145 100.560533,134.521063 C101.102476,139.922107 101.845139,145.443621 102.708233,151.02537 C97.4493791,153.033193 92.2908847,155.161486 87.3331099,157.39017 C31.0111824,182.708821 0,217.765415 0,255.99437 C0,298.781084 39.6220545,336.307301 102.708233,361.003527 C101.845139,366.565197 101.102476,372.066633 100.560533,377.467678 C94.3382249,438.947226 109.151333,483.32012 142.25,502.434597 C153.629683,508.887578 166.52439,512.186771 179.603923,511.991836 C210.956328,511.991836 247.567589,495.487529 284.479928,465.972527 C321.372196,495.487529 358.003528,511.991836 389.396077,511.991836 C402.475265,512.183856 415.36922,508.884856 426.75,502.434597 C459.848667,483.32012 474.661775,438.947226 468.439467,377.467678 C467.897524,372.066633 467.134789,366.565197 466.291767,361.003527 C529.377946,336.347457 569,298.761006 569,255.99437 M389.155214,27.1025182 C397.565154,26.899606 405.877839,28.9368502 413.241569,33.0055186 C436.223966,46.2772304 446.540955,82.2775015 441.522965,131.770345 C441.181741,135.143488 440.780302,138.556788 440.298575,141.990165 C414.066922,134.08804 387.205771,128.452154 360.010724,125.144528 C343.525021,103.224055 325.192524,82.7564475 305.214266,63.9661533 C336.586743,39.7116483 366.032313,27.1025182 389.135142,27.1025182 M378.356498,310.205598 C368.204912,327.830733 357.150626,344.919965 345.237759,361.405091 C325.045049,363.479997 304.758818,364.51205 284.459856,364.497299 C264.167589,364.51136 243.888075,363.479308 223.702025,361.405091 C211.820914,344.919381 200.80007,327.83006 190.683646,310.205598 C180.532593,292.629285 171.306974,274.534187 163.044553,255.99437 C171.306974,237.454554 180.532593,219.359455 190.683646,201.783142 C200.784121,184.229367 211.770999,167.201087 223.601665,150.764353 C243.824636,148.63809 264.145559,147.579168 284.479928,147.591877 C304.772146,147.579725 325.051559,148.611772 345.237759,150.68404 C357.109048,167.14607 368.136094,184.201112 378.27621,201.783142 C388.419418,219.363718 397.644825,237.458403 405.915303,255.99437 C397.644825,274.530337 388.419418,292.625022 378.27621,310.205598 M419.724813,290.127366 C426.09516,307.503536 431.324985,325.277083 435.380944,343.334682 C417.779633,348.823635 399.836793,353.149774 381.668372,356.285142 C388.573127,345.871232 395.263781,335.035679 401.740334,323.778483 C408.143291,312.655143 414.144807,301.431411 419.805101,290.207679 M246.363271,390.377981 C258.848032,391.140954 271.593728,391.582675 284.5,391.582675 C297.406272,391.582675 310.232256,391.140954 322.737089,390.377981 C310.880643,404.583418 298.10766,417.997563 284.5,430.534446 C270.921643,417.999548 258.18192,404.585125 246.363271,390.377981 Z M187.311556,356.244986 C169.137286,353.123646 151.187726,348.810918 133.578912,343.334682 C137.618549,325.305649 142.828222,307.559058 149.174827,290.207679 C154.754833,301.431411 160.736278,312.655143 167.239594,323.778483 C173.74291,334.901824 180.467017,345.864539 187.311556,356.285142 M149.174827,221.760984 C142.850954,204.473938 137.654787,186.794745 133.619056,168.834762 C151.18418,163.352378 169.085653,159.013101 187.211197,155.844146 C180.346585,166.224592 173.622478,176.986525 167.139234,188.210257 C160.65599,199.433989 154.734761,210.517173 149.074467,221.760984 M322.616657,121.590681 C310.131896,120.827708 297.3862,120.385987 284.379568,120.385987 C271.479987,120.385987 258.767744,120.787552 246.242839,121.590681 C258.061488,107.383537 270.801211,93.9691137 284.379568,81.4342157 C297.99241,93.9658277 310.765727,107.380324 322.616657,121.590681 Z M401.70019,188.210257 C395.196875,176.939676 388.472767,166.09743 381.527868,155.68352 C399.744224,158.819049 417.734224,163.151949 435.380944,168.654058 C431.331963,186.680673 426.122466,204.426664 419.785029,221.781062 C414.205023,210.55733 408.203506,199.333598 401.720262,188.230335 M127.517179,131.790423 C122.438973,82.3176579 132.816178,46.2973086 155.778503,33.0255968 C163.144699,28.9632474 171.455651,26.9264282 179.864858,27.1225964 C202.967687,27.1225964 232.413257,39.7317265 263.785734,63.9862316 C243.794133,82.7898734 225.448298,103.270812 208.949132,125.204763 C181.761691,128.528025 154.90355,134.14313 128.661281,141.990165 C128.199626,138.556788 127.778115,135.163566 127.456963,131.790423 M98.4529773,182.106474 C101.54406,180.767925 104.695358,179.429376 107.906872,178.090828 C114.220532,204.735668 122.781793,230.7969 133.498624,255.99437 C122.761529,281.241316 114.193296,307.357063 107.8868,334.058539 C56.7434387,313.076786 27.0971497,284.003505 27.0971497,255.99437 C27.0971497,229.450947 53.1907013,202.526037 98.4529773,182.106474 Z M155.778503,478.963143 C132.816178,465.691432 122.438973,429.671082 127.517179,380.198317 C127.838331,376.825174 128.259842,373.431953 128.721497,369.978497 C154.953686,377.878517 181.814655,383.514365 209.009348,386.824134 C225.500295,408.752719 243.832321,429.233234 263.805806,448.042665 C220.069,481.834331 180.105722,492.97775 155.838719,478.963143 M441.502893,380.198317 C446.520883,429.691161 436.203894,465.691432 413.221497,478.963143 C388.974566,493.017906 348.991216,481.834331 305.274481,448.042665 C325.241364,429.232737 343.566681,408.752215 360.050868,386.824134 C387.245915,383.516508 414.107066,377.880622 440.338719,369.978497 C440.820446,373.431953 441.221885,376.825174 441.563109,380.198317 M461.193488,334.018382 C454.869166,307.332523 446.294494,281.231049 435.561592,255.99437 C446.289797,230.744081 454.857778,204.629101 461.173416,177.930202 C512.216417,198.911955 541.942994,227.985236 541.942994,255.99437 C541.942994,284.003505 512.296705,313.076786 461.153344,334.058539\" />\n          </g>\n        </g>\n      </g>\n    </svg>\n  )\n}\n\nexport function Vitest({ className, ...props }: IconProps) {\n  return (\n    <svg\n      aria-label=\"Vitest\"\n      viewBox=\"0 0 256 234\"\n      width=\"1em\"\n      height=\"1em\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      preserveAspectRatio=\"xMidYMid\"\n      className={className}\n      {...props}\n    >\n      <path\n        d=\"m192.115 70.808-61.2 88.488a5.27 5.27 0 0 1-2.673 2.002 5.285 5.285 0 0 1-3.343-.005 5.25 5.25 0 0 1-2.66-2.01 5.214 5.214 0 0 1-.903-3.203l2.45-48.854-39.543-8.386a5.256 5.256 0 0 1-2.292-1.118 5.222 5.222 0 0 1-1.83-4.581 5.226 5.226 0 0 1 .895-2.383L142.218 2.27a5.279 5.279 0 0 1 6.016-1.996 5.243 5.243 0 0 1 2.66 2.01c.643.942.96 2.066.903 3.203l-2.45 48.855 39.542 8.386a5.262 5.262 0 0 1 2.293 1.117 5.21 5.21 0 0 1 1.829 4.582 5.212 5.212 0 0 1-.896 2.382Z\"\n        fill=\"#FCC72B\"\n      />\n      <path\n        d=\"M128.025 233.537a12.356 12.356 0 0 1-8.763-3.63l-57.828-57.823a12.389 12.389 0 0 1 .023-17.5 12.394 12.394 0 0 1 17.5-.024l49.068 49.061L234.917 96.733a12.39 12.39 0 0 1 17.523 17.524l-115.655 115.65a12.343 12.343 0 0 1-8.76 3.63Z\"\n        fill=\"#729B1B\"\n      />\n      <path\n        d=\"M127.975 233.537a12.356 12.356 0 0 0 8.763-3.63l57.828-57.823a12.385 12.385 0 0 0 3.605-8.754 12.395 12.395 0 0 0-12.375-12.376 12.4 12.4 0 0 0-8.755 3.606l-49.066 49.061L21.082 96.733a12.392 12.392 0 0 0-17.524 17.524l115.656 115.65a12.347 12.347 0 0 0 8.76 3.63Z\"\n        fillOpacity={0.5}\n        fill=\"#729B1B\"\n      />\n    </svg>\n  )\n}\n\nexport function ReactRouter({ className, ...props }: IconProps) {\n  return (\n    <svg\n      aria-label=\"React Router\"\n      width=\"1em\"\n      height=\"1em\"\n      viewBox=\"0 0 94 61\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      {...props}\n      style={{ transform: 'scale(1.2)', ...props.style }}\n    >\n      <path\n        d=\"M72.7315 20.9357C70.0548 20.0941 68.6725 20.3778 65.8649 20.071C61.5246 19.5976 59.7954 17.9013 59.0619 13.5356C58.6514 11.0985 59.1361 7.53022 58.0881 5.32106C56.0839 1.10875 51.3943 -0.780439 46.6828 0.297843C42.7049 1.20956 39.3951 5.18518 39.2117 9.266C39.0021 13.9254 41.657 17.901 46.2156 19.273C48.3814 19.9261 50.6825 20.2548 52.9444 20.4214C57.0925 20.7238 57.4113 23.0297 58.5335 24.9277C59.2409 26.1243 59.9264 27.3034 59.9264 30.8714C59.9264 34.4394 59.2365 35.6185 58.5335 36.8151C57.4113 38.7087 56.0271 39.9491 51.879 40.2559C49.6171 40.4225 47.3116 40.7513 45.1502 41.4044C40.5916 42.7807 37.9367 46.7519 38.1463 51.4113C38.3297 55.4921 41.6395 59.4678 45.6174 60.3795C50.3289 61.4621 55.0185 59.5686 57.0227 55.3563C58.075 53.1471 58.6514 50.6443 59.0619 48.2072C59.7998 43.8414 61.5289 42.1451 65.8649 41.6717C68.6725 41.3649 71.5783 41.6717 74.2093 40.177C76.9895 38.1456 79.4734 35.0968 79.4734 30.8714C79.4734 26.6459 76.7967 22.2156 72.7315 20.9357Z\"\n        fill=\"#F44250\"\n      />\n      <path\n        d=\"M28.1997 40.7739C22.7285 40.7739 18.2656 36.3027 18.2656 30.8213C18.2656 25.3399 22.7285 20.8687 28.1997 20.8687C33.6709 20.8687 38.1338 25.3399 38.1338 30.8213C38.1338 36.2983 33.6665 40.7739 28.1997 40.7739Z\"\n        className=\"fill-[#121212] dark:fill-white\"\n      />\n      <path\n        d=\"M9.899 61C4.43661 60.9868 -0.0130938 56.498 2.89511e-05 51.0122C0.0132099 45.5353 4.4936 41.0773 9.96914 41.0948C15.4359 41.108 19.8856 45.5968 19.8681 51.0825C19.8549 56.5551 15.3745 61.0131 9.899 61Z\"\n        className=\"fill-[#121212] dark:fill-white\"\n      />\n      <path\n        d=\"M83.7137 60.9998C78.2339 61.0304 73.7361 56.5901 73.7052 51.122C73.6747 45.632 78.1068 41.1258 83.5646 41.0949C89.0444 41.0643 93.5423 45.5046 93.5731 50.9727C93.6036 56.4583 89.1716 60.9689 83.7137 60.9998Z\"\n        className=\"fill-[#121212] dark:fill-white\"\n      />\n    </svg>\n  )\n}\n\nexport function ReactRouterV7({ className, ...props }: IconProps) {\n  return (\n    <svg\n      width=\"1em\"\n      height=\"1em\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"100 0 72 72\"\n      className={className}\n      {...props}\n      style={{ transform: 'scale(1.35)', ...props.style }}\n    >\n      <path\n        d=\"M131.275 20.963v-6.645h33.722V22.5c-14.946 12.02-15.149 20.667-15.223 35.744h-11.087c.227-15.835 3.452-24.876 13.139-35.33h-18.585a1.957 1.957 0 0 1-1.963-1.951h-.003Z\"\n        fill=\"#F44250\"\n      />\n      <path\n        d=\"M117.144 27.06h-9.85l12.595 31.186h13.349c.23-13.575 3.147-22.385 10.052-31.19h-10.573a2.1 2.1 0 0 0-1.683.838c-3.042 4.041-5.51 11.168-5.51 16.058l-5.89-15.755a1.756 1.756 0 0 0-1.647-1.138h-.843Z\"\n        className=\"fill-black dark:fill-white\"\n      />\n    </svg>\n  )\n}\n\nexport function Remix({ className, ...props }: IconProps) {\n  return (\n    <svg\n      aria-label=\"Remix\"\n      viewBox=\"0 0 256 297\"\n      width=\"0.9em\"\n      height=\"0.9em\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={className}\n      {...props}\n    >\n      <path\n        d=\"M141.675 0C218.047 0 256 36.35 256 94.414c0 43.43-26.707 71.753-62.785 76.474 30.455 6.137 48.259 23.604 51.54 58.065l.474 6.337.415 5.924.358 5.542.249 4.179.267 4.93.138 2.814.198 4.47.159 4.222.079 2.427.107 3.888.092 4.446.033 2.148.06 6.226.02 6.496v3.885h-78.758l.004-1.62.028-3.147.047-3.065.136-7.424.035-2.489.027-3.902-.004-2.496-.023-2.617-.032-2.054-.064-2.876-.094-3.05-.125-3.242-.16-3.455-.096-1.813-.16-2.833-.186-2.976-.287-4.204-.247-3.342a116.56 116.56 0 0 0-.247-3.02l-.202-1.934c-2.6-22.827-11.655-32.157-27.163-35.269l-1.307-.245a60.184 60.184 0 0 0-2.704-.408l-1.397-.164c-.236-.025-.472-.05-.71-.073l-1.442-.127-1.471-.103-1.502-.081-1.514-.058-1.544-.039-1.574-.018L0 198.74V136.9h127.62c2.086 0 4.108-.04 6.066-.12l1.936-.095 1.893-.122 1.85-.15c.305-.028.608-.056.909-.086l1.785-.193a86.3 86.3 0 0 0 3.442-.475l1.657-.28c20.709-3.755 31.063-14.749 31.063-36.2 0-24.075-16.867-38.666-50.602-38.666H0V0h141.675ZM83.276 250.785c10.333 0 14.657 5.738 16.197 11.23l.203.79.167.782.109.617.046.306.078.603.058.59.023.29.031.569.01.278.008.54v29.507H0v-46.102h83.276Z\"\n        className=\"fill-black dark:fill-white\"\n        fillRule=\"nonzero\"\n      />\n    </svg>\n  )\n}\n\nexport function TanStackRouter({ className, ...props }: IconProps) {\n  return (\n    <img\n      src=\"/tanstack-logo.png\"\n      alt=\"TanStack Router\"\n      width=\"1em\"\n      height=\"1em\"\n      className={className}\n      {...props}\n      style={{ width: '1em', height: '1em', ...props.style }}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/github-profile.tsx",
    "content": "type GitHubProfileProps = {\n  handle: string\n  name?: string\n  url?: string\n}\n\nexport function GitHubProfile({\n  handle,\n  name = handle,\n  url = `https://github.com/${handle}`\n}: GitHubProfileProps) {\n  return (\n    <span>\n      <img\n        src={`https://github.com/${handle}.png`}\n        alt={`${name}'s avatar`}\n        role=\"presentation\"\n        width=\"16px\"\n        height=\"16px\"\n        className=\"not-prose inset-0 mr-1.5 ml-0.5 inline size-5 rounded-full align-middle\"\n      />\n      <a href={url}>{name}</a>\n    </span>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/kibo-ui/contribution-graph/index.tsx",
    "content": "\"use client\";\n\nimport type { Day as WeekDay } from \"date-fns\";\nimport {\n  differenceInCalendarDays,\n  eachDayOfInterval,\n  formatISO,\n  getDay,\n  getMonth,\n  getYear,\n  nextDay,\n  parseISO,\n  subWeeks,\n} from \"date-fns\";\nimport {\n  type CSSProperties,\n  createContext,\n  Fragment,\n  type HTMLAttributes,\n  type ReactNode,\n  useContext,\n  useMemo,\n} from \"react\";\nimport { cn } from \"@/src/lib/utils\";\n\nexport type Activity = {\n  date: string;\n  count: number;\n  level: number;\n};\n\ntype Week = Array<Activity | undefined>;\n\nexport type Labels = {\n  months?: string[];\n  weekdays?: string[];\n  totalCount?: string;\n  legend?: {\n    less?: string;\n    more?: string;\n  };\n};\n\ntype MonthLabel = {\n  weekIndex: number;\n  label: string;\n};\n\nconst DEFAULT_MONTH_LABELS = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\n\nconst DEFAULT_LABELS: Labels = {\n  months: DEFAULT_MONTH_LABELS,\n  weekdays: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n  totalCount: \"{{count}} activities in {{year}}\",\n  legend: {\n    less: \"Less\",\n    more: \"More\",\n  },\n};\n\ntype ContributionGraphContextType = {\n  data: Activity[];\n  weeks: Week[];\n  blockMargin: number;\n  blockRadius: number;\n  blockSize: number;\n  fontSize: number;\n  labels: Labels;\n  labelHeight: number;\n  maxLevel: number;\n  totalCount: number;\n  weekStart: WeekDay;\n  year: number;\n  width: number;\n  height: number;\n};\n\nconst ContributionGraphContext =\n  createContext<ContributionGraphContextType | null>(null);\n\nconst useContributionGraph = () => {\n  const context = useContext(ContributionGraphContext);\n\n  if (!context) {\n    throw new Error(\n      \"ContributionGraph components must be used within a ContributionGraph\"\n    );\n  }\n\n  return context;\n};\n\nconst fillHoles = (activities: Activity[]): Activity[] => {\n  if (activities.length === 0) {\n    return [];\n  }\n\n  // Sort activities by date to ensure correct date range\n  const sortedActivities = [...activities].sort((a, b) =>\n    a.date.localeCompare(b.date)\n  );\n\n  const calendar = new Map<string, Activity>(\n    activities.map((a) => [a.date, a])\n  );\n\n  const firstActivity = sortedActivities[0] as Activity;\n  const lastActivity = sortedActivities.at(-1);\n\n  if (!lastActivity) {\n    return [];\n  }\n\n  return eachDayOfInterval({\n    start: parseISO(firstActivity.date),\n    end: parseISO(lastActivity.date),\n  }).map((day) => {\n    const date = formatISO(day, { representation: \"date\" });\n\n    if (calendar.has(date)) {\n      return calendar.get(date) as Activity;\n    }\n\n    return {\n      date,\n      count: 0,\n      level: 0,\n    };\n  });\n};\n\nconst groupByWeeks = (\n  activities: Activity[],\n  weekStart: WeekDay = 0\n): Week[] => {\n  if (activities.length === 0) {\n    return [];\n  }\n\n  const normalizedActivities = fillHoles(activities);\n  const firstActivity = normalizedActivities[0] as Activity;\n  const firstDate = parseISO(firstActivity.date);\n  const firstCalendarDate =\n    getDay(firstDate) === weekStart\n      ? firstDate\n      : subWeeks(nextDay(firstDate, weekStart), 1);\n\n  const paddedActivities = [\n    ...(new Array(differenceInCalendarDays(firstDate, firstCalendarDate)).fill(\n      undefined\n    ) as Activity[]),\n    ...normalizedActivities,\n  ];\n\n  const numberOfWeeks = Math.ceil(paddedActivities.length / 7);\n\n  return new Array(numberOfWeeks)\n    .fill(undefined)\n    .map((_, weekIndex) =>\n      paddedActivities.slice(weekIndex * 7, weekIndex * 7 + 7)\n    );\n};\n\nconst getMonthLabels = (\n  weeks: Week[],\n  monthNames: string[] = DEFAULT_MONTH_LABELS\n): MonthLabel[] => {\n  return weeks\n    .reduce<MonthLabel[]>((labels, week, weekIndex) => {\n      const firstActivity = week.find((activity) => activity !== undefined);\n\n      if (!firstActivity) {\n        throw new Error(\n          `Unexpected error: Week ${weekIndex + 1} is empty: [${week}].`\n        );\n      }\n\n      const month = monthNames[getMonth(parseISO(firstActivity.date))];\n\n      if (!month) {\n        const monthName = new Date(firstActivity.date).toLocaleString(\"en-US\", {\n          month: \"short\",\n        });\n        throw new Error(\n          `Unexpected error: undefined month label for ${monthName}.`\n        );\n      }\n\n      const prevLabel = labels.at(-1);\n\n      if (weekIndex === 0 || !prevLabel || prevLabel.label !== month) {\n        return labels.concat({ weekIndex, label: month });\n      }\n\n      return labels;\n    }, [])\n    .filter(({ weekIndex }, index, labels) => {\n      const minWeeks = 3;\n\n      if (index === 0) {\n        return labels[1] && labels[1].weekIndex - weekIndex >= minWeeks;\n      }\n\n      if (index === labels.length - 1) {\n        return weeks.slice(weekIndex).length >= minWeeks;\n      }\n\n      return true;\n    });\n};\n\nexport type ContributionGraphProps = HTMLAttributes<HTMLDivElement> & {\n  data: Activity[];\n  blockMargin?: number;\n  blockRadius?: number;\n  blockSize?: number;\n  fontSize?: number;\n  labels?: Labels;\n  maxLevel?: number;\n  style?: CSSProperties;\n  totalCount?: number;\n  weekStart?: WeekDay;\n  children: ReactNode;\n  className?: string;\n};\n\nexport const ContributionGraph = ({\n  data,\n  blockMargin = 4,\n  blockRadius = 2,\n  blockSize = 12,\n  fontSize = 14,\n  labels: labelsProp = undefined,\n  maxLevel: maxLevelProp = 4,\n  style = {},\n  totalCount: totalCountProp = undefined,\n  weekStart = 0,\n  className,\n  ...props\n}: ContributionGraphProps) => {\n  const maxLevel = Math.max(1, maxLevelProp);\n  const weeks = useMemo(() => groupByWeeks(data, weekStart), [data, weekStart]);\n  const LABEL_MARGIN = 8;\n\n  const labels = { ...DEFAULT_LABELS, ...labelsProp };\n  const labelHeight = fontSize + LABEL_MARGIN;\n\n  const year =\n    data.length > 0\n      ? getYear(parseISO(data[0].date))\n      : new Date().getFullYear();\n\n  const totalCount =\n    typeof totalCountProp === \"number\"\n      ? totalCountProp\n      : data.reduce((sum, activity) => sum + activity.count, 0);\n\n  const width = weeks.length * (blockSize + blockMargin) - blockMargin;\n  const height = labelHeight + (blockSize + blockMargin) * 7 - blockMargin;\n\n  if (data.length === 0) {\n    return null;\n  }\n\n  return (\n    <ContributionGraphContext.Provider\n      value={{\n        data,\n        weeks,\n        blockMargin,\n        blockRadius,\n        blockSize,\n        fontSize,\n        labels,\n        labelHeight,\n        maxLevel,\n        totalCount,\n        weekStart,\n        year,\n        width,\n        height,\n      }}\n    >\n      <div\n        className={cn(\"flex w-max max-w-full flex-col gap-2\", className)}\n        style={{ fontSize, ...style }}\n        {...props}\n      />\n    </ContributionGraphContext.Provider>\n  );\n};\n\nexport type ContributionGraphBlockProps = HTMLAttributes<SVGRectElement> & {\n  activity: Activity;\n  dayIndex: number;\n  weekIndex: number;\n};\n\nexport const ContributionGraphBlock = ({\n  activity,\n  dayIndex,\n  weekIndex,\n  className,\n  ...props\n}: ContributionGraphBlockProps) => {\n  const { blockSize, blockMargin, blockRadius, labelHeight, maxLevel } =\n    useContributionGraph();\n\n  if (activity.level < 0 || activity.level > maxLevel) {\n    throw new RangeError(\n      `Provided activity level ${activity.level} for ${activity.date} is out of range. It must be between 0 and ${maxLevel}.`\n    );\n  }\n\n  return (\n    <rect\n      className={cn(\n        'data-[level=\"0\"]:fill-muted',\n        'data-[level=\"1\"]:fill-muted-foreground/20',\n        'data-[level=\"2\"]:fill-muted-foreground/40',\n        'data-[level=\"3\"]:fill-muted-foreground/60',\n        'data-[level=\"4\"]:fill-muted-foreground/80',\n        className\n      )}\n      data-count={activity.count}\n      data-date={activity.date}\n      data-level={activity.level}\n      height={blockSize}\n      rx={blockRadius}\n      ry={blockRadius}\n      width={blockSize}\n      x={(blockSize + blockMargin) * weekIndex}\n      y={labelHeight + (blockSize + blockMargin) * dayIndex}\n      {...props}\n    />\n  );\n};\n\nexport type ContributionGraphCalendarProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  hideMonthLabels?: boolean;\n  className?: string;\n  children: (props: {\n    activity: Activity;\n    dayIndex: number;\n    weekIndex: number;\n  }) => ReactNode;\n};\n\nexport const ContributionGraphCalendar = ({\n  hideMonthLabels = false,\n  className,\n  children,\n  ...props\n}: ContributionGraphCalendarProps) => {\n  const { weeks, width, height, blockSize, blockMargin, labels } =\n    useContributionGraph();\n\n  const monthLabels = useMemo(\n    () => getMonthLabels(weeks, labels.months),\n    [weeks, labels.months]\n  );\n\n  return (\n    <div\n      className={cn(\"max-w-full overflow-x-auto overflow-y-hidden\", className)}\n      {...props}\n    >\n      <svg\n        className=\"block overflow-visible\"\n        height={height}\n        viewBox={`0 0 ${width} ${height}`}\n        width={width}\n      >\n        <title>Contribution Graph</title>\n        {!hideMonthLabels && (\n          <g className=\"fill-current\">\n            {monthLabels.map(({ label, weekIndex }) => (\n              <text\n                dominantBaseline=\"hanging\"\n                key={weekIndex}\n                x={(blockSize + blockMargin) * weekIndex}\n              >\n                {label}\n              </text>\n            ))}\n          </g>\n        )}\n        {weeks.map((week, weekIndex) =>\n          week.map((activity, dayIndex) => {\n            if (!activity) {\n              return null;\n            }\n\n            return (\n              <Fragment key={`${weekIndex}-${dayIndex}`}>\n                {children({ activity, dayIndex, weekIndex })}\n              </Fragment>\n            );\n          })\n        )}\n      </svg>\n    </div>\n  );\n};\n\nexport type ContributionGraphFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ContributionGraphFooter = ({\n  className,\n  ...props\n}: ContributionGraphFooterProps) => (\n  <div\n    className={cn(\n      \"flex flex-wrap gap-1 whitespace-nowrap sm:gap-x-4\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type ContributionGraphTotalCountProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children?: (props: { totalCount: number; year: number }) => ReactNode;\n};\n\nexport const ContributionGraphTotalCount = ({\n  className,\n  children,\n  ...props\n}: ContributionGraphTotalCountProps) => {\n  const { totalCount, year, labels } = useContributionGraph();\n\n  if (children) {\n    return <>{children({ totalCount, year })}</>;\n  }\n\n  return (\n    <div className={cn(\"text-muted-foreground\", className)} {...props}>\n      {labels.totalCount\n        ? labels.totalCount\n            .replace(\"{{count}}\", String(totalCount))\n            .replace(\"{{year}}\", String(year))\n        : `${totalCount} activities in ${year}`}\n    </div>\n  );\n};\n\nexport type ContributionGraphLegendProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children?: (props: { level: number }) => ReactNode;\n};\n\nexport const ContributionGraphLegend = ({\n  className,\n  children,\n  ...props\n}: ContributionGraphLegendProps) => {\n  const { labels, maxLevel, blockSize, blockRadius } = useContributionGraph();\n\n  return (\n    <div\n      className={cn(\"ml-auto flex items-center gap-[3px]\", className)}\n      {...props}\n    >\n      <span className=\"mr-1 text-muted-foreground\">\n        {labels.legend?.less || \"Less\"}\n      </span>\n      {new Array(maxLevel + 1).fill(undefined).map((_, level) =>\n        children ? (\n          <Fragment key={level}>{children({ level })}</Fragment>\n        ) : (\n          <svg height={blockSize} key={level} width={blockSize}>\n            <title>{`${level} contributions`}</title>\n            <rect\n              className={cn(\n                \"stroke-[1px] stroke-border\",\n                'data-[level=\"0\"]:fill-muted',\n                'data-[level=\"1\"]:fill-muted-foreground/20',\n                'data-[level=\"2\"]:fill-muted-foreground/40',\n                'data-[level=\"3\"]:fill-muted-foreground/60',\n                'data-[level=\"4\"]:fill-muted-foreground/80'\n              )}\n              data-level={level}\n              height={blockSize}\n              rx={blockRadius}\n              ry={blockRadius}\n              width={blockSize}\n            />\n          </svg>\n        )\n      )}\n      <span className=\"ml-1 text-muted-foreground\">\n        {labels.legend?.more || \"More\"}\n      </span>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/docs/src/components/link-tree.tsx",
    "content": "import { Button } from '@/src/components/ui/button'\nimport { cn } from '@/src/lib/utils'\nimport Link from 'next/link'\nimport { ReactNode } from 'react'\n\nexport type LinkTreeItemProps = {\n  href: string\n  icon: ReactNode\n  label: ReactNode\n  detail?: ReactNode\n}\n\nexport function LinkTreeItem({ href, icon, label, detail }: LinkTreeItemProps) {\n  const isLocalRoute = href.startsWith('/')\n  return (\n    <li>\n      <Button\n        asChild\n        variant=\"outline\"\n        className={cn(\n          'flex w-full items-center justify-start gap-3 py-6 text-base transition-all active:scale-[0.99]'\n        )}\n      >\n        <Link\n          href={href}\n          target={isLocalRoute ? undefined : '_blank'}\n          rel={isLocalRoute ? undefined : 'noopener noreferrer'}\n        >\n          {icon}\n          <span className=\"justify-self-center\">{label}</span>\n          {detail && (\n            <span className=\"text-muted-foreground ml-auto text-sm\">\n              {detail}\n            </span>\n          )}\n        </Link>\n      </Button>\n    </li>\n  )\n}\n\ntype LinkTreeProps = {\n  items: LinkTreeItemProps[]\n}\n\nexport function LinkTree({ items }: LinkTreeProps) {\n  return (\n    <ul className=\"space-y-4\">\n      {items.map((item, index) => (\n        <LinkTreeItem key={index} {...item} />\n      ))}\n    </ul>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/logo.tsx",
    "content": "import { cn } from '@/src/lib/utils'\n\nexport function NuqsWordmark({ className }: React.ComponentProps<'img'>) {\n  return (\n    <>\n      <svg\n        viewBox=\"0 0 1912 351\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        role=\"presentation\"\n        className={cn('h-[1em] w-auto', className)}\n      >\n        <path\n          className=\"fill-[#d4d4d8] dark:fill-[#3f3f46]\"\n          d=\"M76.2 206.6c0-16 2.667-29.6 8-40.8 5.6-11.467 14.933-22.533 28-33.2l8.8-7.2c8-6.133 14-11.067 18-14.8 4-4 7.2-8.533 9.6-13.6 2.667-5.067 4-11.067 4-18 .267-15.2-5.2-27.467-16.4-36.8-11.2-9.6-25.867-14.4-44-14.4-17.867 0-32.4 6-43.6 18C37.667 57.533 31.267 73.267 29.4 93L.2 91c1.333-17.6 6.133-33.2 14.4-46.8 8.267-13.6 18.933-24.267 32-32C59.933 4.467 74.733.6 91 .6c17.6 0 33.2 3.333 46.8 10 13.867 6.667 24.667 16 32.4 28 7.733 11.733 11.6 25.2 11.6 40.4 0 9.067-1.867 17.6-5.6 25.6-3.467 7.733-8.667 15.2-15.6 22.4-6.933 6.933-16 14.533-27.2 22.8l-.4.4c-10.4 7.733-17.867 16-22.4 24.8-4.533 8.533-6.8 19.067-6.8 31.6H76.2Zm31.6 46.4v38H71v-38h36.8Z\"\n        />\n        <path\n          className=\"fill-black dark:fill-white\"\n          d=\"m275.722 79 2.4 59.6-6.8-3.6c3.2-21.067 10.933-36.4 23.2-46 12.533-9.867 28-14.8 46.4-14.8 22.933 0 40.666 7.333 53.2 22 12.8 14.4 19.2 33.867 19.2 58.4V291h-51.6V171c0-12.533-1.2-22.667-3.6-30.4-2.4-8-6.267-14-11.6-18-5.334-4.267-12.4-6.4-21.2-6.4-14.133 0-25.067 4.667-32.8 14-7.734 9.333-11.6 22.933-11.6 40.8v120h-52V79h46.8Z\"\n        />\n        <path\n          className=\"fill-[#d4d4d8] dark:fill-[#3f3f46]\"\n          d=\"M669.147 119v25.2h-190.4V119h190.4Zm0 82.4v25.2h-190.4v-25.2h190.4Z\"\n        />\n        <path\n          className=\"fill-black dark:fill-white\"\n          d=\"m869.981 291-1.6-58.4 6.4 3.6c-3.2 20.533-10.933 35.6-23.2 45.2-12.266 9.6-27.333 14.4-45.2 14.4-22.666 0-40.266-7.333-52.8-22-12.533-14.667-18.8-34.133-18.8-58.4V79h52v119.6c0 12.8 1.067 23.2 3.2 31.2 2.4 7.733 6.134 13.733 11.2 18 5.334 4 12.4 6 21.2 6 13.6 0 24.134-4.667 31.6-14 7.734-9.333 11.6-23.067 11.6-41.2V79h52v212h-47.6Z\"\n        />\n        <path\n          className=\"fill-[#d4d4d8] dark:fill-[#3f3f46]\"\n          d=\"M1022.65 7c10.14-5.333 21.07-7.6 32.8-6.8 12.27.8 23.34 4.8 33.2 12 9.87 6.933 17.07 16 21.6 27.2 4.54 10.933 5.34 22.8 2.4 35.6-2.66 11.733-8.66 23.067-18 34-9.06 10.667-21.06 20.8-36 30.4l71.2 90.4c4.54-6.4 8-14.8 10.4-25.2 2.67-10.4 3.87-21.067 3.6-32l28.8 2.8c-.53 14.4-3.2 28.4-8 42-4.53 13.333-10.4 24.533-17.6 33.6l32.8 40h-33.2l-16.8-21.2c-8.53 8.267-19.06 14.667-31.6 19.2-12.26 4.533-25.6 6.8-40 6.8-18.66 0-35.2-3.067-49.6-9.2-14.397-6.4-25.73-15.467-33.997-27.2-8-11.733-12-25.867-12-42.4 0-16.267 4-30.4 12-42.4s20.933-24.267 38.797-36.8c2.4-1.6 4.67-3.067 6.8-4.4l-4-5.2c-9.06-12.267-15.86-23.467-20.397-33.6-4.267-10.4-6.4-20.8-6.4-31.2 0-13.067 2.933-24.4 8.8-34 6.137-9.867 14.267-17.333 24.397-22.4Zm66.4 258c9.34-3.733 16.8-8.933 22.4-15.6l-74.8-94.8c-14.66 9.6-26 19.333-34 29.2-7.73 9.6-11.597 20.667-11.597 33.2 0 10.933 2.8 20.4 8.4 28.4 5.867 8 13.867 14.133 23.997 18.4 10.14 4.267 21.74 6.4 34.8 6.4 11.2 0 21.47-1.733 30.8-5.2Zm-66-176.4c4.27 8.533 10.8 18.533 19.6 30 14.4-8 25.34-16.4 32.8-25.2 7.74-9.067 11.6-18.267 11.6-27.6 0-10.933-3.06-20-9.2-27.2-6.13-7.2-14.13-10.933-24-11.2-10.66-.267-19.46 3.067-26.4 10-6.93 6.933-10.4 15.867-10.4 26.8 0 7.467 2 15.6 6 24.4Z\"\n        />\n        <path\n          className=\"fill-black dark:fill-white\"\n          d=\"M1410.8 79v272h-52V246.6l5.2 4c-3.46 9.867-8.4 18.133-14.8 24.8-6.4 6.667-14 11.733-22.8 15.2-8.8 3.467-18.66 5.2-29.6 5.2-19.46 0-36.13-4.933-50-14.8-13.6-9.867-23.86-23.067-30.8-39.6-6.66-16.8-10-35.6-10-56.4 0-20.8 3.34-39.467 10-56 6.94-16.8 17.2-30.133 30.8-40 13.87-9.867 30.54-14.8 50-14.8 16.54 0 30.54 3.867 42 11.6 11.74 7.467 20.27 18.4 25.6 32.8l-4.4 4.8 1.6-44.4h49.2Zm-53.6 140.4c3.74-10.4 5.6-21.867 5.6-34.4 0-12.533-1.86-24-5.6-34.4-3.73-10.4-9.46-18.8-17.2-25.2-7.73-6.4-17.33-9.6-28.8-9.6-16.8 0-29.6 6.667-38.4 20-8.8 13.333-13.2 29.733-13.2 49.2 0 19.733 4.4 36.267 13.2 49.6 8.8 13.067 21.6 19.6 38.4 19.6 11.47 0 21.07-3.2 28.8-9.6 7.74-6.4 13.47-14.8 17.2-25.2Z\"\n        />\n        <path\n          className=\"fill-[#d4d4d8] dark:fill-[#3f3f46]\"\n          d=\"M1666.41 119v25.2h-190.4V119h190.4Zm0 82.4v25.2h-190.4v-25.2h190.4Z\"\n        />\n        <path\n          className=\"fill-black dark:fill-white\"\n          d=\"M1856.85 146.6c-1.87-10.133-6.94-18.267-15.2-24.4-8.27-6.133-17.47-9.2-27.6-9.2-10.14 0-18.54 2.533-25.2 7.6-6.67 4.8-9.87 11.333-9.6 19.6.26 8 4.26 14.133 12 18.4 7.73 4.267 17.86 7.6 30.4 10 20.8 3.467 37.6 7.733 50.4 12.8 12.8 4.8 22.53 11.467 29.2 20 6.93 8.533 10.4 19.467 10.4 32.8 0 13.6-4 25.067-12 34.4-8 9.067-18.8 15.867-32.4 20.4-13.6 4.533-28.94 6.8-46 6.8-19.2 0-36.27-2.8-51.2-8.4-14.67-5.867-26.27-14.133-34.8-24.8-8.54-10.933-13.34-23.867-14.4-38.8l52.4-2.8c1.33 7.467 4 13.867 8 19.2 4 5.333 9.33 9.467 16 12.4 6.66 2.667 14.4 4 23.2 4 10.13 0 18.93-1.867 26.4-5.6 7.73-4 11.6-10 11.6-18-.27-5.6-2-10.133-5.2-13.6-3.2-3.467-7.2-6-12-7.6-4.8-1.867-11.34-3.6-19.6-5.2-2.14-.267-4.94-.933-8.4-2-20-4-36.14-8.267-48.4-12.8-12-4.8-21.6-11.2-28.8-19.2-6.94-8.267-10.4-18.8-10.4-31.6 0-13.6 3.6-25.467 10.8-35.6 7.46-10.133 18-17.867 31.6-23.2 13.86-5.6 30.13-8.4 48.8-8.4 24.8 0 45.46 6.4 62 19.2 16.8 12.533 26.93 29.6 30.4 51.2l-52.4 2.4Z\"\n        />\n      </svg>\n      <span className=\"sr-only\">nuqs</span>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/og-image.tsx",
    "content": "import { ImageResponse } from 'next/og'\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { ComponentProps } from 'react'\n\n// Image metadata\nexport const size = {\n  width: 1200,\n  height: 675\n}\nexport const contentType = 'image/png'\n\ntype OpengraphImageProps = {\n  title: string\n  description?: string\n}\n\n// Image generation\nexport async function generateOpengraphImage({\n  title,\n  description\n}: OpengraphImageProps) {\n  const { fonts, images } = await loadResources()\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          background: 'linear-gradient(to bottom right, #000, #000, #35353A)',\n          width: '100%',\n          height: '100%',\n          display: 'flex',\n          flexDirection: 'column',\n          padding: '50px 0 0',\n          gap: '20px',\n          alignItems: 'center',\n          justifyContent: 'center',\n          fontFamily: 'Inter'\n        }}\n      >\n        <img\n          src={images.bg}\n          alt=\"Background\"\n          style={{ position: 'absolute', inset: 0 }}\n        />\n        {/* <div\n          // Center marker\n          style={{\n            width: '1px',\n            position: 'absolute',\n            top: 0,\n            left: '50%',\n            height: '100%',\n            background: 'red'\n          }}\n        /> */}\n        <header\n          style={{\n            position: 'absolute',\n            left: 0,\n            top: '32px',\n            display: 'flex',\n            alignItems: 'center',\n            gap: '20px',\n            margin: '0 0 40px',\n            width: '100%',\n            padding: '0 40px'\n          }}\n        >\n          <Logo style={{ width: '293px', height: '53px' }} />\n          <p\n            style={{\n              fontSize: '28px',\n              color: 'white',\n              marginLeft: 'auto',\n              fontWeight: 500\n            }}\n          >\n            Type-Safe Search Params State Manager for React\n          </p>\n        </header>\n        <h1\n          style={{\n            fontSize: getFontSize(title) + 'px',\n            color: 'white',\n            fontWeight: 700,\n            textAlign: 'center',\n            lineHeight: 1.1,\n            margin: '0 16px 20px 16px',\n            textWrap: title.length > 20 ? 'balance' : 'wrap'\n          }}\n        >\n          {title}\n        </h1>\n        {description && (\n          <p\n            style={{\n              fontWeight: 300,\n              fontSize: '48px',\n              color: '#CBCBD2',\n              textAlign: 'center',\n              padding: '0 10px',\n              textWrap: description.length > 50 ? 'balance' : 'wrap',\n              margin: 0\n            }}\n          >\n            {description}\n          </p>\n        )}\n      </div>\n    ),\n    {\n      ...size,\n      fonts: [\n        {\n          name: 'Inter',\n          data: fonts.inter.light,\n          style: 'normal',\n          weight: 300\n        },\n        {\n          name: 'Inter',\n          data: fonts.inter.regular,\n          style: 'normal',\n          weight: 400\n        },\n        {\n          name: 'Inter',\n          data: fonts.inter.medium,\n          style: 'normal',\n          weight: 500\n        },\n        {\n          name: 'Inter',\n          data: fonts.inter.semibold,\n          style: 'normal',\n          weight: 600\n        },\n        {\n          name: 'Inter',\n          data: fonts.inter.bold,\n          style: 'normal',\n          weight: 700\n        }\n      ]\n    }\n  )\n}\n\n// --\n\nfunction getFont(weight: string) {\n  return readFile(\n    join(process.cwd(), `src/assets/fonts/Inter_24pt-${weight}.ttf`)\n  )\n}\n\nasync function loadResources() {\n  const [light, regular, medium, semibold, bold] = await Promise.all([\n    getFont('Light'),\n    getFont('Regular'),\n    getFont('Medium'),\n    getFont('SemiBold'),\n    getFont('Bold')\n  ])\n  const bg =\n    'data:image/png;base64,' +\n    (\n      await readFile(join(process.cwd(), 'src/assets/images/og-bg.png'))\n    ).toString('base64')\n  return {\n    fonts: {\n      inter: {\n        light,\n        regular,\n        medium,\n        semibold,\n        bold\n      }\n    },\n    images: {\n      bg\n    }\n  }\n}\n\nfunction getFontSize(text: string) {\n  if (text.length < 30) return 128\n  if (text.length < 50) return 85\n  if (text.length < 70) return 64\n  return 48\n}\n\nfunction Logo(props: ComponentProps<'svg'>) {\n  return (\n    <svg\n      viewBox=\"0 0 1912 351\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M76.2 206.6c0-16 2.667-29.6 8-40.8 5.6-11.467 14.933-22.533 28-33.2l8.8-7.2c8-6.133 14-11.067 18-14.8 4-4 7.2-8.533 9.6-13.6 2.667-5.067 4-11.067 4-18 .267-15.2-5.2-27.467-16.4-36.8-11.2-9.6-25.867-14.4-44-14.4-17.867 0-32.4 6-43.6 18C37.667 57.533 31.267 73.267 29.4 93L.2 91c1.333-17.6 6.133-33.2 14.4-46.8 8.267-13.6 18.933-24.267 32-32C59.933 4.467 74.733.6 91 .6c17.6 0 33.2 3.333 46.8 10 13.867 6.667 24.667 16 32.4 28 7.733 11.733 11.6 25.2 11.6 40.4 0 9.067-1.867 17.6-5.6 25.6-3.467 7.733-8.667 15.2-15.6 22.4-6.933 6.933-16 14.533-27.2 22.8l-.4.4c-10.4 7.733-17.867 16-22.4 24.8-4.533 8.533-6.8 19.067-6.8 31.6H76.2Zm31.6 46.4v38H71v-38h36.8Z\"\n        fill=\"#3F3F46\"\n      />\n      <path\n        d=\"m275.722 79 2.4 59.6-6.8-3.6c3.2-21.067 10.933-36.4 23.2-46 12.533-9.867 28-14.8 46.4-14.8 22.933 0 40.666 7.333 53.2 22 12.8 14.4 19.2 33.867 19.2 58.4V291h-51.6V171c0-12.533-1.2-22.667-3.6-30.4-2.4-8-6.267-14-11.6-18-5.334-4.267-12.4-6.4-21.2-6.4-14.133 0-25.067 4.667-32.8 14-7.734 9.333-11.6 22.933-11.6 40.8v120h-52V79h46.8Z\"\n        fill=\"#fff\"\n      />\n      <path\n        d=\"M669.147 119v25.2h-190.4V119h190.4Zm0 82.4v25.2h-190.4v-25.2h190.4Z\"\n        fill=\"#3F3F46\"\n      />\n      <path\n        d=\"m869.981 291-1.6-58.4 6.4 3.6c-3.2 20.533-10.933 35.6-23.2 45.2-12.266 9.6-27.333 14.4-45.2 14.4-22.666 0-40.266-7.333-52.8-22-12.533-14.667-18.8-34.133-18.8-58.4V79h52v119.6c0 12.8 1.067 23.2 3.2 31.2 2.4 7.733 6.134 13.733 11.2 18 5.334 4 12.4 6 21.2 6 13.6 0 24.134-4.667 31.6-14 7.734-9.333 11.6-23.067 11.6-41.2V79h52v212h-47.6Z\"\n        fill=\"#fff\"\n      />\n      <path\n        d=\"M1022.65 7c10.14-5.333 21.07-7.6 32.8-6.8 12.27.8 23.34 4.8 33.2 12 9.87 6.933 17.07 16 21.6 27.2 4.54 10.933 5.34 22.8 2.4 35.6-2.66 11.733-8.66 23.067-18 34-9.06 10.667-21.06 20.8-36 30.4l71.2 90.4c4.54-6.4 8-14.8 10.4-25.2 2.67-10.4 3.87-21.067 3.6-32l28.8 2.8c-.53 14.4-3.2 28.4-8 42-4.53 13.333-10.4 24.533-17.6 33.6l32.8 40h-33.2l-16.8-21.2c-8.53 8.267-19.06 14.667-31.6 19.2-12.26 4.533-25.6 6.8-40 6.8-18.66 0-35.2-3.067-49.6-9.2-14.397-6.4-25.73-15.467-33.997-27.2-8-11.733-12-25.867-12-42.4 0-16.267 4-30.4 12-42.4s20.933-24.267 38.797-36.8c2.4-1.6 4.67-3.067 6.8-4.4l-4-5.2c-9.06-12.267-15.86-23.467-20.397-33.6-4.267-10.4-6.4-20.8-6.4-31.2 0-13.067 2.933-24.4 8.8-34 6.137-9.867 14.267-17.333 24.397-22.4Zm66.4 258c9.34-3.733 16.8-8.933 22.4-15.6l-74.8-94.8c-14.66 9.6-26 19.333-34 29.2-7.73 9.6-11.597 20.667-11.597 33.2 0 10.933 2.8 20.4 8.4 28.4 5.867 8 13.867 14.133 23.997 18.4 10.14 4.267 21.74 6.4 34.8 6.4 11.2 0 21.47-1.733 30.8-5.2Zm-66-176.4c4.27 8.533 10.8 18.533 19.6 30 14.4-8 25.34-16.4 32.8-25.2 7.74-9.067 11.6-18.267 11.6-27.6 0-10.933-3.06-20-9.2-27.2-6.13-7.2-14.13-10.933-24-11.2-10.66-.267-19.46 3.067-26.4 10-6.93 6.933-10.4 15.867-10.4 26.8 0 7.467 2 15.6 6 24.4Z\"\n        fill=\"#3F3F46\"\n      />\n      <path\n        d=\"M1410.8 79v272h-52V246.6l5.2 4c-3.46 9.867-8.4 18.133-14.8 24.8-6.4 6.667-14 11.733-22.8 15.2-8.8 3.467-18.66 5.2-29.6 5.2-19.46 0-36.13-4.933-50-14.8-13.6-9.867-23.86-23.067-30.8-39.6-6.66-16.8-10-35.6-10-56.4 0-20.8 3.34-39.467 10-56 6.94-16.8 17.2-30.133 30.8-40 13.87-9.867 30.54-14.8 50-14.8 16.54 0 30.54 3.867 42 11.6 11.74 7.467 20.27 18.4 25.6 32.8l-4.4 4.8 1.6-44.4h49.2Zm-53.6 140.4c3.74-10.4 5.6-21.867 5.6-34.4 0-12.533-1.86-24-5.6-34.4-3.73-10.4-9.46-18.8-17.2-25.2-7.73-6.4-17.33-9.6-28.8-9.6-16.8 0-29.6 6.667-38.4 20-8.8 13.333-13.2 29.733-13.2 49.2 0 19.733 4.4 36.267 13.2 49.6 8.8 13.067 21.6 19.6 38.4 19.6 11.47 0 21.07-3.2 28.8-9.6 7.74-6.4 13.47-14.8 17.2-25.2Z\"\n        fill=\"#fff\"\n      />\n      <path\n        d=\"M1666.41 119v25.2h-190.4V119h190.4Zm0 82.4v25.2h-190.4v-25.2h190.4Z\"\n        fill=\"#3F3F46\"\n      />\n      <path\n        d=\"M1856.85 146.6c-1.87-10.133-6.94-18.267-15.2-24.4-8.27-6.133-17.47-9.2-27.6-9.2-10.14 0-18.54 2.533-25.2 7.6-6.67 4.8-9.87 11.333-9.6 19.6.26 8 4.26 14.133 12 18.4 7.73 4.267 17.86 7.6 30.4 10 20.8 3.467 37.6 7.733 50.4 12.8 12.8 4.8 22.53 11.467 29.2 20 6.93 8.533 10.4 19.467 10.4 32.8 0 13.6-4 25.067-12 34.4-8 9.067-18.8 15.867-32.4 20.4-13.6 4.533-28.94 6.8-46 6.8-19.2 0-36.27-2.8-51.2-8.4-14.67-5.867-26.27-14.133-34.8-24.8-8.54-10.933-13.34-23.867-14.4-38.8l52.4-2.8c1.33 7.467 4 13.867 8 19.2 4 5.333 9.33 9.467 16 12.4 6.66 2.667 14.4 4 23.2 4 10.13 0 18.93-1.867 26.4-5.6 7.73-4 11.6-10 11.6-18-.27-5.6-2-10.133-5.2-13.6-3.2-3.467-7.2-6-12-7.6-4.8-1.867-11.34-3.6-19.6-5.2-2.14-.267-4.94-.933-8.4-2-20-4-36.14-8.267-48.4-12.8-12-4.8-21.6-11.2-28.8-19.2-6.94-8.267-10.4-18.8-10.4-31.6 0-13.6 3.6-25.467 10.8-35.6 7.46-10.133 18-17.867 31.6-23.2 13.86-5.6 30.13-8.4 48.8-8.4 24.8 0 45.46 6.4 62 19.2 16.8 12.533 26.93 29.6 30.4 51.2l-52.4 2.4Z\"\n        fill=\"#fff\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/query-spy.tsx",
    "content": "'use client'\n\nimport { useSearchParams } from 'next/navigation'\nimport { Querystring, QuerystringProps } from './querystring'\n\nexport function QuerySpy(props: Omit<QuerystringProps, 'value'>) {\n  const searchParams = useSearchParams()\n  return <Querystring value={searchParams} {...props} />\n}\n"
  },
  {
    "path": "packages/docs/src/components/querystring.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { Fragment, useMemo } from 'react'\n\nexport type QuerystringProps = React.ComponentProps<'pre'> & {\n  path?: string\n  value: string | URLSearchParams\n  keepKeys?: string[]\n}\n\nexport function Querystring({\n  path,\n  value,\n  keepKeys,\n  ...props\n}: QuerystringProps) {\n  const search = useMemo(\n    () => filterQueryKeys(value, keepKeys),\n    [value, keepKeys]\n  )\n  return (\n    <QuerystringSkeleton {...props}>\n      {path && <span className=\"text-zinc-500\">{path}</span>}\n      {Array.from(search.entries()).map(([key, value], i) => (\n        <Fragment key={key + i}>\n          <span className=\"text-zinc-500\">\n            {i === 0 ? (\n              '?'\n            ) : (\n              <>\n                <wbr />&\n              </>\n            )}\n          </span>\n          <span className=\"text-[#005CC5] dark:text-[#79B8FF]\">{key}</span>=\n          <span className=\"text-[#D73A49] dark:text-[#F97583]\">{value}</span>\n        </Fragment>\n      ))}\n      {search.size === 0 && (\n        <span className=\"text-zinc-500 italic\">{'<empty query>'}</span>\n      )}\n    </QuerystringSkeleton>\n  )\n}\n\nexport function QuerystringSkeleton({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'pre'>) {\n  return (\n    <pre\n      aria-label=\"Querystring spy\"\n      aria-description=\"For browsers where the query is hard to see (eg: on mobile)\"\n      className={cn(\n        'bg-background block w-full overflow-x-auto rounded-lg border px-3 py-2 text-xs text-wrap sm:text-sm dark:bg-zinc-900/50 dark:shadow-inner',\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </pre>\n  )\n}\n\nfunction filterQueryKeys(query: string | URLSearchParams, keys?: string[]) {\n  const source = new URLSearchParams(query)\n  if (!keys || keys.length === 0) {\n    return source\n  }\n  const destination = new URLSearchParams()\n  for (const [key, value] of source.entries()) {\n    if (keys.includes(key)) {\n      destination.append(key, value)\n    }\n  }\n  return destination\n}\n"
  },
  {
    "path": "packages/docs/src/components/quote.tsx",
    "content": "/**\n * This code was generated by v0 by Vercel.\n * @see https://v0.dev/t/3bPRztjwG1M\n */\n\ntype AvatarX = {\n  service: 'x'\n  handle: string\n}\ntype AvatarGitHub = {\n  service: 'github'\n  handle: string\n}\ntype AvatarOther = {\n  service: 'http'\n  href: string\n}\ntype Avatar = AvatarX | AvatarGitHub | AvatarOther\n\nfunction getAvatarUrl(avatar: Avatar) {\n  switch (avatar.service) {\n    case 'x':\n      return `https://unavatar.io/x/${avatar.handle}`\n    case 'github':\n      return `https://unavatar.io/github/${avatar.handle}`\n    case 'http':\n      return avatar.href\n  }\n}\n\ntype QuoteProps = {\n  author: {\n    name: string\n    avatar: Avatar\n  }\n  text: React.ReactNode\n  url?: string\n}\n\nexport function Quote({ text, author, url }: QuoteProps) {\n  return (\n    <div className=\"mx-auto flex w-full max-w-md flex-col gap-4 rounded-lg bg-white p-6 shadow-md dark:bg-zinc-900\">\n      <div className=\"flex items-center gap-3\">\n        <img\n          src={getAvatarUrl(author.avatar)}\n          alt={author.name}\n          width={36}\n          height={36}\n          className=\"h-9 w-9 rounded-full\"\n          crossOrigin=\"anonymous\"\n        />\n        <div className=\"grid gap-0.5 text-sm\">\n          {url ? (\n            <a href={url} className=\"font-semibold hover:underline\">\n              {author.name}\n            </a>\n          ) : (\n            <div className=\"font-semibold\">{author.name}</div>\n          )}\n          {author.avatar.service === 'x' ||\n            (author.avatar.service === 'github' && (\n              <div className=\"text-zinc-500 dark:text-zinc-400\">\n                {author.avatar.handle}\n              </div>\n            ))}\n        </div>\n      </div>\n      <blockquote className=\"text-md leading-snug font-medium text-zinc-800 dark:text-zinc-200\">\n        {text}\n      </blockquote>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/react-paris.tsx",
    "content": "import { ComponentProps } from 'react'\n\nexport function ReactParisLogo(props: ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 500 500\"\n      aria-label=\"React Paris\"\n      {...props}\n    >\n      <path\n        d=\"M313.93 365.28c-1.96-4.25-3.9-8.4-5.81-12.53-2.84.36-5.72.68-8.61.99-1.05.11-2.1.22-3.17.32l-1.98 2.55c-2.29 2.93-4.59 5.82-6.92 8.65l-.03.03a377.96 377.96 0 0 1-20.06 22.64c1.14 1.01 2.29 2.01 3.43 2.98 16.28 4.75 30.55 15.11 38.82 25.62 7.96 3.47 15.18 5.3 21.29 5.3 4.53 0 8.41-.99 11.53-2.94.22-.14.45-.29.66-.44a474.043 474.043 0 0 1-23.26-41.02h.36l-6.25-12.15zM237.72 85.05c.73 1.34 1.85 2.51 2.95 3.51-.12 2.68-.15 8.74-.42 17.43l-.17 3.78c-.19 5.58-.46 11.33-.82 17.51-.08 1.39-.16 2.82-.25 4.27v.01c-.32 5.1-.69 10.5-1.15 16.38v.04c3.19-3.18 6.38-6.25 9.59-9.19-.01-.01-.01-.01-.02-.01.08-.08.16-.14.24-.22.77-.71 1.56-1.42 2.34-2.12l.26-.23c5.09-4.56 10.24-8.82 15.32-12.72-.26-4.77-.46-9.29-.62-13.71l-.15-3.78c-.17-8.7-.31-14.75-.36-17.43 1.13-.97 2.22-2.17 2.93-3.51 1.02-1.84 4.9-4.06 4.9-4.06v-6.41h-8.99V62.87c0-4.1-3.88-7.5-8.92-8.18v-22.9h-3.63v22.89c-5.08.66-8.94 4.08-8.94 8.18v11.72h-9.03v6.41c-.01 0 3.96 2.22 4.94 4.06z\"\n        className=\"fill-[#cd1126] dark:fill-[#fe6497]\"\n      />\n      <path\n        d=\"M356.59 438.44c-.46.31-.92.62-1.39.9-6.99 4.35-15.1 6.55-24.1 6.55-1.59 0-3.19-.07-4.82-.21 1 1.54 1.89 2.87 2.67 4H176.16c.88-1.3 1.94-2.86 3.12-4.71.79-1.21 1.63-2.55 2.53-4 3.55-5.74 7.92-13.27 12.69-22.49v-.01c.34-.64.67-1.29 1.01-1.95 1.03-1.41 2.12-2.8 3.28-4.12 7.31-8.49 16.98-15.26 28-19.44 1.96-1.62 3.91-3.31 5.86-5.04.05-.04.09-.08.13-.12.82-.73 1.63-1.45 2.44-2.19l.02.02c4.13-3.75 8.27-7.74 12.37-11.92l-.02-.02c.77-.79 1.54-1.58 2.31-2.38.04-.04.08-.08.11-.12 1.86-1.93 3.72-3.91 5.56-5.92 1.05-1.14 2.1-2.3 3.12-3.46h-21.98c-1.7-1.93-3.38-3.89-5.07-5.89l-.01-.01c-2.28-.09-4.56-.19-6.81-.31-1.21-.06-2.41-.12-3.61-.21-4.32-.24-8.6-.56-12.84-.92l.01.01c-1.07-.09-2.13-.18-3.19-.28l-1.53-.15c-1.06-.1-2.11-.21-3.16-.32-1.28-.13-2.56-.28-3.83-.42-1.81 3.94-3.66 7.9-5.54 11.95l-6.17 12.14h.29c-8.38 16.74-16.97 31.26-24.53 43.02-.64 1.01-1.29 2-1.92 2.97-3.58 5.48-6.9 10.28-9.81 14.36-.7.99-1.38 1.93-2.04 2.83-2.81 3.85-5.14 6.91-6.86 9.09h-19.81v18.54h264.59v-18.54l-19.88.02c-2.03-2.57-4.91-6.37-8.4-11.25zM216.48 330.96c.97.08 1.92.14 2.89.2 3.26.22 6.54.4 9.85.55 1.18.06 2.37.11 3.57.14 1.92-6.48 3.73-13.42 5.36-21.2h28.8c1.62 7.7 3.41 14.57 5.31 20.99l3.57-.18c1.6-.1 3.2-.19 4.8-.3.97-.06 1.94-.12 2.9-.2l1.69-2.35c2.8-3.88 5.56-7.87 8.28-11.92-2.35-7.05-4.47-14.52-6.31-22.66l-1.95-9.16c-.47-2.44-.98-4.83-1.34-7.28-4.65-26.37-8.19-45.9-10.86-68.02-.13-1.15-.27-2.32-.4-3.49-.44-3.77-.85-7.62-1.24-11.61l-.01-.12-.01-.04-.43-3.58c-.09-.91-.17-1.82-.25-2.71-.01-.02-.01-.04-.01-.06-1.01-10.8-1.86-20.34-2.59-28.92l-.45-5.5v.01c-.02-.18-.03-.37-.05-.54-.09.07-.16.14-.25.22l-2.6 2.3c-4.13 3.75-8.27 7.73-12.37 11.92l.02.02c-.79.8-1.55 1.58-2.31 2.37-.03.04-.07.07-.11.11-4.57 4.76-9.17 9.83-13.7 15.13-.63.74-1.26 1.48-1.89 2.23l-.12 1.3c-.08.7-.14 1.41-.21 2.13l-.36 3.73c-.38 3.91-.79 7.7-1.22 11.4-.13 1.17-.27 2.33-.4 3.48-2.69 22.2-6.22 41.73-10.87 68.22-.39 2.41-.9 4.83-1.36 7.28l-1.99 9.13c-2.3 10.08-5.02 19.13-8.03 27.61-.47 1.34-.96 2.66-1.45 3.98-.57 1.54-1.15 3.06-1.75 4.56 0 .01 0 .02-.01.03 1.77.17 3.55.33 5.33.48-.01-.01-.02-.03-.03-.04.99.09 1.97.16 2.96.24.55.04 1.07.08 1.6.12zm23.46-51.55c3.6-27.2 6.19-47.42 7.93-70.39.09-1.15.17-2.31.26-3.47.25-3.6.49-7.28.7-11.07h7.39c.23 3.81.47 7.51.74 11.12.08 1.16.16 2.32.26 3.47 1.77 22.95 4.28 43.16 7.94 70.34.22 1.8.51 3.61.8 5.44h-26.84c.28-1.82.55-3.64.82-5.44z\"\n        className=\"fill-[#cd1126] dark:fill-[#fe6497]\"\n      />\n      <path\n        d=\"m216.51 210.21 2.88-.19c3.04-.2 6.1-.37 9.2-.51.14-1.15.29-2.32.42-3.49.44-3.85.87-7.79 1.27-11.87l.2-2.08v-.04c-3.83 4.69-7.61 9.52-11.32 14.53h.02c-.59.79-1.17 1.58-1.75 2.36-.31.43-.63.86-.92 1.29zM252.1 197.94c-.15 2.59-.33 5.11-.5 7.61.62-.01 1.25 0 1.87.01-.17-2.5-.35-5.04-.51-7.62h-.86zM385.24 210.53a232.26 232.26 0 0 0-13.23-4.77c-.01-.01-.01-.01-.02-.01-1.4 5.41-3.02 10.92-4.82 16.51.01 0 .02.01.03.01 4.05 1.34 7.96 2.75 11.74 4.24 31.31 12.35 49.28 28.41 49.28 44.08 0 15.67-17.96 31.73-49.28 44.07-3.78 1.49-7.69 2.9-11.74 4.25-.01 0-.02.01-.03.01 1.81 5.58 3.41 11.1 4.82 16.5.01 0 .01 0 .02-.01 4.57-1.49 8.99-3.08 13.23-4.76 38.8-15.29 60.16-36.62 60.16-60.06-.01-23.44-21.36-44.78-60.16-60.06z\"\n        className=\"fill-[#002654] dark:fill-[#00acff]\"\n      />\n      <path\n        d=\"M128.41 335.54c.95.32 1.91.62 2.88.93a289.1 289.1 0 0 0 16.55 4.65c1.11.28 2.22.55 3.34.82h.01c13.63 3.27 28.18 5.83 43.37 7.65 1.19.14 2.4.28 3.6.41.89.1 1.79.19 2.68.29 1.35.14 2.69.28 4.05.4l.62.06c5.56.53 11.21.95 16.91 1.28.01-.01.01-.02.01-.03 1.2.08 2.41.13 3.61.19 0 0 0 .01-.01.02.87.06 1.76.1 2.64.13 1.53.07 3.07.12 4.6.17h.03c5.53.19 11.1.29 16.7.29 5.61 0 11.18-.1 16.7-.29h.03c1.54-.05 3.08-.11 4.6-.17 2.56-.12 5.1-.25 7.64-.4 1.2-.07 2.4-.14 3.6-.22 1.04-.08 2.09-.14 3.11-.23 1.46-.11 2.91-.22 4.36-.35 1.49-.12 2.97-.25 4.44-.39.22-.02.43-.04.65-.07l.21-.02.08-.01.22-.02h.02c1.16-.11 2.33-.22 3.48-.35 2.51-.26 5-.55 7.47-.85 1.2-.14 2.4-.3 3.59-.45 13.46-1.77 26.38-4.12 38.57-7.04.01-.01.02-.01.03-.01-1.39-5.4-3-10.91-4.82-16.51-.01 0-.02 0-.03.01-9.92 2.35-20.39 4.32-31.24 5.86-.01.01-.03.01-.04.01-1.52.22-3.06.43-4.59.63-1.66.22-3.33.42-5.01.62-1.19.15-2.38.29-3.58.4-.09.02-.17.03-.26.03-1.08.12-2.16.24-3.25.35-.19.03-.38.05-.59.06-1.31.14-2.62.27-3.95.38-.63.06-1.28.12-1.92.16-1.38.12-2.79.24-4.19.35-1.49.12-2.97.23-4.47.33h-.04c-1.33.09-2.65.17-3.99.25-1.18.07-2.37.12-3.57.19-7.66.37-15.45.57-23.3.57-6.14 0-12.23-.12-18.26-.36-1.2-.04-2.39-.09-3.58-.14-3.02-.14-6.03-.31-9.01-.51h-.05c-1.49-.1-2.99-.21-4.47-.33-3.13-.24-6.24-.51-9.32-.82-1.2-.12-2.4-.23-3.59-.37 0 .01-.01.02-.01.03-4.37-.47-8.69-1-12.89-1.59l-1.53-.21-.02-.04c-10.88-1.55-21.34-3.51-31.26-5.87h-.02c-1.12-.27-2.24-.53-3.34-.82a274.84 274.84 0 0 1-16.55-4.64c-1.1-.35-2.18-.7-3.27-1.06-.01 0-.03-.01-.04-.01v-.01c-4.05-1.34-7.95-2.75-11.73-4.24-31.31-12.34-49.28-28.4-49.28-44.07 0-15.67 17.96-31.73 49.28-44.08 3.78-1.49 7.68-2.9 11.73-4.24.01 0 .03-.01.04-.01 1.09-.37 2.17-.71 3.27-1.06a274.79 274.79 0 0 1 16.55-4.65c1.11-.28 2.22-.55 3.34-.82h.05c9.93-2.35 20.39-4.32 31.27-5.87.01-.01.03-.01.04-.01 4.4-6.52 8.92-12.85 13.52-18.98h-.03c-17.45 1.84-34.13 4.64-49.62 8.35h-.01c-1.11.27-2.23.54-3.34.83-5.69 1.42-11.22 2.98-16.54 4.65-.97.3-1.94.61-2.89.92l-.38.12c-4.57 1.5-9 3.09-13.26 4.78-38.79 15.28-60.16 36.62-60.16 60.06 0 23.44 21.37 44.77 60.16 60.06 4.27 1.68 8.69 3.27 13.26 4.77l.39.16z\"\n        className=\"fill-[#002654] dark:fill-[#00acff]\"\n      />\n      <path\n        d=\"M154.23 266.6c.01.01.01.03.02.04 3.17-6.43 6.54-12.88 10.1-19.32-.01-.01-.01-.03-.02-.04-4.03-9.29-7.57-18.47-10.58-27.44-.01-.01-.01-.01-.01-.02-5.72 1.41-11.24 2.97-16.56 4.64 0 .01.01.02.01.03 4.56 13.7 10.28 27.83 17.04 42.11zM177.29 274.38c-.01-.01-.01-.02-.02-.03-3.26 6.48-6.3 12.93-9.1 19.33.01.01.01.02.02.03 3.8 6.81 7.81 13.61 12.02 20.38 2.97 4.76 6 9.44 9.07 14.02h.01c0 .01.01.01.01.02 4.52.63 9.1 1.19 13.75 1.67.01-.01.01-.01 0-.02 1.01-2.52 1.98-5.07 2.93-7.67-3.82-5.56-7.54-11.27-11.19-17.12a468.765 468.765 0 0 1-17.5-30.61zM130.46 201.35c5.34-1.66 10.87-3.21 16.56-4.64v-.01c-1.01-4.14-1.88-8.22-2.62-12.22-6.08-33.11-1.95-56.85 11.34-65.12 3.73-2.33 8.22-3.47 13.36-3.47 13.21 0 30.64 7.54 50.1 21.97 5 3.7 10.01 7.78 15.03 12.19.61-7.68 1.09-14.64 1.47-21.17v-.01c-2.1-1.66-4.18-3.27-6.27-4.82-33.49-24.84-62.9-31.68-82.79-19.28s-26.7 41.81-19.15 82.82c.83 4.51 1.82 9.09 2.97 13.76zM275.36 199.36v.03c.25 2.34.5 4.62.76 6.88.14 1.17.28 2.34.42 3.49 1.36.08 2.72.16 4.07.25l2.89.19c-.31-.42-.62-.85-.93-1.27-.59-.8-1.16-1.59-1.76-2.37h.04c-1.81-2.44-3.64-4.83-5.49-7.2z\"\n        className=\"fill-[#002654] dark:fill-[#00acff]\"\n      />\n      <path\n        d=\"M270.57 417.13c17.67 13.09 34.2 21.18 48.79 24.09 1.54.31 3.04.56 4.53.74 2.47.33 4.87.48 7.21.48 8.3 0 15.79-1.99 22.27-6.03.41-.26.82-.52 1.21-.8.96-.63 1.87-1.33 2.77-2.05 16.8-13.72 22.19-41.84 15.18-79.98-.83-4.51-1.83-9.1-2.97-13.77-.05-.21-.11-.41-.15-.62-.22-.89-.45-1.81-.68-2.71a292.68 292.68 0 0 0-4.81-16.5c-.35-1.09-.7-2.16-1.08-3.26v-.01c-4.57-13.72-10.3-27.86-17.07-42.16-.31-.66-.61-1.32-.93-1.97l-.14-.29c-.27-.57-.54-1.12-.82-1.69a448.185 448.185 0 0 0-12.04-23.09c0-.01-.01-.01-.01-.02-3.81-6.81-7.81-13.63-12.03-20.39-2.97-4.76-6-9.44-9.07-14.02-.01-.01-.02-.01-.02-.02-.86-1.28-1.72-2.56-2.59-3.82-4.43-6.48-8.98-12.75-13.6-18.82-.34-.45-.68-.89-1.03-1.34-.61-.8-1.22-1.59-1.84-2.37-6.44-8.27-13.03-16.12-19.71-23.48.7 8.17 1.52 17.16 2.47 27.2l.3 2.49c3.02 3.78 6.01 7.65 8.93 11.58l4.49 6.02-.12-.01c.01.01.02.03.03.04 5.9 8.21 11.65 16.77 17.17 25.64 6.34 10.17 12.2 20.42 17.53 30.64.01.01.01.03.02.04.65 1.25 1.3 2.49 1.93 3.73 3.3 6.47 6.38 12.91 9.24 19.3a1224 1224 0 0 1 1.75 3.96c.01.01.01.03.02.04 4.02 9.3 7.56 18.49 10.58 27.45v.01c.37 1.1.73 2.18 1.09 3.28 1.81 5.59 3.41 11.1 4.81 16.5.29 1.11.57 2.23.83 3.34 0 .01.01.03.01.04 1.01 4.14 1.87 8.21 2.61 12.2 5.54 30.09 2.61 52.45-7.95 62.48-.85.82-1.74 1.54-2.69 2.18-.23.16-.46.32-.7.46-7.77 4.84-18.9 4.56-32.21-.54-1.62-.62-3.28-1.31-4.97-2.08-8.14-3.66-16.98-8.99-26.29-15.89-3.88-2.88-7.79-5.98-11.69-9.29-1.39-1.18-2.78-2.38-4.17-3.61l-.02-.02c-.72.73-1.43 1.46-2.16 2.18-3.38 3.41-6.79 6.68-10.21 9.81.01.01.02.02.03.02 5.98 5.36 11.99 10.28 17.97 14.71z\"\n        className=\"fill-[#002654] dark:fill-[#00acff]\"\n      />\n      <path\n        d=\"M301.77 329.26c3.01-.34 5.98-.72 8.93-1.13 0-.01.01-.01.01-.02h.01c3.08-4.58 6.1-9.27 9.07-14.02 4.21-6.77 8.23-13.56 12.02-20.38.01-.01.01-.02.02-.03-2.8-6.4-5.84-12.85-9.09-19.32-.01.01-.01.02-.02.03-5.32 10.21-11.19 20.44-17.52 30.6-2.58 4.13-5.2 8.2-7.87 12.2v.01c-.8 1.21-1.61 2.41-2.42 3.61-2.26 3.32-4.55 6.58-6.85 9.79-.01.01-.02.03-.03.04 3.42-.29 6.81-.61 10.18-.98 1.18-.11 2.37-.25 3.56-.4zM147.02 344.48v-.02c-5.69-1.43-11.22-2.98-16.56-4.65v.02c-1.14 4.66-2.13 9.26-2.97 13.75-7.46 40.56-.88 69.78 18.5 82.4 2.87-4.01 6.16-8.76 9.71-14.18-13.26-8.31-17.38-32.03-11.3-65.11.73-3.98 1.61-8.07 2.62-12.21zM228.4 396.09a213.49 213.49 0 0 1-9.21 7.23c-6.31 4.68-12.41 8.64-18.22 11.84a62.7 62.7 0 0 0-2.52 3.18c-4.46 8.71-8.53 15.9-11.86 21.43 13.08-3.71 27.54-11.3 42.84-22.64 5.98-4.43 11.98-9.35 17.97-14.7.01-.02.03-.03.04-.04.07-.06.13-.12.2-.18.78-.7 1.57-1.41 2.35-2.13 2.84-2.6 5.67-5.3 8.5-8.09.52-.5 1.03-1.02 1.55-1.54.52-.51 1.04-1.04 1.55-1.56.26-.26.52-.53.78-.79.8-.82 1.6-1.63 2.39-2.46l.01-.01c.77-.8 1.54-1.61 2.3-2.41.76-.81 1.53-1.62 2.29-2.45.35-.37.69-.74 1.03-1.11.83-.9 1.65-1.81 2.48-2.72 3.38-3.76 6.75-7.64 10.07-11.65 1.06-1.26 2.1-2.54 3.14-3.83 1.86-2.29 3.71-4.61 5.55-6.97l.01-.01c-2.6.23-5.22.43-7.85.61-1.19.1-2.39.17-3.59.24-3.91.25-7.85.44-11.81.6l-.01.01c-1.68 2-3.36 3.96-5.07 5.89-.95 1.1-1.91 2.17-2.87 3.25-.07.07-.13.13-.19.21-2.59 2.88-5.21 5.7-7.83 8.43l-.01.01c-.8.83-1.6 1.65-2.39 2.46-4.1 4.19-8.24 8.17-12.37 11.92-.82.76-1.65 1.5-2.48 2.23h-.01a.66.66 0 0 0-.15.05l.05.05c-2.24 1.94-4.44 3.84-6.66 5.65zM344 215.74c1.83-5.6 3.43-11.11 4.82-16.51-.01 0-.02 0-.03-.01-15.48-3.7-32.16-6.51-49.6-8.34h-.03c4.6 6.13 9.12 12.47 13.53 18.98h.03c10.86 1.56 21.34 3.53 31.26 5.87v.01h.02z\"\n        className=\"fill-[#002654] dark:fill-[#00acff]\"\n      />\n      <path\n        d=\"M269.14 125.13c-1.11.83-2.21 1.67-3.32 2.54v.01c-4.4 3.44-8.82 7.15-13.22 11.08l-.03.03c-.04.03-.07.06-.11.1-.82.73-1.64 1.47-2.46 2.22a320.73 320.73 0 0 0-12.37 11.98l-.2.2c-.73.75-1.46 1.51-2.19 2.27-.53.55-1.06 1.11-1.59 1.65v.01c-8.59 9.06-17.07 18.94-25.3 29.5-.51.65-1.02 1.32-1.53 1.98-.13.16-.25.33-.37.5-.32.4-.63.82-.95 1.23-4.62 6.06-9.17 12.35-13.6 18.82-.87 1.26-1.74 2.54-2.59 3.82 0 .01-.01.01-.01.02h-.01c-3.08 4.58-6.1 9.27-9.07 14.02-4.22 6.77-8.23 13.58-12.03 20.39 0 .01-.01.01-.01.02-.7 1.26-1.4 2.52-2.09 3.78-3.52 6.44-6.83 12.88-9.95 19.31-.38.79-.77 1.58-1.14 2.37-.09.16-.16.33-.24.5-.17.36-.35.72-.51 1.08-6.77 14.28-12.49 28.44-17.06 42.14-.01.01-.01.01-.01.02 5.31 1.67 10.84 3.23 16.56 4.64 0-.01 0-.01.01-.02 3.01-8.96 6.55-18.13 10.58-27.43.01-.02.01-.03.02-.05.57-1.32 1.15-2.63 1.74-3.96a401.58 401.58 0 0 1 9.25-19.3c.62-1.24 1.27-2.49 1.93-3.74v-.01c0-.01.01-.01.01-.02 5.33-10.21 11.2-20.47 17.53-30.64 5.53-8.87 11.27-17.43 17.17-25.64.01-.01.02-.03.03-.04.87-1.22 1.75-2.42 2.63-3.62a420.31 420.31 0 0 1 14.02-18.06c.8-.96 1.6-1.93 2.4-2.88v-.01c.18-.22.37-.44.56-.66l.02-.02c1.11-1.32 2.21-2.61 3.34-3.9v-.01c4.17-4.82 8.38-9.46 12.62-13.86l.01-.01c.8-.84 1.6-1.65 2.39-2.47 4.1-4.18 8.24-8.16 12.37-11.92.86-.78 1.71-1.55 2.57-2.3.01-.01.02-.02.03-.02.77-.68 1.54-1.36 2.31-2.02 1.09-.94 2.17-1.87 3.26-2.78 3.43-2.86 6.86-5.57 10.27-8.1 19.46-14.44 36.9-21.97 50.1-21.97 5.13 0 9.63 1.14 13.36 3.47 13.29 8.28 17.42 32.01 11.34 65.12-.74 3.99-1.6 8.05-2.61 12.19 0 .01-.01.03-.01.04-.26 1.11-.54 2.22-.83 3.34-1.39 5.4-3 10.9-4.81 16.5-.36 1.08-.71 2.17-1.09 3.27v.01c-3.02 8.98-6.57 18.16-10.59 27.47 0 .01-.01.01-.01.02 3.56 6.44 6.92 12.89 10.09 19.31 0-.01.01-.02.01-.03 6.77-14.29 12.48-28.43 17.06-42.13.37-1.1.73-2.17 1.08-3.26.67-2.09 1.32-4.16 1.91-6.22 1.06-3.47 2.01-6.9 2.89-10.28.2-.8.4-1.59.6-2.37l.09-.33c.05-.21.11-.43.15-.64 1.14-4.67 2.14-9.27 2.97-13.77 7.54-41.01.74-70.42-19.15-82.82-19.9-12.4-49.3-5.56-82.79 19.28-.48.34-.95.69-1.43 1.06z\"\n        className=\"fill-[#002654] dark:fill-[#00acff]\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/release-contribution-graph.client.tsx",
    "content": "'use client'\n\nimport {\n  ContributionGraph,\n  ContributionGraphBlock,\n  ContributionGraphCalendar,\n  ContributionGraphFooter,\n  ContributionGraphTotalCount,\n  type Activity\n} from '@/src/components/kibo-ui/contribution-graph'\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger\n} from '@/src/components/ui/tooltip'\nimport { cn } from '@/src/lib/utils'\nimport { useState } from 'react'\nimport type { ReleasesByDate } from './release-contribution-graph'\n\ntype ReleaseContributionGraphClientProps = {\n  activities: Activity[]\n  releasesByDate: ReleasesByDate\n  stableCount: number\n  betaCount: number\n}\n\nexport function ReleaseContributionGraphClient({\n  activities,\n  releasesByDate,\n  stableCount,\n  betaCount\n}: ReleaseContributionGraphClientProps) {\n  const [highlightStable, setHighlightStable] = useState(true)\n  const [highlightBeta, setHighlightBeta] = useState(false)\n  return (\n    <TooltipProvider>\n      <ContributionGraph\n        data={activities}\n        maxLevel={2}\n        blockSize={10}\n        blockMargin={3}\n        blockRadius={2}\n        fontSize={12}\n        labels={{\n          totalCount: `${stableCount + betaCount} releases in {{year}}`\n        }}\n      >\n        <ContributionGraphCalendar>\n          {({ activity, dayIndex, weekIndex }) => {\n            const versions = releasesByDate[activity.date]\n            const block = (\n              <ContributionGraphBlock\n                activity={activity}\n                dayIndex={dayIndex}\n                weekIndex={weekIndex}\n                className={cn(\n                  'data-[level=\"0\"]:fill-muted',\n                  highlightBeta\n                    ? 'data-[level=\"1\"]:fill-amber-500 dark:data-[level=\"1\"]:fill-amber-400'\n                    : 'data-[level=\"1\"]:fill-muted-foreground/60',\n                  highlightStable\n                    ? 'data-[level=\"2\"]:fill-green-500 dark:data-[level=\"2\"]:fill-green-400'\n                    : 'data-[level=\"2\"]:fill-foreground/80'\n                )}\n              />\n            )\n            if (!versions) {\n              return block\n            }\n            return (\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <g>{block}</g>\n                </TooltipTrigger>\n                <TooltipContent>\n                  <p className=\"font-semibold\">{activity.date}</p>\n                  <ul>\n                    {versions.map(v => (\n                      <li key={v}>{v}</li>\n                    ))}\n                  </ul>\n                </TooltipContent>\n              </Tooltip>\n            )\n          }}\n        </ContributionGraphCalendar>\n        <ContributionGraphFooter>\n          <ContributionGraphTotalCount />\n          <div className=\"text-muted-foreground ml-auto flex items-center gap-4\">\n            <button\n              aria-label=\"Toggle highlight stable releases\"\n              className=\"flex cursor-pointer items-center gap-1.5\"\n              onClick={() => setHighlightStable(x => !x)}\n            >\n              <div\n                className={cn(\n                  'h-3 w-3 rounded-sm',\n                  highlightStable\n                    ? 'bg-green-500 dark:bg-green-400'\n                    : 'bg-foreground/80'\n                )}\n              />\n              <span>Stable ({stableCount})</span>\n            </button>\n            <button\n              aria-label=\"Toggle highlight beta releases\"\n              className=\"flex cursor-pointer items-center gap-1.5\"\n              onClick={() => setHighlightBeta(x => !x)}\n            >\n              <div\n                className={cn(\n                  'h-3 w-3 rounded-sm',\n                  highlightBeta\n                    ? 'bg-amber-500 dark:bg-amber-400'\n                    : 'bg-muted-foreground/60'\n                )}\n              />\n              <span>Beta ({betaCount})</span>\n            </button>\n          </div>\n        </ContributionGraphFooter>\n      </ContributionGraph>\n    </TooltipProvider>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/release-contribution-graph.tsx",
    "content": "import { type Activity } from '@/src/components/kibo-ui/contribution-graph'\nimport { Suspense } from 'react'\nimport { z } from 'zod'\nimport { ReleaseContributionGraphClient } from './release-contribution-graph.client'\n\nfunction ReleaseContributionGraphSkeleton() {\n  return (\n    <div className=\"flex w-full max-w-[686px] flex-col gap-2\">\n      <div className=\"bg-muted h-[108px] animate-pulse rounded\" />\n      <div className=\"flex h-7 items-center justify-between\">\n        <div className=\"bg-muted h-4.5 w-36 animate-pulse rounded\" />\n        <div className=\"bg-muted h-4.5 w-44 animate-pulse rounded\" />\n      </div>\n    </div>\n  )\n}\n\nexport function ReleaseContributionGraph({\n  year\n}: ReleaseContributionGraphProps) {\n  return (\n    <div className=\"my-12 flex flex-col items-center justify-center\">\n      <Suspense fallback={<ReleaseContributionGraphSkeleton />}>\n        <ReleaseContributionGraphLoader year={year} />\n      </Suspense>\n    </div>\n  )\n}\n\n// --\n\nconst gitHubReleaseSchema = z.object({\n  tag_name: z.string(),\n  published_at: z.string()\n})\n\nconst gitHubReleasesSchema = z.array(gitHubReleaseSchema)\n\ntype GitHubRelease = z.infer<typeof gitHubReleaseSchema>\n\nasync function fetchGitHubReleases(): Promise<GitHubRelease[]> {\n  const releases: GitHubRelease[] = []\n  let page = 1\n  const perPage = 100\n\n  while (true) {\n    const response = await fetch(\n      `https://api.github.com/repos/47ng/nuqs/releases?per_page=${perPage}&page=${page}`,\n      {\n        headers: {\n          Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,\n          Accept: 'application/vnd.github.v3+json'\n        },\n        next: { revalidate: 3600 } // Cache for 1 hour\n      }\n    )\n\n    if (!response.ok) {\n      throw new Error(`GitHub API error: ${response.status}`)\n    }\n\n    const json = await response.json()\n    const data = gitHubReleasesSchema.parse(json)\n\n    if (data.length === 0) {\n      break\n    }\n\n    releases.push(...data)\n\n    if (data.length < perPage) {\n      break\n    }\n\n    page++\n  }\n\n  return releases\n}\n\nfunction isBetaVersion(tag: string): boolean {\n  return tag.includes('beta') || tag.includes('alpha') || tag.includes('rc')\n}\n\ntype ReleaseDay = {\n  date: string\n  hasStable: boolean\n  hasBeta: boolean\n  versions: string[]\n}\n\nexport type ReleasesByDate = Record<string, string[]>\n\ntype ProcessedReleases = {\n  activities: Activity[]\n  releasesByDate: ReleasesByDate\n}\n\nfunction processReleases(\n  releases: GitHubRelease[],\n  year: number\n): ProcessedReleases {\n  const yearPrefix = `${year}-`\n\n  // Filter to specified year releases only\n  const yearReleases = releases.filter(r =>\n    r.published_at.startsWith(yearPrefix)\n  )\n\n  // Group releases by date\n  const releaseDayMap = new Map<string, ReleaseDay>()\n\n  for (const release of yearReleases) {\n    const date = release.published_at.split('T')[0]\n    const isBeta = isBetaVersion(release.tag_name)\n\n    const existing = releaseDayMap.get(date)\n    if (existing) {\n      if (isBeta) {\n        existing.hasBeta = true\n      } else {\n        existing.hasStable = true\n      }\n      existing.versions.push(release.tag_name)\n    } else {\n      releaseDayMap.set(date, {\n        date,\n        hasStable: !isBeta,\n        hasBeta: isBeta,\n        versions: [release.tag_name]\n      })\n    }\n  }\n\n  // Generate all days of the year\n  const startDate = new Date(`${year}-01-01`)\n  const endDate = new Date(`${year}-12-31`)\n  const activities: Activity[] = []\n\n  for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {\n    const dateStr = d.toISOString().split('T')[0]\n    const releaseDay = releaseDayMap.get(dateStr)\n\n    let level = 0\n    if (releaseDay?.hasStable) {\n      level = 2 // Green for stable\n    } else if (releaseDay?.hasBeta) {\n      level = 1 // Amber for beta\n    }\n\n    activities.push({\n      date: dateStr,\n      count: level > 0 ? 1 : 0,\n      level\n    })\n  }\n\n  // Convert map to plain object for serialization\n  const releasesByDate: ReleasesByDate = {}\n  for (const [date, day] of releaseDayMap) {\n    releasesByDate[date] = day.versions\n  }\n\n  return { activities, releasesByDate }\n}\n\ntype ReleaseContributionGraphProps = {\n  year: number\n}\n\nasync function ReleaseContributionGraphLoader({\n  year\n}: ReleaseContributionGraphProps) {\n  const releases = await fetchGitHubReleases()\n  const { activities, releasesByDate } = processReleases(releases, year)\n  const stableCount = activities.filter(a => a.level === 2).length\n  const betaCount = activities.filter(a => a.level === 1).length\n\n  return (\n    <ReleaseContributionGraphClient\n      activities={activities}\n      releasesByDate={releasesByDate}\n      stableCount={stableCount}\n      betaCount={betaCount}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/responsive-helpers.tsx",
    "content": "import { cn } from '@/src/lib/utils'\n\nexport function ResponsiveHelper() {\n  if (process.env.NODE_ENV !== 'development') {\n    return null\n  }\n  return (\n    <aside\n      className=\"\n      pointer-events-none fixed bottom-4 right-4 flex select-none gap-2\n      rounded border bg-background px-2 py-1 font-mono text-xs shadow-xl\n      \"\n    >\n      <span className=\"opacity-25 sm:opacity-100\">sm</span>\n      <span className=\"opacity-25 md:opacity-100\">md</span>\n      <span className=\"opacity-25 lg:opacity-100\">lg</span>\n      <span className=\"opacity-25 xl:opacity-100\">xl</span>\n    </aside>\n  )\n}\n\nconst cqcn = (size: string) =>\n  cn(\n    'absolute inset-0 hidden pointer-events-none bg-background rounded-lg items-center justify-center',\n    size\n  )\n\nexport function ContainerQueryHelper() {\n  if (process.env.NODE_ENV !== 'development') {\n    return null\n  }\n\n  return (\n    <aside\n      className=\"\n      pointer-events-none absolute left-0 top-0 h-6 w-10 select-none\n      rounded border bg-background font-mono text-xs shadow-xl\n      \"\n    >\n      <span className={cqcn('flex')}>base</span>\n      <span className={cqcn('@xs:flex')}>@xs</span>\n      <span className={cqcn('@sm:flex')}>@sm</span>\n      <span className={cqcn('@md:flex')}>@md</span>\n      <span className={cqcn('@lg:flex')}>@lg</span>\n      <span className={cqcn('@xl:flex')}>@xl</span>\n      <span className={cqcn('@2xl:flex')}>@2xl</span>\n      <span className={cqcn('@3xl:flex')}>@3xl</span>\n      <span className={cqcn('@4xl:flex')}>@4xl</span>\n      <span className={cqcn('@5xl:flex')}>@5xl</span>\n    </aside>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/shared-layout.tsx",
    "content": "import { NuqsWordmark } from '@/src/components/logo'\nimport type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'\n\nexport function getSharedLayoutProps(): BaseLayoutProps {\n  return {\n    githubUrl: 'https://github.com/47ng/nuqs',\n    nav: {\n      title: <NuqsWordmark className=\"ml-2 text-xl\" />,\n      transparentMode: 'top'\n    },\n    links: [\n      {\n        text: 'Documentation',\n        url: '/docs',\n        active: 'nested-url'\n      },\n      {\n        text: 'Playground',\n        url: '/playground',\n        active: 'nested-url'\n      },\n      {\n        text: 'Registry',\n        url: '/registry',\n        active: 'nested-url'\n      },\n      {\n        text: 'Blog',\n        url: '/blog',\n        active: 'nested-url'\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/components/sidebar-footer.tsx",
    "content": "'use client'\n\nimport { useEffect, useState } from 'react'\n\nexport function SidebarFooter() {\n  const [version, setVersion] = useState<string | null>(null)\n  useEffect(() => {\n    getLatestVersion().then(setVersion).catch(console.error)\n  }, [])\n  if (!version) return null\n  return (\n    <footer className=\"ml-2 flex w-full items-baseline gap-2 text-zinc-600 dark:text-zinc-400\">\n      <a\n        href={`https://npmjs.com/package/nuqs/v/${version}`}\n        className=\"hover:underline\"\n        tabIndex={-1}\n      >\n        <pre className=\"text-xs\">nuqs@{version}</pre>\n      </a>\n    </footer>\n  )\n}\n\nasync function getLatestVersion() {\n  try {\n    const res = await fetch('https://registry.npmjs.org/nuqs').then(r =>\n      r.json()\n    )\n    return res['dist-tags'].latest\n  } catch {\n    return 'latest'\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/components/typography.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport { Link } from 'lucide-react'\nimport NextLink from 'next/link'\nimport { ComponentProps } from 'react'\n\nexport function H1({ children, className, ...props }: ComponentProps<'h1'>) {\n  return (\n    <h1\n      className={cn(\n        'scroll-m-28',\n        props.id && 'flex flex-row items-center gap-2',\n        className\n      )}\n      {...props}\n    >\n      {props.id ? (\n        <>\n          <NextLink\n            href={`#${props.id}`}\n            className=\"peer font-extrabold no-underline hover:opacity-100\"\n          >\n            {children}\n          </NextLink>\n          <Link\n            className=\"text-fd-muted-foreground size-3.5 shrink-0 opacity-0 transition-opacity peer-hover:opacity-100\"\n            aria-label=\"Link to section\"\n          />\n        </>\n      ) : (\n        children\n      )}\n    </h1>\n  )\n}\n\nexport function H2({ children, className, ...props }: ComponentProps<'h2'>) {\n  return (\n    <h2\n      className={cn(\n        'scroll-m-28',\n        props.id && 'flex flex-row items-center gap-2',\n        className\n      )}\n      {...props}\n    >\n      {props.id ? (\n        <>\n          <NextLink\n            href={`#${props.id}`}\n            className=\"peer font-semibold no-underline hover:opacity-100\"\n          >\n            {children}\n          </NextLink>\n          <Link\n            className=\"text-fd-muted-foreground size-3.5 shrink-0 opacity-0 transition-opacity peer-hover:opacity-100\"\n            aria-label=\"Link to section\"\n          />\n        </>\n      ) : (\n        children\n      )}\n    </h2>\n  )\n}\n\nexport const Description: React.FC<React.ComponentProps<'p'>> = ({\n  children,\n  className,\n  ...props\n}) => (\n  <p className={cn('text-muted-foreground text-lg', className)} {...props}>\n    {children}\n  </p>\n)\n"
  },
  {
    "path": "packages/docs/src/components/ui/badge.tsx",
    "content": "import { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from '@/src/lib/utils'\n\nconst badgeVariants = cva(\n  'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n  {\n    variants: {\n      variant: {\n        default:\n          'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',\n        secondary:\n          'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        destructive:\n          'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',\n        outline: 'text-foreground'\n      }\n    },\n    defaultVariants: {\n      variant: 'default'\n    }\n  }\n)\n\nexport interface BadgeProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n  return (\n    <div className={cn(badgeVariants({ variant }), className)} {...props} />\n  )\n}\n\nexport { Badge, badgeVariants }\n"
  },
  {
    "path": "packages/docs/src/components/ui/breadcrumb.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst Breadcrumb = React.forwardRef<\n  HTMLElement,\n  React.ComponentPropsWithoutRef<\"nav\"> & {\n    separator?: React.ReactNode\n  }\n>(({ ...props }, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />)\nBreadcrumb.displayName = \"Breadcrumb\"\n\nconst BreadcrumbList = React.forwardRef<\n  HTMLOListElement,\n  React.ComponentPropsWithoutRef<\"ol\">\n>(({ className, ...props }, ref) => (\n  <ol\n    ref={ref}\n    className={cn(\n      \"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5\",\n      className\n    )}\n    {...props}\n  />\n))\nBreadcrumbList.displayName = \"BreadcrumbList\"\n\nconst BreadcrumbItem = React.forwardRef<\n  HTMLLIElement,\n  React.ComponentPropsWithoutRef<\"li\">\n>(({ className, ...props }, ref) => (\n  <li\n    ref={ref}\n    className={cn(\"inline-flex items-center gap-1.5\", className)}\n    {...props}\n  />\n))\nBreadcrumbItem.displayName = \"BreadcrumbItem\"\n\nconst BreadcrumbLink = React.forwardRef<\n  HTMLAnchorElement,\n  React.ComponentPropsWithoutRef<\"a\"> & {\n    asChild?: boolean\n  }\n>(({ asChild, className, ...props }, ref) => {\n  const Comp = asChild ? Slot : \"a\"\n\n  return (\n    <Comp\n      ref={ref}\n      className={cn(\"transition-colors hover:text-foreground\", className)}\n      {...props}\n    />\n  )\n})\nBreadcrumbLink.displayName = \"BreadcrumbLink\"\n\nconst BreadcrumbPage = React.forwardRef<\n  HTMLSpanElement,\n  React.ComponentPropsWithoutRef<\"span\">\n>(({ className, ...props }, ref) => (\n  <span\n    ref={ref}\n    role=\"link\"\n    aria-disabled=\"true\"\n    aria-current=\"page\"\n    className={cn(\"font-normal text-foreground\", className)}\n    {...props}\n  />\n))\nBreadcrumbPage.displayName = \"BreadcrumbPage\"\n\nconst BreadcrumbSeparator = ({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) => (\n  <li\n    role=\"presentation\"\n    aria-hidden=\"true\"\n    className={cn(\"[&>svg]:w-3.5 [&>svg]:h-3.5\", className)}\n    {...props}\n  >\n    {children ?? <ChevronRight />}\n  </li>\n)\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\"\n\nconst BreadcrumbEllipsis = ({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) => (\n  <span\n    role=\"presentation\"\n    aria-hidden=\"true\"\n    className={cn(\"flex h-9 w-9 items-center justify-center\", className)}\n    {...props}\n  >\n    <MoreHorizontal className=\"h-4 w-4\" />\n    <span className=\"sr-only\">More</span>\n  </span>\n)\nBreadcrumbEllipsis.displayName = \"BreadcrumbElipssis\"\n\nexport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  BreadcrumbEllipsis,\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n        outline:\n          \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-10 px-4 py-2\",\n        sm: \"h-9 rounded-md px-3\",\n        lg: \"h-11 rounded-md px-8\",\n        icon: \"h-10 w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\"\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "packages/docs/src/components/ui/card.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/src/lib/utils'\n\nfunction Card({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card\"\n      className={cn(\n        'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn('leading-none font-semibold', className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn('px-6', className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn('flex items-center px-6 [.border-t]:pt-6', className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardAction,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/chart.tsx",
    "content": "// From https://evilcharts.com/\n\n'use client'\n\nimport * as React from 'react'\nimport * as RechartsPrimitive from 'recharts'\n\nimport { cn } from '@/src/lib/utils'\nimport type { ValueType } from 'recharts/types/component/DefaultTooltipContent'\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const\n\nexport type ChartConfig = {\n  [k in string]: {\n    label?: React.ReactNode\n    icon?: React.ComponentType\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n}\n\ntype ChartContextProps = {\n  config: ChartConfig\n}\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n  const context = React.useContext(ChartContext)\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />')\n  }\n\n  return context\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  domChildren,\n  config = {},\n  ...props\n}: React.ComponentProps<'div'> & {\n  config?: ChartConfig\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children']\n  domChildren?: React.ReactNode\n}) {\n  const uniqueId = React.useId()\n  const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer>\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n        {domChildren}\n      </div>\n    </ChartContext.Provider>\n  )\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme || config.color\n  )\n\n  if (!colorConfig.length) {\n    return null\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||\n      itemConfig.color\n    return color ? `  --color-${key}: ${color};` : null\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n')\n      }}\n    />\n  )\n}\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  valueFormatter = v => v.toLocaleString(),\n  color,\n  nameKey,\n  labelKey\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    payload?: RechartsPrimitive.DefaultTooltipContentProps<\n      ValueType,\n      string | number\n    >['payload']\n    label?: string\n    hideLabel?: boolean\n    hideIndicator?: boolean\n    indicator?: 'line' | 'dot' | 'dashed'\n    nameKey?: string\n    labelKey?: string\n    valueFormatter?: (value: ValueType) => React.ReactNode\n  }) {\n  const { config } = useChart()\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null\n    }\n\n    const [item] = payload\n    const key = `${labelKey || item?.dataKey || item?.name || 'value'}`\n    const itemConfig = getPayloadConfigFromPayload(config, item, key)\n    const value =\n      !labelKey && typeof label === 'string'\n        ? config[label as keyof typeof config]?.label || label\n        : itemConfig?.label\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      )\n    }\n\n    if (!value) {\n      return null\n    }\n\n    return (\n      <div\n        className={cn(\n          'border-border border-b px-3 py-2 font-medium',\n          labelClassName\n        )}\n      >\n        {value}\n      </div>\n    )\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey\n  ])\n\n  if (!active || !payload?.length) {\n    return null\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot'\n\n  return (\n    <div\n      className={cn(\n        'border-border bg-muted/30 grid min-w-[8rem] items-start rounded-lg border text-sm shadow-xl backdrop-blur-md',\n        className\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5 px-3 py-2.5\">\n        {payload.map((item, index) => {\n          const key = `${nameKey || item.name || item.dataKey || 'value'}`\n          const itemConfig = getPayloadConfigFromPayload(config, item, key)\n          const indicatorColor = color || item.payload.fill || item.color\n\n          return (\n            <div\n              key={String(item.dataKey)}\n              className={cn(\n                '[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',\n                indicator === 'dot' && 'items-baseline'\n              )}\n            >\n              {formatter && item?.value !== undefined && item.name ? (\n                formatter(item.value, item.name, item, index, item.payload)\n              ) : (\n                <>\n                  {itemConfig?.icon ? (\n                    <itemConfig.icon />\n                  ) : (\n                    !hideIndicator && (\n                      <div\n                        className={cn(\n                          'shrink-0 rounded-full border-(--color-border) bg-(--color-bg)',\n                          {\n                            'size-2.25': indicator === 'dot',\n                            'w-1': indicator === 'line',\n                            'w-0 border-[1.5px] border-dashed bg-transparent':\n                              indicator === 'dashed',\n                            'my-0.5': nestLabel && indicator === 'dashed'\n                          }\n                        )}\n                        style={\n                          {\n                            '--color-bg': indicatorColor,\n                            '--color-border': indicatorColor\n                          } as React.CSSProperties\n                        }\n                      />\n                    )\n                  )}\n                  <div\n                    className={cn(\n                      'flex flex-1 justify-between gap-8 leading-none',\n                      nestLabel ? 'items-end' : 'items-center'\n                    )}\n                  >\n                    <div className=\"grid gap-1.5\">\n                      {nestLabel ? tooltipLabel : null}\n                      <span className=\"text-muted-foreground\">\n                        {itemConfig?.label || item.name}\n                      </span>\n                    </div>\n                    {item.value && (\n                      <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                        {valueFormatter(item.value)}\n                      </span>\n                    )}\n                  </div>\n                </>\n              )}\n            </div>\n          )\n        })}\n      </div>\n    </div>\n  )\n}\n\nconst ChartLegend = RechartsPrimitive.Legend\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload = [],\n  verticalAlign = 'bottom',\n  align = 'center',\n  nameKey\n}: React.ComponentProps<'div'> &\n  Pick<RechartsPrimitive.LegendProps, 'verticalAlign' | 'align'> & {\n    payload?: RechartsPrimitive.LegendPayload[]\n    hideIcon?: boolean\n    nameKey?: string\n  }) {\n  const { config } = useChart()\n\n  if (!payload.length) {\n    return null\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center gap-4 text-sm',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        align === 'center' && 'justify-center',\n        align === 'left' && 'justify-start',\n        align === 'right' && 'justify-end',\n        className\n      )}\n    >\n      {payload.map(item => {\n        const key = `${nameKey || item.dataKey || 'value'}`\n        const itemConfig = getPayloadConfigFromPayload(config, item, key)\n\n        return (\n          <div\n            key={item.value}\n            className={cn(\n              'text-muted-foreground/75 flex items-baseline gap-1.5'\n            )}\n          >\n            {itemConfig?.icon && !hideIcon ? (\n              <itemConfig.icon />\n            ) : (\n              <div\n                className=\"size-2.25 shrink-0 rounded-full\"\n                style={{\n                  backgroundColor: item.color\n                }}\n              />\n            )}\n            {itemConfig?.label}\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n\n// Helper to extract item config from a payload.\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return undefined\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined\n\n  let configLabelKey: string = key\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string\n  }\n\n  return configLabelKey in config\n    ? config[configLabelKey]\n    : config[key as keyof typeof config]\n}\n\nexport {\n  ChartContainer,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n  ChartTooltip,\n  ChartTooltipContent\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/checkbox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n  React.ElementRef<typeof CheckboxPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <CheckboxPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n      className\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator\n      className={cn(\"flex items-center justify-center text-current\")}\n    >\n      <Check className=\"h-4 w-4\" />\n    </CheckboxPrimitive.Indicator>\n  </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n"
  },
  {
    "path": "packages/docs/src/components/ui/input.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/src/lib/utils'\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          'border-input bg-background ring-offset-background file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',\n          className\n        )}\n        ref={ref}\n        autoComplete=\"off\"\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = 'Input'\n\nexport { Input }\n"
  },
  {
    "path": "packages/docs/src/components/ui/label.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst labelVariants = cva(\n  \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n)\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n    VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(labelVariants(), className)}\n    {...props}\n  />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"
  },
  {
    "path": "packages/docs/src/components/ui/pagination.tsx",
    "content": "import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'\nimport * as React from 'react'\n\nimport { Button, ButtonProps, buttonVariants } from '@/src/components/ui/button'\nimport { cn } from '@/src/lib/utils'\nimport Link, { LinkProps } from 'next/link'\n\nconst Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (\n  <nav\n    role=\"navigation\"\n    aria-label=\"pagination\"\n    className={cn('mx-auto flex w-full justify-center', className)}\n    {...props}\n  />\n)\nPagination.displayName = 'Pagination'\n\nconst PaginationContent = React.forwardRef<\n  HTMLUListElement,\n  React.ComponentProps<'ul'>\n>(({ className, ...props }, ref) => (\n  <ul\n    ref={ref}\n    className={cn('flex flex-row items-center gap-1', className)}\n    {...props}\n  />\n))\nPaginationContent.displayName = 'PaginationContent'\n\nconst PaginationItem = React.forwardRef<\n  HTMLLIElement,\n  React.ComponentProps<'li'>\n>(({ className, ...props }, ref) => (\n  <li\n    ref={ref}\n    className={cn('cursor-pointer select-none', className)}\n    {...props}\n  />\n))\nPaginationItem.displayName = 'PaginationItem'\n\ntype PaginationButtonProps = {\n  isActive?: boolean\n} & ButtonProps\n\nconst PaginationButton = ({\n  className,\n  isActive,\n  size = 'icon',\n  ...props\n}: PaginationButtonProps) => (\n  <Button\n    aria-current={isActive ? 'page' : undefined}\n    variant={isActive ? 'outline' : 'ghost'}\n    size={size}\n    className={className}\n    {...props}\n  />\n)\nPaginationButton.displayName = 'PaginationButton'\n\ntype PaginationLinkProps = {\n  isActive?: boolean\n} & LinkProps &\n  Pick<ButtonProps, 'size' | 'disabled'> &\n  React.ComponentPropsWithoutRef<'a'>\n\nconst PaginationLink = ({\n  className,\n  isActive,\n  size = 'icon',\n  disabled,\n  ...props\n}: PaginationLinkProps) => (\n  <Link\n    aria-current={isActive ? 'page' : undefined}\n    className={cn(\n      buttonVariants({\n        variant: isActive ? 'outline' : 'ghost',\n        size,\n        className\n      }),\n      disabled && 'pointer-events-none opacity-50'\n    )}\n    {...props}\n  />\n)\nPaginationLink.displayName = 'PaginationLink'\n\nconst PaginationPrevious = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationButton>) => (\n  <PaginationButton\n    aria-label=\"Go to previous page\"\n    size=\"default\"\n    className={cn('gap-1 pl-2.5', className)}\n    {...props}\n  >\n    <ChevronLeft className=\"h-4 w-4\" />\n    <span className=\"hidden sm:inline\">Previous</span>\n  </PaginationButton>\n)\nPaginationPrevious.displayName = 'PaginationPrevious'\n\nconst PaginationPreviousLink = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) => (\n  <PaginationLink\n    aria-label=\"Go to previous page\"\n    size=\"default\"\n    className={cn('gap-1 pl-2.5', className)}\n    {...props}\n  >\n    <ChevronLeft className=\"h-4 w-4\" />\n    <span className=\"hidden sm:inline\">Previous</span>\n  </PaginationLink>\n)\nPaginationPreviousLink.displayName = 'PaginationPreviousLink'\n\nconst PaginationNext = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationButton>) => (\n  <PaginationButton\n    aria-label=\"Go to next page\"\n    size=\"default\"\n    className={cn('gap-1 pr-2.5', className)}\n    {...props}\n  >\n    <span className=\"hidden sm:inline\">Next</span>\n    <ChevronRight className=\"h-4 w-4\" />\n  </PaginationButton>\n)\nPaginationNext.displayName = 'PaginationNext'\n\nconst PaginationNextLink = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof PaginationLink>) => (\n  <PaginationLink\n    aria-label=\"Go to next page\"\n    size=\"default\"\n    className={cn('gap-1 pr-2.5', className)}\n    {...props}\n  >\n    <span className=\"hidden sm:inline\">Next</span>\n    <ChevronRight className=\"h-4 w-4\" />\n  </PaginationLink>\n)\nPaginationNextLink.displayName = 'PaginationNextLink'\n\nconst PaginationEllipsis = ({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) => (\n  <span\n    aria-hidden\n    className={cn('flex h-9 w-9 items-center justify-center', className)}\n    {...props}\n  >\n    <MoreHorizontal className=\"h-4 w-4\" />\n    <span className=\"sr-only\">More pages</span>\n  </span>\n)\nPaginationEllipsis.displayName = 'PaginationEllipsis'\n\nexport {\n  Pagination,\n  PaginationButton,\n  PaginationContent,\n  PaginationEllipsis,\n  PaginationItem,\n  PaginationLink,\n  PaginationNext,\n  PaginationNextLink,\n  PaginationPrevious,\n  PaginationPreviousLink\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/popover.tsx",
    "content": "'use client'\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\nimport * as React from 'react'\n\nimport { cn } from '@/src/lib/utils'\n\nfunction Popover({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Root>) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />\n}\n\nfunction PopoverTrigger({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />\n}\n\nfunction PopoverContent({\n  className,\n  align = 'center',\n  sideOffset = 4,\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Content>) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Content\n        data-slot=\"popover-content\"\n        align={align}\n        sideOffset={sideOffset}\n        className={cn(\n          'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',\n          className\n        )}\n        {...props}\n      />\n    </PopoverPrimitive.Portal>\n  )\n}\n\nfunction PopoverAnchor({\n  ...props\n}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {\n  return <PopoverPrimitive.Anchor data-slot=\"popover-anchor\" {...props} />\n}\n\nexport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }\n"
  },
  {
    "path": "packages/docs/src/components/ui/pr-line.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport {\n  GitMerge,\n  GitPullRequestArrow,\n  GitPullRequestClosed,\n  GitPullRequestDraft,\n  type LucideIcon\n} from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { z } from 'zod/v4'\n\nexport type PullRequestLineProps = ComponentProps<'li'> & {\n  number: number | string\n}\n\nconst pullRequestSchema = z.object({\n  title: z.string(),\n  state: z.enum(['open', 'closed', 'merged']),\n  draft: z.boolean(),\n  merged: z.boolean(),\n  html_url: z.url(),\n  user: z.object({\n    login: z.string(),\n    avatar_url: z.url()\n  })\n})\n\ntype Status = 'open' | 'closed' | 'merged' | 'draft'\n\nconst statusColors: Record<Status, string> = {\n  open: 'text-green-600 dark:text-green-400',\n  closed: 'text-red-500 dark:text-red-400',\n  merged: 'text-purple-600 dark:text-purple-400',\n  draft: 'text-gray-600 dark:text-gray-400'\n}\n\nconst statusIcons: Record<Status, LucideIcon> = {\n  open: GitPullRequestArrow,\n  closed: GitPullRequestClosed,\n  merged: GitMerge,\n  draft: GitPullRequestDraft\n}\n\nexport async function PullRequestLine({\n  number,\n  className,\n  children,\n  ...props\n}: PullRequestLineProps) {\n  const response = await fetch(\n    `https://api.github.com/repos/47ng/nuqs/pulls/${number}`,\n    {\n      headers: {\n        Accept: 'application/vnd.github.v3+json',\n        Authorization: `bearer ${process.env.GITHUB_TOKEN}`\n      },\n      cache: 'force-cache'\n    }\n  )\n  if (!response.ok) {\n    return (\n      <li className={cn('not-prose space-x-2', className)} {...props}>\n        <span className=\"text-sm text-gray-500 tabular-nums sm:text-base sm:font-medium\">\n          <span aria-label=\"number\">#</span>\n          {number}\n        </span>\n        <span className=\"font-semibold text-gray-500\">\n          Failed to fetch details: {response.status} {response.statusText}\n        </span>\n        {children}\n      </li>\n    )\n  }\n  const data = pullRequestSchema.parse(await response.json())\n\n  const status: Status = data.merged\n    ? 'merged'\n    : data.draft\n      ? 'draft'\n      : data.state === 'open'\n        ? 'open'\n        : 'closed'\n  const StatusIcon = statusIcons[status]\n\n  // Clean up conventional commit prefixes, including scopes\n  const title = data.title\n    .replace(\n      /^(feat|fix|doc[s]?|style|refactor|perf|test|chore|ci)(\\(\\w+\\))?:\\s*/i,\n      ''\n    )\n    .trim()\n  return (\n    <li className={cn('not-prose space-x-2', className)} {...props}>\n      <a\n        href={data.html_url}\n        className=\"group space-x-1.5\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n      >\n        <StatusIcon\n          className={cn('hidden self-center sm:inline', statusColors[status])}\n          aria-label={`${status} PR`}\n          size={16}\n        />\n        <span className=\"text-sm text-gray-500 tabular-nums sm:text-base sm:font-medium\">\n          <span aria-label=\"number\">#</span>\n          {number}\n        </span>\n        <span className=\"font-medium group-hover:underline\">{title}</span>\n      </a>\n      <a\n        href={`https://github.com/${data.user.login}`}\n        className=\"text-sm whitespace-nowrap text-gray-500 hover:underline\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n      >\n        <img\n          src={data.user.avatar_url}\n          alt={`${data.user.login}'s avatar`}\n          role=\"presentation\"\n          className=\"inline size-5 rounded-full\"\n        />{' '}\n        <span className=\"sr-only\">by</span>\n        {data.user.login}\n      </a>\n      {children}\n    </li>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst Select = SelectPrimitive.Root\n\nconst SelectGroup = SelectPrimitive.Group\n\nconst SelectValue = SelectPrimitive.Value\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDown className=\"h-4 w-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n))\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronUp className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollUpButton>\n))\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronDown className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollDownButton>\n))\nSelectScrollDownButton.displayName =\n  SelectPrimitive.ScrollDownButton.displayName\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        position === \"popper\" &&\n          \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n        className\n      )}\n      position={position}\n      {...props}\n    >\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          \"p-1\",\n          position === \"popper\" &&\n            \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n        )}\n      >\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n))\nSelectContent.displayName = SelectPrimitive.Content.displayName\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\"py-1.5 pl-8 pr-2 text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nSelectLabel.displayName = SelectPrimitive.Label.displayName\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n))\nSelectItem.displayName = SelectPrimitive.Item.displayName\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n))\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton,\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/separator.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst Separator = React.forwardRef<\n  React.ElementRef<typeof SeparatorPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>\n>(\n  (\n    { className, orientation = \"horizontal\", decorative = true, ...props },\n    ref\n  ) => (\n    <SeparatorPrimitive.Root\n      ref={ref}\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"shrink-0 bg-border\",\n        orientation === \"horizontal\" ? \"h-[1px] w-full\" : \"h-full w-[1px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n)\nSeparator.displayName = SeparatorPrimitive.Root.displayName\n\nexport { Separator }\n"
  },
  {
    "path": "packages/docs/src/components/ui/slider.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst Slider = React.forwardRef<\n  React.ElementRef<typeof SliderPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <SliderPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex w-full touch-none select-none items-center\",\n      className\n    )}\n    {...props}\n  >\n    <SliderPrimitive.Track className=\"relative h-2 w-full grow overflow-hidden rounded-full bg-secondary\">\n      <SliderPrimitive.Range className=\"absolute h-full bg-primary\" />\n    </SliderPrimitive.Track>\n    <SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\n  </SliderPrimitive.Root>\n))\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider }\n"
  },
  {
    "path": "packages/docs/src/components/ui/switch.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      \"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  >\n    <SwitchPrimitives.Thumb\n      className={cn(\n        \"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\"\n      )}\n    />\n  </SwitchPrimitives.Root>\n))\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"
  },
  {
    "path": "packages/docs/src/components/ui/tabs.tsx",
    "content": "'use client'\n\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\nimport * as React from 'react'\n\nimport { cn } from '@/src/lib/utils'\n\nfunction Tabs({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Root>) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      className={cn('flex flex-col gap-2', className)}\n      {...props}\n    />\n  )\n}\n\nfunction TabsList({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.List>) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      className={cn(\n        'bg-muted/50 text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsTrigger({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n  return (\n    <TabsPrimitive.Trigger\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        \"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction TabsContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof TabsPrimitive.Content>) {\n  return (\n    <TabsPrimitive.Content\n      data-slot=\"tabs-content\"\n      className={cn('flex-1 outline-none', className)}\n      {...props}\n    />\n  )\n}\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger }\n"
  },
  {
    "path": "packages/docs/src/components/ui/toggle-group.tsx",
    "content": "'use client'\n\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'\nimport { VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { toggleVariants } from '@/src/components/ui/toggle'\nimport { cn } from '@/src/lib/utils'\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants>\n>({\n  size: 'default',\n  variant: 'default'\n})\n\nconst ToggleGroup = React.forwardRef<\n  React.ElementRef<typeof ToggleGroupPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &\n    VariantProps<typeof toggleVariants>\n>(({ className, variant, size, children, ...props }, ref) => (\n  <ToggleGroupPrimitive.Root\n    ref={ref}\n    className={cn('flex items-center justify-center gap-1', className)}\n    {...props}\n  >\n    <ToggleGroupContext.Provider value={{ variant, size }}>\n      <>{children}</>\n    </ToggleGroupContext.Provider>\n  </ToggleGroupPrimitive.Root>\n))\n\nToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName\n\nconst ToggleGroupItem = React.forwardRef<\n  React.ElementRef<typeof ToggleGroupPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &\n    VariantProps<typeof toggleVariants>\n>(({ className, children, variant, size, ...props }, ref) => {\n  const context = React.useContext(ToggleGroupContext)\n\n  return (\n    <ToggleGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size\n        }),\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </ToggleGroupPrimitive.Item>\n  )\n})\n\nToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName\n\nexport { ToggleGroup, ToggleGroupItem }\n"
  },
  {
    "path": "packages/docs/src/components/ui/toggle.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nconst toggleVariants = cva(\n  \"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline:\n          \"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground\",\n      },\n      size: {\n        default: \"h-10 px-3\",\n        sm: \"h-9 px-2.5\",\n        lg: \"h-11 px-5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nconst Toggle = React.forwardRef<\n  React.ElementRef<typeof TogglePrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &\n    VariantProps<typeof toggleVariants>\n>(({ className, variant, size, ...props }, ref) => (\n  <TogglePrimitive.Root\n    ref={ref}\n    className={cn(toggleVariants({ variant, size, className }))}\n    {...props}\n  />\n))\n\nToggle.displayName = TogglePrimitive.Root.displayName\n\nexport { Toggle, toggleVariants }\n"
  },
  {
    "path": "packages/docs/src/components/ui/tooltip-popover.tsx",
    "content": "// Tooltip on desktop, Popover on touch devices\n// Source: https://github.com/shadcn-ui/ui/issues/2402#issuecomment-1930895113\n\n'use client'\n\nimport {\n  PopoverContentProps,\n  PopoverProps,\n  PopoverTriggerProps\n} from '@radix-ui/react-popover'\nimport {\n  TooltipContentProps,\n  TooltipProps,\n  TooltipTriggerProps\n} from '@radix-ui/react-tooltip'\nimport {\n  PropsWithChildren,\n  createContext,\n  useContext,\n  useEffect,\n  useState\n} from 'react'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\nimport { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'\n\nconst TouchContext = createContext<boolean>(false)\nconst useTouch = () => useContext(TouchContext)\n\nexport const TouchProvider = (props: PropsWithChildren) => {\n  const [isTouch, setTouch] = useState(false)\n  useEffect(() => {\n    setTouch(window.matchMedia('(pointer: coarse)').matches)\n  }, [])\n  return <TouchContext.Provider value={isTouch} {...props} />\n}\n\nexport const TooltipPopover = (props: TooltipProps & PopoverProps) => {\n  return (\n    <TouchProvider>\n      <TooltipPopoverRoot {...props} />\n    </TouchProvider>\n  )\n}\n\nexport const TooltipPopoverRoot = (props: TooltipProps & PopoverProps) => {\n  const isTouch = useTouch()\n  return isTouch ? <Popover {...props} /> : <Tooltip {...props} />\n}\n\nexport const TooltipPopoverTrigger = (\n  props: TooltipTriggerProps & PopoverTriggerProps\n) => {\n  const isTouch = useTouch()\n  return isTouch ? <PopoverTrigger {...props} /> : <TooltipTrigger {...props} />\n}\n\nexport const TooltipPopoverContent = (\n  props: TooltipContentProps & PopoverContentProps\n) => {\n  const isTouch = useTouch()\n  return isTouch ? <PopoverContent {...props} /> : <TooltipContent {...props} />\n}\n"
  },
  {
    "path": "packages/docs/src/components/ui/tooltip.tsx",
    "content": "'use client'\n\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip'\nimport * as React from 'react'\n\nimport { cn } from '@/src/lib/utils'\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  )\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  )\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\n"
  },
  {
    "path": "packages/docs/src/components/vercel-oss-badge.tsx",
    "content": "import { cn } from '@/src/lib/utils'\nimport type { ComponentProps } from 'react'\n\nexport const VercelOssBadge = ({\n  className,\n  ...props\n}: ComponentProps<'svg'>) => {\n  return (\n    <svg\n      width=\"240\"\n      height=\"24\"\n      viewBox=\"0 0 240 24\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className={cn('fill-black dark:fill-white', className)}\n      {...props}\n    >\n      <path\n        d=\"M126.243 10.3583H125.068L128.654 0H129.829L126.243 10.3583Z\"\n        fillOpacity=\"0.5\"\n      />\n      <path\n        d=\"M133.507 10.3583H132.332L135.918 0H137.093L133.507 10.3583Z\"\n        fillOpacity=\"0.5\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M156.511 0.472649C158.401 0.472655 159.589 2.21719 159.589 4.97938C159.589 7.71735 158.401 9.46185 156.511 9.46185C154.621 9.46185 153.434 7.71736 153.434 4.97938C153.434 2.21719 154.621 0.472649 156.511 0.472649ZM156.511 1.61138C155.385 1.61138 154.682 2.88351 154.682 4.97938C154.682 7.05103 155.385 8.32312 156.511 8.32312C157.638 8.32311 158.341 7.05103 158.341 4.97938C158.341 2.88352 157.638 1.61139 156.511 1.61138Z\"\n        fillOpacity=\"0.5\"\n      />\n      <path\n        d=\"M173.609 1.80541H169.913L169.586 4.24053C169.974 3.78017 170.689 3.53778 171.319 3.53777C172.881 3.53777 174.069 4.77346 174.069 6.49375C174.069 8.23829 172.821 9.46184 171.064 9.46185C169.501 9.46185 168.217 8.39595 168.084 7.02703L169.332 6.95427C169.453 7.80226 170.168 8.32312 171.173 8.32312C172.13 8.32305 172.821 7.57193 172.821 6.49375C172.821 5.39133 172.07 4.66438 171.028 4.66438C170.374 4.66438 169.707 5.02786 169.489 5.56087H168.229L168.896 0.666677H173.609V1.80541Z\"\n        fillOpacity=\"0.5\"\n      />\n      <path\n        d=\"M149.465 0.472649C151.076 0.472649 152.252 1.52671 152.252 2.95627C152.252 4.20408 151.282 5.06413 150.022 5.82736C149.392 6.19084 147.745 7.29347 147.684 8.12938H152.312V9.26812H146.206C146.206 6.67556 147.927 5.75481 149.477 4.77352C150.337 4.22836 151.016 3.61046 151.016 2.95627C151.016 2.1688 150.374 1.61138 149.465 1.61138C148.472 1.61141 147.805 2.24139 147.599 3.35587L146.364 3.27127C146.57 1.66001 147.708 0.472682 149.465 0.472649Z\"\n        fillOpacity=\"0.5\"\n      />\n      <path\n        d=\"M163.994 0.472649C165.605 0.472649 166.78 1.52671 166.78 2.95627C166.78 4.20408 165.811 5.06413 164.551 5.82736C163.921 6.19084 162.273 7.29347 162.213 8.12938H166.841V9.26812H160.735C160.735 6.67556 162.455 5.75481 164.006 4.77352C164.866 4.22836 165.544 3.61046 165.544 2.95627C165.544 2.1688 164.902 1.61138 163.994 1.61138C163 1.61141 162.334 2.24139 162.128 3.35587L160.892 3.27127C161.098 1.66001 162.237 0.472682 163.994 0.472649Z\"\n        fillOpacity=\"0.5\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M40.2826 15.0106C42.2937 15.0106 43.4808 16.7551 43.4808 19.5173C43.4808 22.2553 42.2937 23.9998 40.2826 23.9998C38.2715 23.9998 37.0843 22.2553 37.0843 19.5173C37.0843 16.7551 38.2715 15.0106 40.2826 15.0106ZM40.2826 16.1493C39.0347 16.1493 38.3319 17.4215 38.3319 19.5173C38.3319 21.589 39.0347 22.8611 40.2826 22.8611C41.5304 22.8611 42.2332 21.589 42.2332 19.5173C42.2332 17.4215 41.5304 16.1493 40.2826 16.1493Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M83.8684 15.0106C85.8795 15.0106 87.0666 16.7552 87.0666 19.5173C87.0666 22.2553 85.8795 23.9998 83.8684 23.9998C81.8573 23.9998 80.6701 22.2553 80.6701 19.5173C80.6701 16.7551 81.8573 15.0106 83.8684 15.0106ZM83.8684 16.1493C82.6205 16.1493 81.9177 17.4215 81.9177 19.5173C81.9177 21.589 82.6205 22.8611 83.8684 22.8611C85.1162 22.8611 85.819 21.589 85.819 19.5173C85.819 17.4215 85.1162 16.1494 83.8684 16.1493Z\"\n      />\n      <path d=\"M89.4124 20.8258C89.4124 22.1221 89.9818 22.8611 91.1327 22.8611C92.2836 22.8611 92.8529 22.1221 92.8529 20.8258V15.1925H94.0402V20.7895C94.0402 22.7642 92.9742 23.9998 91.1327 23.9998C89.2912 23.9998 88.2252 22.7642 88.2252 20.7895V15.1925H89.4124V20.8258Z\" />\n      <path d=\"M105.831 15.0106C107.369 15.0106 108.46 16.0768 108.86 17.7971L107.6 17.882C107.309 16.7553 106.679 16.1493 105.819 16.1493C104.462 16.1494 103.699 17.3488 103.699 19.5173C103.699 21.6737 104.462 22.861 105.819 22.8611C106.764 22.8611 107.418 22.1947 107.672 20.9589L108.92 21.0317C108.581 22.8611 107.466 23.9998 105.831 23.9998C103.686 23.9997 102.463 22.2674 102.463 19.5173C102.463 16.7431 103.686 15.0107 105.831 15.0106Z\" />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M134.718 15.0106C136.73 15.0106 137.917 16.7552 137.917 19.5173C137.917 22.2553 136.73 23.9998 134.718 23.9998C132.707 23.9998 131.52 22.2553 131.52 19.5173C131.52 16.7551 132.707 15.0106 134.718 15.0106ZM134.718 16.1493C133.471 16.1493 132.768 17.4215 132.768 19.5173C132.768 21.589 133.471 22.8611 134.718 22.8611C135.966 22.8611 136.669 21.589 136.669 19.5173C136.669 17.4215 135.966 16.1494 134.718 16.1493Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M207.362 15.0106C209.373 15.0106 210.56 16.7552 210.56 19.5173C210.56 22.2553 209.373 23.9998 207.362 23.9998C205.35 23.9998 204.163 22.2553 204.163 19.5173C204.163 16.7551 205.35 15.0106 207.362 15.0106ZM207.362 16.1493C206.114 16.1493 205.411 17.4215 205.411 19.5173C205.411 21.589 206.114 22.8611 207.362 22.8611C208.609 22.8611 209.312 21.589 209.312 19.5173C209.312 17.4215 208.609 16.1494 207.362 16.1493Z\"\n      />\n      <path d=\"M214.699 15.0106C216.189 15.0106 217.388 16.1131 217.776 17.7728L216.528 17.8577C216.274 16.828 215.571 16.1494 214.711 16.1493C213.378 16.1493 212.651 17.373 212.651 19.5173C212.651 21.6496 213.378 22.8611 214.711 22.8611C215.789 22.861 216.552 21.8556 216.637 20.5351H214.686V19.4203H217.8V23.8061H216.964L216.819 22.6673C216.455 23.5153 215.74 23.9998 214.699 23.9998C212.687 23.9998 211.403 22.2553 211.403 19.5173C211.403 16.7551 212.687 15.0106 214.699 15.0106Z\" />\n      <path d=\"M76.4346 15.0106C78.167 15.0107 79.3784 16.1616 79.5601 17.7728L78.3364 17.8456C78.1911 16.8159 77.4158 16.1494 76.4103 16.1493C75.5865 16.1493 74.9081 16.6461 74.9202 17.4336C74.9323 18.4028 75.9863 18.6814 77.0161 18.948C78.482 19.3599 79.7177 20.1717 79.7177 21.4922C79.7177 23.1034 78.3001 23.9513 76.8221 23.9513C75.017 23.9513 73.6601 22.8005 73.5147 21.0317L74.7383 20.9589C74.9564 22.0735 75.7076 22.8126 76.8948 22.8126C77.8277 22.8126 78.4941 22.4006 78.4941 21.5889C78.4941 20.7652 77.7913 20.2685 76.2891 19.8687C74.9322 19.5174 73.6966 18.8026 73.6966 17.4215C73.6966 15.9434 74.8596 15.0106 76.4346 15.0106Z\" />\n      <path d=\"M127.285 15.0106C129.017 15.0107 130.228 16.1616 130.41 17.7728L129.187 17.8456C129.041 16.8159 128.266 16.1494 127.26 16.1493C126.437 16.1493 125.758 16.6461 125.77 17.4336C125.782 18.4028 126.836 18.6814 127.866 18.948C129.332 19.3599 130.568 20.1717 130.568 21.4922C130.568 23.1034 129.15 23.9513 127.672 23.9513C125.867 23.9513 124.51 22.8005 124.365 21.0317L125.588 20.9589C125.806 22.0735 126.558 22.8126 127.745 22.8126C128.678 22.8126 129.344 22.4006 129.344 21.5889C129.344 20.7652 128.641 20.2685 127.139 19.8687C125.782 19.5174 124.547 18.8026 124.547 17.4215C124.547 15.9434 125.71 15.0106 127.285 15.0106Z\" />\n      <path d=\"M26.6531 23.8061H0L13.3266 0.787657L26.6531 23.8061Z\" />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M47.6318 15.2046C49.5338 15.2047 50.6848 16.2101 50.6848 17.8456C50.6847 19.5174 49.5337 20.5472 47.6318 20.5472H46.1416V23.8061H44.9544V15.2046H47.6318ZM46.1416 19.4085H47.5954C48.8189 19.4085 49.4369 18.8753 49.4369 17.8456C49.4369 16.8522 48.819 16.3434 47.5954 16.3434H46.1416V19.4085Z\"\n      />\n      <path d=\"M57.6944 16.3434H53.4059V18.9358H57.5492V20.0506H53.4059V22.6673H57.7914V23.8061H52.2187V15.2046H57.6944V16.3434Z\" />\n      <path d=\"M63.6262 22.1222V15.2046H64.8135V23.8061H63.1293L60.5247 17.155V23.8061H59.3375V15.2046H60.9125L63.6262 22.1222Z\" />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M98.3727 15.2046C100.178 15.2046 101.317 16.2101 101.317 17.8092C101.317 18.8026 100.675 19.5779 99.9235 19.8202C100.735 19.9777 101.087 20.4139 101.171 21.2982L101.414 23.8061H100.214L99.9963 21.4195C99.9236 20.6683 99.4387 20.4138 98.2636 20.4138H96.7977V23.8061H95.6105V15.2046H98.3727ZM96.7977 19.2872H98.3606C99.4509 19.2872 100.069 18.7542 100.069 17.8214C100.069 16.8764 99.451 16.3434 98.3606 16.3434H96.7977V19.2872Z\"\n      />\n      <path d=\"M115.809 16.3434H111.52V18.9358H115.664V20.0506H111.52V22.6673H115.906V23.8061H110.333V15.2046H115.809V16.3434Z\" />\n      <path d=\"M144.806 16.3434H140.65V19.045H144.587V20.1595H140.65V23.8061H139.451V15.2046H144.806V16.3434Z\" />\n      <path d=\"M152.385 16.3434H149.841V23.8061H148.653V16.3434H146.109V15.2046H152.385V16.3434Z\" />\n      <path d=\"M155.142 22.2675L155.978 15.9314H157.044L157.88 22.2675L158.571 15.2046H159.782L158.91 23.8061H157.214L156.511 17.9908L155.809 23.8061H154.113L153.24 15.2046H154.452L155.142 22.2675Z\" />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M167.132 23.8061H165.908L165.242 21.5165H162.31L161.643 23.8061H160.42L163.037 15.2046H164.515L167.132 23.8061ZM162.613 20.4745H164.939L163.776 16.4643L162.613 20.4745Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M171.016 15.2046C172.821 15.2046 173.96 16.2101 173.96 17.8092C173.96 18.8026 173.318 19.5779 172.567 19.8202C173.378 19.9777 173.73 20.4139 173.814 21.2982L174.057 23.8061H172.857L172.639 21.4195C172.567 20.6684 172.082 20.4138 170.907 20.4138H169.441V23.8061H168.253V15.2046H171.016ZM169.441 19.2872H171.004C172.094 19.2872 172.712 18.7542 172.712 17.8214C172.712 16.8764 172.094 16.3434 171.004 16.3434H169.441V19.2872Z\"\n      />\n      <path d=\"M181.188 16.3434H176.899V18.9358H181.042V20.0506H176.899V22.6673H181.285V23.8061H175.712V15.2046H181.188V16.3434Z\" />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M192.918 15.2046C194.82 15.2047 195.971 16.2101 195.971 17.8456C195.971 19.5174 194.82 20.5472 192.918 20.5472H191.428V23.8061H190.24V15.2046H192.918ZM191.428 19.4085H192.881C194.105 19.4085 194.723 18.8753 194.723 17.8456C194.723 16.8522 194.105 16.3434 192.881 16.3434H191.428V19.4085Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M200.073 15.2046C201.878 15.2046 203.017 16.2101 203.017 17.8092C203.017 18.8026 202.375 19.5779 201.624 19.8202C202.435 19.9777 202.787 20.4139 202.872 21.2982L203.114 23.8061H201.914L201.696 21.4195C201.624 20.6684 201.139 20.4138 199.964 20.4138H198.498V23.8061H197.311V15.2046H200.073ZM198.498 19.2872H200.061C201.151 19.2872 201.769 18.7542 201.769 17.8214C201.769 16.8764 201.151 16.3434 200.061 16.3434H198.498V19.2872Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M221.866 15.2046C223.671 15.2046 224.81 16.2101 224.81 17.8092C224.81 18.8026 224.168 19.5779 223.417 19.8202C224.228 19.9777 224.58 20.4139 224.665 21.2982L224.907 23.8061H223.707L223.489 21.4195C223.417 20.6684 222.932 20.4138 221.757 20.4138H220.291V23.8061H219.104V15.2046H221.866ZM220.291 19.2872H221.854C222.944 19.2872 223.562 18.7542 223.562 17.8214C223.562 16.8764 222.944 16.3434 221.854 16.3434H220.291V19.2872Z\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M232.51 23.8061H231.287L230.62 21.5165H227.689L227.022 23.8061H225.799L228.415 15.2046H229.894L232.51 23.8061ZM227.991 20.4745H230.317L229.154 16.4643L227.991 20.4745Z\"\n      />\n      <path d=\"M236.419 21.1408L237.533 15.2046H239.338V23.8061H238.151V16.6826L236.891 23.2003H235.934L234.686 16.6826V23.8061H233.499V15.2046H235.304L236.419 21.1408Z\" />\n      <path d=\"M62.245 0.472656C63.7836 0.472656 64.8739 1.53883 65.2737 3.25916L64.014 3.34404C63.7232 2.21735 63.093 1.61139 62.2328 1.61139C60.876 1.61146 60.1127 2.8109 60.1127 4.97938C60.1127 7.13578 60.876 8.32305 62.2328 8.32312C63.1778 8.32312 63.8321 7.65672 64.0865 6.42099L65.3344 6.49375C64.9951 8.32311 63.8805 9.46186 62.245 9.46186C60.1007 9.4618 58.8772 7.72943 58.8772 4.97938C58.8772 2.20513 60.1007 0.472716 62.245 0.472656Z\" />\n      <path d=\"M105.831 0.472656C107.369 0.472656 108.46 1.53883 108.86 3.25916L107.6 3.34404C107.309 2.21735 106.679 1.61139 105.819 1.61139C104.462 1.61146 103.699 2.8109 103.699 4.97938C103.699 7.13578 104.462 8.32305 105.819 8.32312C106.764 8.32312 107.418 7.65672 107.672 6.42099L108.92 6.49375C108.581 8.32311 107.466 9.46186 105.831 9.46186C103.686 9.4618 102.463 7.72943 102.463 4.97938C102.463 2.20513 103.686 0.472716 105.831 0.472656Z\" />\n      <path d=\"M40.2826 7.81439L42.3663 0.666685H43.6506L41.0096 9.26813H39.5555L36.9145 0.666685H38.1988L40.2826 7.81439Z\" />\n      <path d=\"M50.4301 1.80542H46.1416V4.39789H50.2849V5.51267H46.1416V8.12939H50.5271V9.26813H44.9544V0.666685H50.4301V1.80542Z\" />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M54.7869 0.666685C56.5921 0.666685 57.7308 1.67211 57.7308 3.27128C57.7308 4.26468 57.0888 5.03995 56.3377 5.28226C57.1494 5.43975 57.5007 5.87593 57.5856 6.76025L57.8278 9.26813H56.6284L56.4104 6.88152C56.3378 6.13039 55.8529 5.87588 54.6778 5.87588H53.2119V9.26813H52.0246V0.666685H54.7869ZM53.2119 4.74927H54.7748C55.8651 4.74927 56.4829 4.21624 56.4829 3.28341C56.4829 2.33844 55.8651 1.80542 54.7748 1.80542H53.2119V4.74927Z\"\n      />\n      <path d=\"M72.223 1.80542H67.9345V4.39789H72.0778V5.51267H67.9345V8.12939H72.32V9.26813H66.7473V0.666685H72.223V1.80542Z\" />\n      <path d=\"M75.2352 8.12939H79.6692V9.26813H74.048V0.666685H75.2352V8.12939Z\" />\n      <path d=\"M93.6648 1.80542H91.7263V8.12939H93.7373V9.26813H88.5402V8.12939H90.5391V1.80542H88.6005V0.666685H93.6648V1.80542Z\" />\n      <path d=\"M99.9477 7.58428V0.666685H101.135V9.26813H99.4508L96.8462 2.61703V9.26813H95.659V0.666685H97.234L99.9477 7.58428Z\" />\n      <path d=\"M113.689 9.26813H112.162V7.79014H113.689V9.26813Z\" />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "packages/docs/src/instrumentation-client.ts",
    "content": "// This file configures the initialization of Sentry on the client.\n// The config you add here will be used whenever a users loads a page in their browser.\n// https://docs.sentry.io/platforms/javascript/guides/nextjs/\n\nimport * as Sentry from '@sentry/nextjs'\n\nconst enabled =\n  process.env.ENABLE_SENTRY === 'true' &&\n  Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN) &&\n  ['production', 'preview'].includes(process.env.VERCEL_ENV ?? '')\n\nSentry.init({\n  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,\n  enabled,\n\n  // Adjust this value in production, or use tracesSampler for greater control\n  tracesSampleRate: 1,\n\n  // Setting this option to true will print useful information to the console while you're setting up Sentry.\n  debug: false,\n\n  replaysOnErrorSampleRate: 1.0,\n\n  // This sets the sample rate to be 10%. You may want this to be 100% while\n  // in development and sample at a lower rate in production\n  replaysSessionSampleRate: 0.01,\n\n  // You can remove this option if you're not planning to use the Sentry Session Replay feature:\n  integrations: [\n    Sentry.replayIntegration({\n      // Additional Replay configuration goes in here, for example:\n      maskAllText: true,\n      blockAllMedia: true\n    })\n  ]\n})\n\nexport const onRouterTransitionStart = Sentry.captureRouterTransitionStart\n"
  },
  {
    "path": "packages/docs/src/instrumentation.ts",
    "content": "import * as Sentry from '@sentry/nextjs'\n\nconst enabled =\n  process.env.ENABLE_SENTRY === 'true' &&\n  Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN) &&\n  Boolean(process.env.SENTRY_AUTH_TOKEN) &&\n  ['production', 'preview'].includes(process.env.VERCEL_ENV ?? '')\n\nexport async function register() {\n  if (!enabled) {\n    return\n  }\n  if (process.env.NEXT_RUNTIME === 'nodejs') {\n    await import('../sentry.server.config')\n  }\n\n  if (process.env.NEXT_RUNTIME === 'edge') {\n    await import('../sentry.edge.config')\n  }\n}\n\nexport const onRequestError = Sentry.captureRequestError\n"
  },
  {
    "path": "packages/docs/src/lib/get-last-modified.ts",
    "content": "import { getGithubLastEdit } from 'fumadocs-core/content/github'\n\nexport async function getLastModified(\n  path: string,\n  branch = 'master'\n): Promise<Date> {\n  try {\n    const lastEdit = await getGithubLastEdit({\n      owner: '47ng',\n      repo: 'nuqs',\n      path: `packages/docs${path}`,\n      sha: branch,\n      token: `Bearer ${process.env.GITHUB_TOKEN}`\n    })\n    return lastEdit ?? new Date()\n  } catch (error) {\n    console.error(`Error fetching last modification date for ${path}:`, error)\n    return new Date()\n  }\n}\n"
  },
  {
    "path": "packages/docs/src/lib/get-llm-text.ts",
    "content": "import type { Page } from '@/src/app/source'\nimport { github } from './utils'\n\nexport async function getLLMText(page: Page) {\n  const processed = await page.data.getText('processed')\n\n  // Collapse 3+ consecutive newlines to 2 (removes extra blank lines from removed content)\n  const normalized = processed\n    .replace(/\\n{3,}/g, '\\n\\n')\n    // Strip Fumadocs inline code syntax highlighting hints (e.g. `code{:ts}` -> `code`)\n    .replace(/`([^`]+)\\{:\\w+\\}`/g, '`$1`')\n\n  return `# ${page.data.title}\n\nURL (HTML): ${page.url}\nURL (LLMs): ${page.url}.md\nSource: https://raw.githubusercontent.com/${github.owner}/${github.repo}/refs/heads/${github.branch}/packages/docs/content/docs/${page.path}\n\n${page.data.description ?? ''}\n\n${normalized}`\n}\n"
  },
  {
    "path": "packages/docs/src/lib/remark-audience.ts",
    "content": "import type { Parents, Root } from 'mdast'\nimport {\n  mdxJsxToMarkdown,\n  type MdxJsxFlowElement,\n  type MdxJsxTextElement\n} from 'mdast-util-mdx-jsx'\nimport type {\n  Info,\n  State,\n  Options as ToMarkdownOptions\n} from 'mdast-util-to-markdown'\nimport type { Processor, Transformer } from 'unified'\n\ntype JsxNode = MdxJsxFlowElement | MdxJsxTextElement\n\n// Get the default MDX JSX handler from mdast-util-mdx-jsx\nconst mdxJsxExtension = mdxJsxToMarkdown()\nconst defaultMdxJsxHandler = mdxJsxExtension.handlers!.mdxJsxFlowElement!\n\n/**\n * Creates a handler for HumanContent/LLMContent JSX elements.\n * - HumanContent: serializes to empty string (hidden from LLMs)\n * - LLMContent: serializes children only (unwrapped, visible to LLMs)\n * - Other elements: delegates to default mdast-util-mdx-jsx handler\n */\nfunction createJsxHandler(isFlow: boolean) {\n  return function handler(\n    node: JsxNode,\n    parent: Parents | undefined,\n    state: State,\n    info: Info\n  ): string {\n    // HumanContent: serialize to empty string (hide from LLMs)\n    if (node.name === 'HumanContent') {\n      return ''\n    }\n\n    // LLMContent: serialize children only (unwrap for LLMs)\n    if (node.name === 'LLMContent') {\n      return isFlow\n        ? state.containerFlow(node as MdxJsxFlowElement, info)\n        : state.containerPhrasing(node, info)\n    }\n\n    // Default: delegate to mdast-util-mdx-jsx handler\n    return defaultMdxJsxHandler(node, parent, state, info)\n  }\n}\n\n/**\n * Remark plugin that handles audience-specific content for LLM output.\n *\n * For processed markdown (used by llms-full.txt and .mdx endpoints):\n * - HumanContent serializes to empty string (hidden from LLMs)\n * - LLMContent serializes children only (unwrapped, visible to LLMs)\n *\n * The React components control visibility in HTML rendering separately.\n *\n * Note: Extra blank lines from empty HumanContent are collapsed in get-llm-text.ts\n */\nexport function remarkAudience(this: Processor): Transformer<Root, Root> {\n  const data = this.data() as {\n    toMarkdownExtensions?: ToMarkdownOptions['extensions']\n  }\n  data.toMarkdownExtensions ??= []\n\n  data.toMarkdownExtensions.push({\n    handlers: {\n      mdxJsxFlowElement: createJsxHandler(true),\n      mdxJsxTextElement: createJsxHandler(false)\n    }\n  })\n\n  // No tree transformation - we only customize serialization handlers\n  // This ensures the React compilation still sees all nodes\n  return (tree: Root) => tree\n}\n"
  },
  {
    "path": "packages/docs/src/lib/typed-links.ts",
    "content": "import type { Route } from 'next'\nimport {\n  createSerializer,\n  type CreateSerializerOptions,\n  type ParserMap\n} from 'nuqs/server'\n\nexport function createTypedLink<Parsers extends ParserMap>(\n  route: Route,\n  parsers: Parsers,\n  options: CreateSerializerOptions<Parsers> = {}\n) {\n  const serialize = createSerializer<Parsers, Route, Route>(parsers, options)\n  return serialize.bind(null, route)\n}\n"
  },
  {
    "path": "packages/docs/src/lib/url.ts",
    "content": "export function getBaseUrl() {\n  return process.env.VERCEL_ENV === 'production'\n    ? 'https://' + process.env.VERCEL_PROJECT_PRODUCTION_URL\n    : process.env.VERCEL_URL\n      ? 'https://' + process.env.VERCEL_URL\n      : ''\n}\n"
  },
  {
    "path": "packages/docs/src/lib/utils.ts",
    "content": "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n\nexport const github = {\n  owner: \"47ng\",\n  repo: \"nuqs\",\n  branch: \"master\",\n}"
  },
  {
    "path": "packages/docs/src/registry/assemble.ts",
    "content": "#! /usr/bin/env node\n\nimport { createHash } from 'node:crypto'\nimport { glob, readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { styleText } from 'node:util'\nimport { z } from 'zod'\nimport {\n  registrySourceItemSchema,\n  type Registry,\n  type RegistrySourceFile,\n  type RegistrySourceItem\n} from './schemas.ts'\n\n// Paths\nconst __dirname = fileURLToPath(new URL('.', import.meta.url))\nconst packageRoot = resolve(__dirname, '../../')\nconst itemsDir = resolve(__dirname, './items')\nconst remoteDir = resolve(__dirname, './remote')\nconst registryJson = resolve(packageRoot, 'registry.json')\n\nasync function loadItems() {\n  const itemFiles = await Array.fromAsync(glob(`${itemsDir}/*.json`))\n  return await Promise.all(\n    itemFiles.map(async filePath => {\n      const item = await loadItem(filePath)\n      console.log(`  ${styleText('green', '✔')} %s`, item.name)\n      return item\n    })\n  )\n}\n\nasync function loadItem(filePath: string) {\n  const contents = await readFile(filePath, 'utf-8')\n  const item = registrySourceItemSchema.parse(JSON.parse(contents))\n  await hydrateItem(item)\n  return item\n}\n\nasync function hydrateItem(item: RegistrySourceItem) {\n  async function hydrateFile(file: RegistrySourceFile) {\n    if (z.url().safeParse(file.path).success === false) {\n      return\n    }\n    const response = await fetch(file.path)\n    if (!response.ok) {\n      throw new Error(\n        `Failed to fetch file at ${file.path}: ${response.statusText}`\n      )\n    }\n    // This is kind of dumb, but we need to save it to a temporary file\n    // so that the `shadcn build` command can do this on the assembled registry.\n    const content = await response.text()\n    const tempFilePath = resolve(\n      remoteDir,\n      `${item.name}.${hash(file.path, file.target, content).slice(0, 12)}.txt`\n    )\n    await writeFile(tempFilePath, content)\n    file.path = tempFilePath.replace(packageRoot + '/', '')\n  }\n  if (!item.docs) {\n    item.docs = `https://nuqs.dev/registry/${item.name}`\n  }\n  return Promise.all(item.files.map(file => hydrateFile(file)))\n}\n\nfunction hash(...contents: string[]) {\n  const hash = createHash('sha256')\n  for (const content of contents) {\n    hash.update(content)\n  }\n  return hash.digest('base64url')\n}\n\nasync function main() {\n  console.log(`${styleText('blue', 'i')} Assembling registry...`)\n  const items = await loadItems()\n  const registry: Registry = {\n    $schema: 'https://ui.shadcn.com/schema/registry.json',\n    name: 'nuqs',\n    homepage: 'https://nuqs.dev',\n    items: items.sort((a, b) => a.name.localeCompare(b.name))\n  }\n  await writeFile(registryJson, JSON.stringify(registry, null, 2), 'utf-8')\n  console.log(\n    `${styleText('green', '✔')} Registry assembled successfully (processed %d items)`,\n    items.length\n  )\n}\n\nawait main()\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-inertia.json",
    "content": "{\n  \"type\": \"registry:item\",\n  \"name\": \"adapter-inertia\",\n  \"title\": \"Inertia.js Adapter\",\n  \"description\": \"Using nuqs in Inertia.js apps (eg: with a Laravel backend)\",\n  \"categories\": [\"adapter\"],\n  \"author\": \"François Best <franky47>\",\n  \"dependencies\": [\"nuqs\"],\n  \"files\": [\n    {\n      \"type\": \"registry:file\",\n      \"path\": \"https://raw.githubusercontent.com/47ng/nuqs-inertia-pingcrm/refs/heads/with-nuqs/resources/js/lib/nuqs-inertia-adapter.ts\",\n      \"target\": \"~/resources/js/lib/nuqs-inertia-adapter.ts\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-inertia.md",
    "content": "[Inertia](https://inertiajs.com/) is supported as a community-contributed adapter.\n\nInstall the adapter using the CLI or copy/paste above,\nthen integrate the adapter into the root layout file,\nby wrapping it around the children of the layout component:\n\n```tsx title=\"resources/js/Layouts/AppLayout.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from '.@/lib/nuqs-inertia-adapter'\nimport { PropsWithChildren } from 'react'\n\nexport default function Layout({ children }: PropsWithChildren) {\n  return <NuqsAdapter>{children}</NuqsAdapter>\n}\n```\n\n## Try it out\n\nCheck out the [demo app](https://github.com/47ng/nuqs-inertia-pingcrm)\nwhich is a fork of Inertia's Ping CRM demo with nuqs.\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-onejs.json",
    "content": "{\n  \"type\": \"registry:item\",\n  \"name\": \"adapter-onejs\",\n  \"title\": \"One.js Adapter\",\n  \"description\": \"Using nuqs in One.js applications.\",\n  \"author\": \"François Best <franky47>\",\n  \"categories\": [\"adapter\"],\n  \"dependencies\": [\"one\", \"nuqs\"],\n  \"files\": [\n    {\n      \"type\": \"registry:file\",\n      \"path\": \"src/registry/items/adapter-onejs.source\",\n      \"target\": \"~/app/nuqs-onejs-adapter.tsx\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-onejs.md",
    "content": "[One.js](https://onestack.dev/) is supported as a community-contributed adapter.\n\nIt's not built-in because it's based on both React web & React Native, and\npulls a lot of dependencies into the nuqs build process (doubling the dependency\ninstall time).\n\nIf it becomes popular and there is sufficient demand, it may be included in the\ncore package.\n\nInstall the adapter using the CLI or copy/paste above,\nthen integrate the adapter into the root layout file,\nby wrapping the `<Slot>` component:\n\n```tsx title=\"app/_layout.tsx\"\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from './nuqs-one-adapter'\nimport { Slot } from 'one'\n\nexport default function Layout() {\n  return (\n    <>\n      {typeof document !== 'undefined' && (\n        <>\n          <meta charSet=\"utf-8\" />\n          <meta httpEquiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n          <meta\n            name=\"viewport\"\n            content=\"width=device-width, initial-scale=1, maximum-scale=5\"\n          />\n          <link rel=\"icon\" href=\"/favicon.svg\" />\n        </>\n      )}\n      <NuqsAdapter>\n        <Slot />\n      </NuqsAdapter>\n    </>\n  )\n}\n```\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-onejs.source",
    "content": "import {\n  type unstable_AdapterOptions as AdapterOptions,\n  unstable_createAdapterProvider as createAdapterProvider,\n  renderQueryString\n} from 'nuqs/adapters/custom'\nimport { useActiveParams, useRouter } from 'one'\n\nfunction useNuqsOneAdapter() {\n  const router = useRouter()\n  const searchParams = new URLSearchParams(useActiveParams() as {})\n  const updateUrl = (search: URLSearchParams, options: AdapterOptions) => {\n    if (options.history === 'push') {\n      router.push(renderQueryString(search), {\n        scroll: options.scroll\n      })\n    } else {\n      router.replace(renderQueryString(search), {\n        scroll: options.scroll\n      })\n    }\n  }\n  return {\n    searchParams,\n    updateUrl\n  }\n}\n\nexport const NuqsAdapter = createAdapterProvider(useNuqsOneAdapter)"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-react-router-v5.json",
    "content": "{\n  \"type\": \"registry:item\",\n  \"name\": \"adapter-react-router-v5\",\n  \"title\": \"React Router v5 Adapter\",\n  \"description\": \"Using nuqs in React Router v5 applications.\",\n  \"author\": \"François Best <franky47>\",\n  \"categories\": [\"adapter\"],\n  \"dependencies\": [\"react-router-dom@^5\", \"nuqs\"],\n  \"files\": [\n    {\n      \"type\": \"registry:file\",\n      \"path\": \"src/registry/items/adapter-react-router-v5.source\",\n      \"target\": \"~/nuqs-adapter.ts\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-react-router-v5.md",
    "content": "Wrap your `<BrowserRouter>` with the `<NuqsAdapter>`:\n\n```tsx\n// [!code word:NuqsAdapter]\nimport { NuqsAdapter } from './nuqs-adapter'\n\nexport function ReactRouter() {\n  return (\n    <NuqsAdapter>\n      <BrowserRouter>\n        <Switch>{/* Your routes here */}</Switch>\n      </BrowserRouter>\n    </NuqsAdapter>\n  )\n}\n```\n\n## Compatibility\n\nThis adapter is compatible with `nuqs@^2.8`: support for `react-router-dom@^5`\nwas extended in `nuqs@2.8.0`, but will likely be removed in `nuqs@3.0.0`.\n\nIf you need support for React Router v5, pin your dependency to `nuqs@^2`.\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-react-router-v5.source",
    "content": "import {\n  type unstable_AdapterInterface as AdapterInterface,\n  unstable_createAdapterProvider as createAdapterProvider,\n  renderQueryString,\n  type unstable_UpdateUrlFunction as UpdateUrlFunction\n} from 'nuqs/adapters/custom'\nimport { useCallback, useMemo } from 'react'\nimport { useHistory, useLocation } from 'react-router-dom'\n\nfunction useNuqsReactRouterV5Adapter(): AdapterInterface {\n  const history = useHistory()\n  const location = useLocation()\n  const searchParams = useMemo(() => {\n    return new URLSearchParams(location.search)\n  }, [location.search])\n\n  const updateUrl = useCallback<UpdateUrlFunction>(\n    (search, options) => {\n      const queryString = renderQueryString(search)\n      if (options.history === 'push') {\n        history.push({\n          search: queryString,\n          hash: window.location.hash\n        })\n      } else {\n        history.replace({\n          search: queryString,\n          hash: window.location.hash\n        })\n      }\n      if (options.scroll) {\n        window.scrollTo(0, 0)\n      }\n    },\n    [history.push, history.replace]\n  )\n  return {\n    searchParams,\n    updateUrl\n  }\n}\n\nexport const NuqsAdapter = createAdapterProvider(useNuqsReactRouterV5Adapter)\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-waku.json",
    "content": "{\n  \"type\": \"registry:item\",\n  \"name\": \"adapter-waku\",\n  \"title\": \"Waku Adapter\",\n  \"description\": \"Using nuqs in ⛩️ Waku applications.\",\n  \"author\": \"François Best <franky47>\",\n  \"categories\": [\"adapter\"],\n  \"dependencies\": [\"waku@>=0.22.0\", \"nuqs\"],\n  \"files\": [\n    {\n      \"type\": \"registry:file\",\n      \"path\": \"https://gist.githubusercontent.com/franky47/57d6b9d1ffc18faa81c97ecf2a46e274/raw/852f8a453135481896c0139fb2e27b6c600ddfbb/nuqs-waku-adapter.ts\",\n      \"target\": \"~/app/nuqs-waku-adapter.tsx\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/docs/src/registry/items/adapter-waku.md",
    "content": "[Waku](https://waku.gg/) is supported as a community-contributed adapter.\n\nInstall the adapter using the CLI or copy/paste above,\nthen integrate the adapter into a \\_layout.tsx or \\_root.tsx file,\nby wrapping the `{children}` component:\n\n```tsx title=\"app/_layout.tsx\"\n// [!code word:NuqsAdapter]\nimport { Suspense, type ReactNode } from 'react'\n\nimport { NuqsAdapter } from './nuqs-waku-adapter'\n\ntype LayoutProps = { children: ReactNode }\n\nexport default async function Layout({ children }: LayoutProps) {\n  return (\n    <>\n      <NuqsAdapter>\n        <Suspense>{children}</Suspense>\n      </NuqsAdapter>\n    </>\n  )\n}\n\nexport const getConfig = async () => {\n  return {\n    render: 'dynamic'\n    // render: 'static', // works but can cause hydration warnings\n  } as const\n}\n```\n"
  },
  {
    "path": "packages/docs/src/registry/items/next-typed-links.json",
    "content": "{\n  \"type\": \"registry:item\",\n  \"name\": \"next-typed-links\",\n  \"title\": \"Typed Links for Next.js\",\n  \"description\": \"Type-safe linking for Next.js (using typedRoutes + nuqs)\",\n  \"author\": \"François Best <franky47>\",\n  \"categories\": [\"utility\"],\n  \"dependencies\": [\"next@>=15.5.0\", \"nuqs\"],\n  \"files\": [\n    {\n      \"type\": \"registry:file\",\n      \"path\": \"src/lib/typed-links.ts\",\n      \"target\": \"~/src/lib/typed-links.ts\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/docs/src/registry/items/next-typed-links.md",
    "content": "This utility connects your `typedRoutes` type-safe pathnames\nto a search params descriptor object, and gives you back a function\nyou can call to generate a fully type-safe (pathname + search params) href\nfor linking & routing:\n\n```ts title=\"src/app/map/search-params.ts\"\nimport { createTypedLink } from '@/src/lib/typed-links'\nimport { parseAsFloat, type UrlKeys } from 'nuqs/server'\n\nconst coordinates = {\n  latitude: parseAsFloat.withDefault(0),\n  longitude: parseAsFloat.withDefault(0)\n}\n// Optional remapping for shorter keys\nconst urlKeys: UrlKeys<typeof coordinates> = {\n  latitude: 'lat',\n  longitude: 'lng'\n}\n\n// [!code word:createTypedLink]\nexport const getMapLink = createTypedLink(\n  '/map', // The values here are inferred from your app's routes\n  coordinates,\n  { urlKeys }\n)\n\n// Usage:\ngetMapLink({ latitude: 12.34, longitude: 56.78 })\n// \"/map?lat=12.34&lng=56.78\"\n```\n"
  },
  {
    "path": "packages/docs/src/registry/read.ts",
    "content": "import {\n  type ItemCategory,\n  type Registry,\n  registryBuiltItemSchema,\n  registrySchema,\n  type RegistrySourceItem\n} from '@/src/registry/schemas'\nimport { readFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\n\nexport async function readRegistry() {\n  try {\n    const fileName = resolve(process.cwd(), 'public/r/registry.json')\n    const fileContents = await readFile(fileName, 'utf-8')\n    return [registrySchema.parse(JSON.parse(fileContents)), null] as const\n  } catch (error) {\n    return [null, error] as const\n  }\n}\n\nexport async function readRegistryItem(name: string) {\n  try {\n    const fileName = resolve(process.cwd(), `public/r/${name}.json`)\n    const fileContents = await readFile(fileName, 'utf-8')\n    return [\n      registryBuiltItemSchema.parse(JSON.parse(fileContents)),\n      null\n    ] as const\n  } catch (error) {\n    return [null, error] as const\n  }\n}\n\nexport async function readUsage(name: string) {\n  try {\n    const fileName = resolve(process.cwd(), `src/registry/items/${name}.md`)\n    return await readFile(fileName, 'utf-8')\n  } catch {\n    return null\n  }\n}\n\nexport function categorizeRegistryItems(registry: Registry) {\n  const categories: Record<ItemCategory, RegistrySourceItem[]> = {\n    adapter: [],\n    parser: [],\n    utility: []\n  }\n  for (const item of registry.items) {\n    if (!item.categories || item.categories.length === 0) {\n      categories.utility.push(item)\n      continue\n    }\n    if (item.categories.includes('adapter')) {\n      categories.adapter.push(item)\n    } else if (item.categories.includes('parser')) {\n      categories.parser.push(item)\n    } else {\n      categories.utility.push(item)\n    }\n  }\n  return categories\n}\n"
  },
  {
    "path": "packages/docs/src/registry/remote/.gitignore",
    "content": "*\n!.gitignore"
  },
  {
    "path": "packages/docs/src/registry/schemas.ts",
    "content": "import { z } from 'zod'\n\n// Shared schemas --\n\n// Format: \"François Best <franky47>\" (github username is required)\nexport const authorRegex = /^(.*?) <([^>]*)>$/\n\nexport const itemCategories = ['adapter', 'parser', 'utility'] as const\nexport type ItemCategory = (typeof itemCategories)[number]\n\nconst registryBaseItemSchema = z.object({\n  type: z.literal('registry:item'),\n  name: z.string(),\n  title: z.string(),\n  description: z.string().optional(),\n  dependencies: z.array(z.string()),\n  categories: z.array(z.enum(itemCategories)).optional(),\n  author: z.string().regex(authorRegex).optional(),\n  docs: z.url().optional()\n})\n\n// Source schemas --\n\nexport type RegistrySourceFile = z.infer<typeof registrySourceFileSchema>\nconst registrySourceFileSchema = z.object({\n  type: z.literal('registry:file'),\n  path: z.string(),\n  target: z.string()\n})\n\nexport type RegistrySourceItem = z.infer<typeof registrySourceItemSchema>\nexport const registrySourceItemSchema = registryBaseItemSchema.extend({\n  files: z.array(registrySourceFileSchema)\n})\n\n// Shape of the root registry.json file\nexport type Registry = z.infer<typeof registrySchema>\nexport const registrySchema = z.object({\n  $schema: z.url(),\n  name: z.string(),\n  homepage: z.url(),\n  items: z.array(registrySourceItemSchema)\n})\n\n// Built schemas --\n\nexport type RegistryBuiltFile = z.infer<typeof registryBuiltFileSchema>\nconst registryBuiltFileSchema = registrySourceFileSchema.extend({\n  content: z.string()\n})\n\nexport type RegistryBuiltItem = z.infer<typeof registryBuiltItemSchema>\nexport const registryBuiltItemSchema = registryBaseItemSchema.extend({\n  files: z.array(registryBuiltFileSchema),\n  docs: z.string().optional()\n})\n"
  },
  {
    "path": "packages/docs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"allowImportingTsExtensions\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    },\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ]\n  },\n  \"mdx\": {\n    // Enable strict type checking in MDX files.\n    \"checkMdx\": true\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.mdx\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\", \".next\", \"./src/registry/items/*.{ts,tsx}\"]\n}\n"
  },
  {
    "path": "packages/docs/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\".next/**\", \"!.next/cache/**\"],\n      \"dependsOn\": [\"^build\", \"nuqs#build:size-json\"],\n      \"env\": [\n        \"VERCEL_ENV\",\n        \"VERCEL_URL\",\n        \"VERCEL_PROJECT_PRODUCTION_URL\",\n        \"ENABLE_SENTRY\",\n        \"SENTRY_ORG\",\n        \"SENTRY_PROJECT\",\n        \"SENTRY_AUTH_TOKEN\",\n        \"NEXT_PUBLIC_SENTRY_DSN\",\n        \"GITHUB_TOKEN\",\n        \"ISR_TOKEN\"\n      ]\n    },\n    \"test\": {\n      \"dependsOn\": [\"^build\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/.gitignore",
    "content": ".playwright/"
  },
  {
    "path": "packages/e2e/next/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n/// <reference types=\"next/navigation-types/compat/navigation\" />\nimport \"./.next/types/routes.d.ts\";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "packages/e2e/next/next.config.mjs",
    "content": "// @ts-check\n\nconst basePath =\n  process.env.BASE_PATH === '/' ? undefined : process.env.BASE_PATH\n\nconst enableCacheComponents =\n  process.env.CACHE_COMPONENTS === 'true'\n    ? {\n        cacheComponents: true\n      }\n    : {}\n\n/** @type {import('next').NextConfig } */\nconst config = {\n  basePath,\n  productionBrowserSourceMaps: true,\n  ...enableCacheComponents,\n  reactCompiler: process.env.REACT_COMPILER === 'true',\n  experimental: {\n    clientRouterFilter: false,\n    serverSourceMaps: true\n  },\n  transpilePackages: ['e2e-shared'],\n  rewrites: async () => [\n    {\n      source: '/app/rewrites/source',\n      destination: '/app/rewrites/destination?injected=by+rewrites'\n    },\n    {\n      source: '/app/rewrites/source/match-query',\n      destination: '/app/rewrites/destination?injected=disallowed',\n      has: [{ type: 'query', key: 'injected', value: 'blocked' }]\n    }\n  ]\n}\n\nconsole.info(`Next.js config:\n  basePath:        ${config.basePath}\n  reactCompiler:   ${config.reactCompiler}\n  cacheComponents: ${config.cacheComponents}\n`)\n\nexport default config\n"
  },
  {
    "path": "packages/e2e/next/package.json",
    "content": "{\n  \"name\": \"e2e-next\",\n  \"version\": \"0.0.0-internal\",\n  \"description\": \"End-to-end test bench for nuqs\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"author\": {\n    \"name\": \"François Best\",\n    \"email\": \"contact@francoisbest.com\",\n    \"url\": \"https://francoisbest.com\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"next dev --port 3001\",\n    \"build\": \"next build\",\n    \"start\": \"NODE_OPTIONS='--enable-source-maps=true' next start --port 3001\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"playwright test --project=chromium\",\n    \"cacheComponents:codemod\": \"node scripts/cache-components-codemod.ts\"\n  },\n  \"dependencies\": {\n    \"next\": \"catalog:next\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@types/semver\": \"^7.7.1\",\n    \"@types/webpack\": \"^5.28.5\",\n    \"babel-plugin-react-compiler\": \"1.0.0\",\n    \"e2e-shared\": \"workspace:*\",\n    \"semver\": \"^7.7.3\",\n    \"typescript\": \"^5.9.3\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'next start',\n  port: 3001,\n  basePath: process.env.BASE_PATH || '/'\n})\n"
  },
  {
    "path": "packages/e2e/next/scripts/cache-components-codemod.ts",
    "content": "import { exec } from 'node:child_process'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve as resolvePath } from 'node:path'\n\nfunction grepFilesWithDynamicExport(dir: string): Promise<string[]> {\n  return new Promise((resolve, reject) => {\n    exec(\n      'grep -rl \"export const dynamic = \" .',\n      { cwd: dir },\n      (error, stdout, stderr) => {\n        if (error) {\n          reject(new Error(`Error executing grep: ${error.message}`))\n          return\n        }\n        if (stderr) {\n          reject(new Error(`Grep stderr: ${stderr}`))\n          return\n        }\n\n        const files = stdout\n          .split('\\n')\n          .filter(Boolean)\n          .map(file => resolvePath(dir, file))\n        resolve(files)\n      }\n    )\n  })\n}\n\nasync function commentDynamicExportInFile(filePath: string) {\n  try {\n    const content = await readFile(filePath, 'utf-8')\n    const modifiedContent = content.replace(\n      /export const dynamic = /g,\n      '// export const dynamic = '\n    )\n    await writeFile(filePath, modifiedContent, 'utf-8')\n    console.info(`Commented dynamic export in: ${filePath}`)\n  } catch (error) {\n    throw new Error(\n      `Error commenting line in ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n    )\n  }\n}\n\nasync function main() {\n  try {\n    const sourceDir = resolvePath(import.meta.dirname, '../src')\n    const files = await grepFilesWithDynamicExport(sourceDir)\n    console.table(files)\n    for (const file of files) {\n      await commentDynamicExportInFile(file)\n    }\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nmain()\n"
  },
  {
    "path": "packages/e2e/next/specs/cache.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest.describe('cache', () => {\n  test('works in app router', async ({ page }) => {\n    await navigateTo(\n      page,\n      '/app/cache',\n      '?str=foo&num=42&idx=1&bool=true&multi=foo&multi=bar'\n    )\n    await expect(page.locator('#parse-str')).toHaveText('foo')\n    await expect(page.locator('#parse-num')).toHaveText('42')\n    await expect(page.locator('#parse-idx')).toHaveText('0')\n    await expect(page.locator('#parse-bool')).toHaveText('true')\n    await expect(page.locator('#parse-def')).toHaveText('default')\n    await expect(page.locator('#parse-nope')).toHaveText('null')\n    await expect(page.locator('#all-str')).toHaveText('foo')\n    await expect(page.locator('#all-num')).toHaveText('42')\n    await expect(page.locator('#all-idx')).toHaveText('0')\n    await expect(page.locator('#all-bool')).toHaveText('true')\n    await expect(page.locator('#all-def')).toHaveText('default')\n    await expect(page.locator('#all-nope')).toHaveText('null')\n    await expect(page.locator('#get-str')).toHaveText('foo')\n    await expect(page.locator('#get-num')).toHaveText('42')\n    await expect(page.locator('#get-idx')).toHaveText('0')\n    await expect(page.locator('#get-bool')).toHaveText('true')\n    await expect(page.locator('#get-def')).toHaveText('default')\n    await expect(page.locator('#get-nope')).toHaveText('null')\n    await expect(page).toHaveTitle('metadata-title-str:foo')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/deferred.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('Deferred updates', async ({ page }) => {\n  await navigateTo(page, '/app/deferred', '?a=init+a&b=init+b')\n  await expect(page.locator('#input-a')).toHaveValue('init a')\n  await expect(page.locator('#input-b')).toHaveValue('init b')\n  await expect(page.locator('#state-a')).toHaveText('init a')\n  await expect(page.locator('#state-b')).toHaveText('init b')\n\n  await page.locator('#input-a').clear()\n  await page.locator('#input-a').fill('a')\n  await page.locator('#input-b').clear()\n  await page.locator('#input-b').fill('b')\n\n  await expect(page.locator('#input-a')).toHaveValue('a')\n  await expect(page.locator('#input-b')).toHaveValue('b')\n  await expect(page.locator('#state-a')).toHaveText('a')\n  await expect(page.locator('#state-b')).toHaveText('b')\n  await expect(page).toHaveURL(url => url.search === '?a=init+a&b=init+b')\n\n  await page.locator('button').click()\n  await expect(page).toHaveURL(url => url.search === '?a=a&b=b')\n  await expect(page.locator('#input-a')).toHaveValue('a')\n  await expect(page.locator('#input-b')).toHaveValue('b')\n  await expect(page.locator('#state-a')).toHaveText('a')\n  await expect(page.locator('#state-b')).toHaveText('b')\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/multitenant.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\nimport { createSerializer, parseAsBoolean, parseAsStringLiteral } from 'nuqs'\n\nconst getUrl = createSerializer({\n  shallow: parseAsBoolean.withDefault(true),\n  history: parseAsStringLiteral(['replace', 'push']).withDefault('replace')\n})\n\ntype Config = {\n  path: string\n  router: 'next-app' | 'next-pages'\n  expectedPathname: string\n}\n\nfunction testMultitenant(config: Config) {\n  const shallows = [true, false]\n  const histories = ['replace', 'push'] as const\n\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      test.describe(`Multitenant - ${config.router} - shallow: ${shallow}, history: ${history}`, () => {\n        test(`Updates with ({ shallow: ${shallow}, history: ${history} })`, async ({\n          page\n        }) => {\n          await navigateTo(page, getUrl(config.path, { shallow, history }))\n          await expect(page.locator('#client-state')).toBeEmpty()\n          await expect(page.locator('#server-state')).toBeEmpty()\n          await expect(page.locator('#client-tenant')).toHaveText('david')\n          await expect(page.locator('#server-tenant')).toHaveText('david')\n          await expect(page.locator('#router-pathname')).toHaveText(\n            config.expectedPathname\n          )\n          await page.locator('button').click()\n          await expect(page.locator('#client-state')).toHaveText('pass')\n          await expect(page.locator('#client-tenant')).toHaveText('david')\n          await expect(page.locator('#server-tenant')).toHaveText('david')\n          await expect(page.locator('#router-pathname')).toHaveText(\n            config.expectedPathname\n          )\n          if (shallow === false) {\n            await expect(page.locator('#server-state')).toHaveText('pass')\n          } else {\n            await expect(page.locator('#server-state')).toBeEmpty()\n          }\n          if (history !== 'push') {\n            return\n          }\n          await page.goBack()\n          await expect(page.locator('#client-tenant')).toHaveText('david')\n          await expect(page.locator('#server-tenant')).toHaveText('david')\n          await expect(page.locator('#client-state')).toBeEmpty()\n          await expect(page.locator('#server-state')).toBeEmpty()\n          await expect(page.locator('#router-pathname')).toHaveText(\n            config.expectedPathname\n          )\n        })\n      })\n    }\n  }\n}\n\ntestMultitenant({\n  path: '/app/multitenant',\n  router: 'next-app',\n  expectedPathname: '/app/multitenant'\n})\n\ntestMultitenant({\n  path: '/pages/multitenant',\n  router: 'next-pages',\n  expectedPathname: '/pages/multitenant/[tenant]'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/persist-across-navigation.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('Persists search params across navigation using a generated Link href', async ({\n  page\n}) => {\n  await navigateTo(page, '/app/persist-across-navigation/a')\n  await page.getByRole('textbox').fill('foo')\n  await page.getByRole('checkbox').check()\n  await page.locator('a').click()\n  await expect(page).toHaveURL(/\\/app\\/persist-across-navigation\\/b/)\n  await expect(page).toHaveURL(url => url.search === '?q=foo&checked=true')\n  await expect(page.getByRole('textbox')).toHaveValue('foo')\n  await expect(page.getByRole('checkbox')).toBeChecked()\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/push.spec.ts",
    "content": "import { expect, test, type Page } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\nasync function runPushTest(page: Page) {\n  await expect(page.locator('#server-side')).toHaveText('0')\n  await expect(page.locator('#server')).toHaveText('0')\n  await expect(page.locator('#client')).toHaveText('0')\n\n  await page.locator('button#server-incr').click()\n  await expect(page).toHaveURL(url => url.search === '?server=1')\n  await expect(page.locator('#server-side')).toHaveText('1')\n  await expect(page.locator('#server')).toHaveText('1')\n  await expect(page.locator('#client')).toHaveText('0')\n\n  await page.locator('button#client-incr').click()\n  await expect(page).toHaveURL(url => url.search === '?server=1&client=1')\n  await expect(page.locator('#server-side')).toHaveText('1')\n  await expect(page.locator('#server')).toHaveText('1')\n  await expect(page.locator('#client')).toHaveText('1')\n\n  await page.goBack()\n  await expect(page).toHaveURL(url => url.search === '?server=1')\n  await expect(page.locator('#server-side')).toHaveText('1')\n  await expect(page.locator('#server')).toHaveText('1')\n  await expect(page.locator('#client')).toHaveText('0')\n\n  await page.goBack()\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#server-side')).toHaveText('0')\n  await expect(page.locator('#server')).toHaveText('0')\n  await expect(page.locator('#client')).toHaveText('0')\n\n  await page.goForward()\n  await expect(page).toHaveURL(url => url.search === '?server=1')\n  await expect(page.locator('#server-side')).toHaveText('1')\n  await expect(page.locator('#server')).toHaveText('1')\n  await expect(page.locator('#client')).toHaveText('0')\n\n  await page.goForward()\n  await expect(page).toHaveURL(url => url.search === '?server=1&client=1')\n  await expect(page.locator('#server-side')).toHaveText('1')\n  await expect(page.locator('#server')).toHaveText('1')\n  await expect(page.locator('#client')).toHaveText('1')\n}\n\ntest.describe('push', () => {\n  test('works in app router', async ({ page }) => {\n    await navigateTo(page, '/app/push')\n    await runPushTest(page)\n  })\n\n  test('works in pages router', async ({ page }) => {\n    await navigateTo(page, '/pages/push')\n    await runPushTest(page)\n  })\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/referential-equality.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('Referential equality', async ({ page }) => {\n  await navigateTo(page, '/app/referential-equality')\n  await expect(page.locator('#ref-a')).toHaveText('1')\n  await expect(page.locator('#ref-b')).toHaveText('1')\n\n  await page.locator('#increment-a').click()\n  await expect(page.locator('#ref-a')).toHaveText('2')\n  await expect(page.locator('#ref-b')).toHaveText('1')\n\n  await page.locator('#increment-b').click()\n  await expect(page.locator('#ref-a')).toHaveText('2')\n  await expect(page.locator('#ref-b')).toHaveText('2')\n\n  await page.locator('#idempotent-a').click()\n  await expect(page.locator('#ref-a')).toHaveText('2')\n  await expect(page.locator('#ref-b')).toHaveText('2')\n\n  await page.locator('#idempotent-b').click()\n  await expect(page.locator('#ref-a')).toHaveText('2')\n  await expect(page.locator('#ref-b')).toHaveText('2')\n\n  await page.locator('#clear-a').click()\n  await expect(page.locator('#ref-a')).toHaveText('3')\n  await expect(page.locator('#ref-b')).toHaveText('2')\n\n  await page.locator('#clear-b').click()\n  await expect(page.locator('#ref-a')).toHaveText('3')\n  await expect(page.locator('#ref-b')).toHaveText('3')\n\n  await page.locator('#link').click()\n  await expect(page.locator('#ref-a')).toHaveText('3')\n  await expect(page.locator('#ref-b')).toHaveText('3')\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/remapped-keys.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('Remapped keys', async ({ page }) => {\n  await navigateTo(page, '/app/remapped-keys')\n  await page.locator('#search').fill('a')\n  await page.locator('#page').clear()\n  await page.locator('#page').fill('42')\n  await page.locator('#react').check()\n  await page.locator('#nextjs').check()\n  await expect(page).toHaveURL(url => url.search === '?q=a&page=42&tags=react,next.js')\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/repros.spec.ts",
    "content": "import { expect, test, type Page } from '@playwright/test'\nimport { expectSearch, expectUrl } from 'e2e-shared/playwright/expect-url.ts'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('Reproduction for issue #388', async ({ page }) => {\n  await navigateTo(page, '/app/repro-388')\n\n  await page.locator('#start').click()\n  // The URL should have a ?counter=1 query string\n  await expect(page).toHaveURL(url => url.search === '?counter=1')\n  // The counter should be rendered as 1 on the page\n  await expect(page.locator('#counter')).toHaveText('Counter: 1')\n  // Hover the \"Hover me\" link\n  await page.locator('#hover-me').hover()\n  await page.waitForTimeout(100)\n  // The URL should have a ?counter=1 query string\n  await expect(page).toHaveURL(url => url.search === '?counter=1')\n  // The counter should be rendered as 1 on the page\n  await expect(page.locator('#counter')).toHaveText('Counter: 1')\n\n  // Reset the page\n  await navigateTo(page, '/app/repro-388')\n  await page.locator('#start').click()\n  // The URL should have a ?counter=1 query string\n  await expect(page).toHaveURL(url => url.search === '?counter=1')\n  // The counter should be rendered as 1 on the page\n  await expect(page.locator('#counter')).toHaveText('Counter: 1')\n  // Mount the other link\n  await page.locator('#toggle').click()\n  await page.waitForTimeout(100)\n  // The URL should have a ?counter=1 query string\n  await expect(page).toHaveURL(url => url.search === '?counter=1')\n  // The counter should be rendered as 1 on the page\n  await expect(page.locator('#counter')).toHaveText('Counter: 1')\n})\n\ntest('Reproduction for issue #498', async ({ page }) => {\n  await navigateTo(page, '/app/repro-498')\n  await page.locator('#start').click()\n  await expect(page).toHaveURL(url => url.hash === '#section')\n  await page.locator('button').click()\n  await expect(page).toHaveURL(url => url.search === '?q=test')\n  await expect(page).toHaveURL(url => url.hash === '#section')\n})\n\ntest('Reproduction for issue #542', async ({ page }) => {\n  await navigateTo(page, '/app/repro-542/a', '?q=foo&r=bar')\n  await expect(page.locator('#q').first()).toHaveText('foo')\n  await expect(page.locator('#r').first()).toHaveText('bar')\n  await expect(page.locator('#initial').first()).toHaveText(\n    '{\"q\":\"foo\",\"r\":\"bar\"}'\n  )\n  await page.locator('a').click()\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#q').first()).toHaveText('')\n  await expect(page.locator('#r').first()).toHaveText('')\n  await expect(page.locator('#initial').first()).toHaveText(\n    '{\"q\":null,\"r\":null}'\n  )\n})\n\ntest.describe('Reproduction for issue #630', () => {\n  test('works with useQueryState', async ({ page }) => {\n    await runTest(page, '1')\n  })\n  test('works with useQueryStates', async ({ page }) => {\n    await runTest(page, '3')\n  })\n\n  async function runTest(page: Page, sectionToTry: string) {\n    await navigateTo(page, '/app/repro-630')\n    await expect(page.getByTestId('1-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('2-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('3-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('4-pre')).toHaveText('{\"a\":null,\"b\":null}')\n\n    await page.getByTestId(`${sectionToTry}-set`).click()\n    await expect(page.getByTestId('1-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expect(page.getByTestId('2-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expect(page.getByTestId('3-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expect(page.getByTestId('4-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expectSearch(page, { a: '1', b: '2' })\n\n    await page.getByTestId(`${sectionToTry}-clear`).click()\n    await expect(page.getByTestId('1-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('2-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('3-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('4-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expectUrl(page, url => url.search === '')\n\n    await page.goBack()\n    await expect(page.getByTestId('1-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expect(page.getByTestId('2-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expect(page.getByTestId('3-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expect(page.getByTestId('4-pre')).toHaveText('{\"a\":\"1\",\"b\":\"2\"}')\n    await expectSearch(page, { a: '1', b: '2' })\n\n    await page.goBack()\n    await expect(page.getByTestId('1-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('2-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('3-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expect(page.getByTestId('4-pre')).toHaveText('{\"a\":null,\"b\":null}')\n    await expectUrl(page, url => url.search === '')\n  }\n})\n\ntest.describe('repro-758', () => {\n  test('honors urlKeys when navigating back after a push', async ({ page }) => {\n    await navigateTo(page, '/app/repro-758', '?q=init')\n    await page.locator('button').click()\n    await expect(page.locator('#state')).toHaveText('test')\n    await expectSearch(page, { q: 'test' })\n    await page.goBack()\n    await expect(page.locator('#state')).toHaveText('init')\n    await expectUrl(page, url => url.search === '?q=init')\n  })\n})\n\ntest.describe('repro-760', () => {\n  test('supports dynamic default values', async ({ page }) => {\n    await navigateTo(page, '/app/repro-760')\n    await expect(page.locator('#value-a')).toHaveText('a')\n    await expect(page.locator('#value-b')).toHaveText('b')\n    await page.locator('#trigger-a').click()\n    await page.locator('#trigger-b').click()\n    await expect(page.locator('#value-a')).toHaveText('pass')\n    await expect(page.locator('#value-b')).toHaveText('pass')\n  })\n})\n\ntest.describe('repro-774', () => {\n  test('updates internal state on navigation', async ({ page }) => {\n    await navigateTo(page, '/app/repro-774')\n    await page.locator('#trigger-a').click()\n    await expect(page.locator('#value-a')).toHaveText('a')\n    await expect(page.locator('#value-b')).toBeEmpty()\n    await page.locator('#link').click()\n    await expect(page.locator('#value-a')).toBeEmpty()\n    await expect(page.locator('#value-b')).toBeEmpty()\n    await page.locator('#trigger-b').click()\n    await expect(page.locator('#value-a')).toBeEmpty()\n    await expect(page.locator('#value-b')).toHaveText('b')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/rewrites.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('Supports rewrites (server-side only)', async ({ page }) => {\n  await navigateTo(page, '/app/rewrites/source', '?through=original')\n  await expect(page.locator('#injected-server')).toHaveText('by rewrites')\n  await expect(page.locator('#injected-client')).toHaveText('null')\n  await expect(page.locator('#through-server')).toHaveText('original')\n  await expect(page.locator('#through-client')).toHaveText('original')\n\n  await navigateTo(page, '/app/rewrites/source', '?injected=original')\n  await expect(page.locator('#injected-server')).toHaveText('by rewrites')\n  await expect(page.locator('#injected-client')).toHaveText('original')\n\n  await navigateTo(page, '/app/rewrites/source/match-query', '?injected=blocked')\n  await expect(page.locator('#injected-server')).toHaveText('disallowed')\n  await expect(page.locator('#injected-client')).toHaveText('blocked')\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/routing-tour.spec.ts",
    "content": "import { expect, test, type Page } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\nasync function waitForHydration(page: Page) {\n  await page.locator('#hydration-marker').waitFor({ state: 'hidden' })\n  await page.waitForTimeout(50)\n}\n\nfunction clickNext(page: Page) {\n  return page\n    .getByRole('link', { name: 'Next', exact: true, includeHidden: false })\n    .filter({ visible: true, hasText: 'Next' })\n    .first()\n    .click()\n}\n\nfunction withId(page: Page, id: string) {\n  return page.locator(`#${id}`).filter({ visible: true })\n}\n\ntest.describe('routing-tour', () => {\n  test('server -> a', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/server')\n\n    await page.locator('a').filter({ hasText: 'a (server, prefetch)' }).click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.server')\n    await expect(withId(page, 'from')).toHaveText('start.server')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=1')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=2')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=3')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=4')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('server -> b', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/server')\n\n    await page\n      .locator('a')\n      .filter({ hasText: 'b (server, no prefetch)' })\n      .click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.server')\n    await expect(withId(page, 'from')).toHaveText('start.server')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=1')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=2')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=3')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=4')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('server -> c', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/server')\n\n    await page.locator('a').filter({ hasText: 'c (client, prefetch)' }).click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.server')\n    await expect(withId(page, 'from')).toHaveText('start.server')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=1')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=2')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=3')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=4')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('server -> d', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/server')\n\n    await page\n      .locator('a')\n      .filter({ hasText: 'd (client, no prefetch)' })\n      .click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.server')\n    await expect(withId(page, 'from')).toHaveText('start.server')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=1')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=2')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=3')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=4')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('client -> a', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/client')\n\n    await page.locator('a').filter({ hasText: 'a (server, prefetch)' }).click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.client')\n    await expect(withId(page, 'from')).toHaveText('start.client')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=1')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=2')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=3')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=4')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('client -> b', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/client')\n\n    await page\n      .locator('a')\n      .filter({ hasText: 'b (server, no prefetch)' })\n      .click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.client')\n    await expect(withId(page, 'from')).toHaveText('start.client')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=1')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=2')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=3')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=4')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('client -> c', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/client')\n\n    await page.locator('a').filter({ hasText: 'c (client, prefetch)' }).click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.client')\n    await expect(withId(page, 'from')).toHaveText('start.client')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=1')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=2')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=3')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=4')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n\n  test('client -> d', async ({ page }) => {\n    await navigateTo(page, '/app/routing-tour/start/client')\n\n    await page\n      .locator('a')\n      .filter({ hasText: 'd (client, no prefetch)' })\n      .click()\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=start.client')\n    await expect(withId(page, 'from')).toHaveText('start.client')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('0')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/a/)\n    await expect(page).toHaveURL(url => url.search === '?from=d&counter=1')\n    await expect(withId(page, 'from')).toHaveText('d')\n    await expect(withId(page, 'this')).toHaveText('a')\n    await expect(withId(page, 'next')).toHaveText('b')\n    await expect(withId(page, 'counter')).toHaveText('1')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/b/)\n    await expect(page).toHaveURL(url => url.search === '?from=a&counter=2')\n    await expect(withId(page, 'from')).toHaveText('a')\n    await expect(withId(page, 'this')).toHaveText('b')\n    await expect(withId(page, 'next')).toHaveText('c')\n    await expect(withId(page, 'counter')).toHaveText('2')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/c/)\n    await expect(page).toHaveURL(url => url.search === '?from=b&counter=3')\n    await expect(withId(page, 'from')).toHaveText('b')\n    await expect(withId(page, 'this')).toHaveText('c')\n    await expect(withId(page, 'next')).toHaveText('d')\n    await expect(withId(page, 'counter')).toHaveText('3')\n\n    await clickNext(page)\n    await waitForHydration(page)\n    await expect(page).toHaveURL(/\\/app\\/routing-tour\\/d/)\n    await expect(page).toHaveURL(url => url.search === '?from=c&counter=4')\n    await expect(withId(page, 'from')).toHaveText('c')\n    await expect(withId(page, 'this')).toHaveText('d')\n    await expect(withId(page, 'next')).toHaveText('a')\n    await expect(withId(page, 'counter')).toHaveText('4')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/basic-io-agnostic.spec.ts",
    "content": "import { testBasicIO } from 'e2e-shared/specs/basic-io.spec.ts'\n\ntestBasicIO({\n  hook: 'useQueryState',\n  path: '/app/agnostic/basic-io',\n  router: 'next-app',\n  description: 'Agnostic adapter'\n})\n\ntestBasicIO({\n  hook: 'useQueryState',\n  path: '/pages/agnostic/basic-io',\n  router: 'next-pages',\n  description: 'Agnostic adapter'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/debounce.spec.ts",
    "content": "import { testDebounce } from 'e2e-shared/specs/debounce.spec.ts'\n\ntestDebounce({\n  path: '/app/debounce',\n  router: 'next-app'\n})\n\ntestDebounce({\n  path: '/pages/debounce',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/dynamic-segments.spec.ts",
    "content": "import { testDynamicSegments } from 'e2e-shared/specs/dynamic-segments.spec.ts'\n\ntestDynamicSegments({\n  path: '/app/dynamic-segments/dynamic/segment',\n  expectedSegments: ['segment'],\n  router: 'next-app'\n})\n\ntestDynamicSegments({\n  path: '/pages/dynamic-segments/dynamic/segment',\n  expectedSegments: ['segment'],\n  router: 'next-pages'\n})\n\n// Catch-all --\n\ntestDynamicSegments({\n  path: '/app/dynamic-segments/catch-all/foo',\n  expectedSegments: ['foo'],\n  router: 'next-app'\n})\n\ntestDynamicSegments({\n  path: '/pages/dynamic-segments/catch-all/foo',\n  expectedSegments: ['foo'],\n  router: 'next-pages'\n})\n\ntestDynamicSegments({\n  path: '/app/dynamic-segments/catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c'],\n  router: 'next-app'\n})\n\ntestDynamicSegments({\n  path: '/pages/dynamic-segments/catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c'],\n  router: 'next-pages'\n})\n\n// Optional catch-all --\n\ntestDynamicSegments({\n  path: '/app/dynamic-segments/optional-catch-all', // no segments\n  expectedSegments: [],\n  router: 'next-app'\n})\n\ntestDynamicSegments({\n  path: '/pages/dynamic-segments/optional-catch-all', // no segments\n  expectedSegments: [],\n  router: 'next-pages'\n})\n\ntestDynamicSegments({\n  path: '/app/dynamic-segments/optional-catch-all/foo',\n  expectedSegments: ['foo'],\n  router: 'next-app'\n})\n\ntestDynamicSegments({\n  path: '/pages/dynamic-segments/optional-catch-all/foo',\n  expectedSegments: ['foo'],\n  router: 'next-pages'\n})\n\ntestDynamicSegments({\n  path: '/app/dynamic-segments/optional-catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c'],\n  router: 'next-app'\n})\n\ntestDynamicSegments({\n  path: '/pages/dynamic-segments/optional-catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c'],\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/flush-after-navigate.spec.ts",
    "content": "import { testFlushAfterNavigate } from 'e2e-shared/specs/flush-after-navigate.spec.ts'\n\ntestFlushAfterNavigate({\n  path: '/app/flush-after-navigate/useQueryState',\n  router: 'next-app',\n  hook: 'useQueryState'\n})\n\ntestFlushAfterNavigate({\n  path: '/app/flush-after-navigate/useQueryStates',\n  router: 'next-app',\n  hook: 'useQueryStates'\n})\n\ntestFlushAfterNavigate({\n  path: '/pages/flush-after-navigate/useQueryState',\n  router: 'next-pages',\n  hook: 'useQueryState'\n})\n\ntestFlushAfterNavigate({\n  path: '/pages/flush-after-navigate/useQueryStates',\n  router: 'next-pages',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/hash-preservation.spec.ts",
    "content": "import { testHashPreservation } from 'e2e-shared/specs/hash-preservation.spec.ts'\n\ntestHashPreservation({\n  path: '/app/hash-preservation/dynamic/route',\n  router: 'next-app',\n  description: 'dynamic route'\n})\n\ntestHashPreservation({\n  path: '/pages/hash-preservation/dynamic/route',\n  router: 'next-pages',\n  description: 'dynamic route'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/loader.spec.ts",
    "content": "import { testLoader } from 'e2e-shared/specs/loader.spec.ts'\n\n// In page components:\n\ntestLoader({\n  path: '/app/loader',\n  router: 'next-app',\n  description: 'Loads from page component'\n})\n\ntestLoader({\n  path: '/pages/loader',\n  router: 'next-pages',\n  description: 'Loads from page component'\n})\n\n// In API routes:\n\ntestLoader({\n  path: '/api/app/loader',\n  router: 'next-app',\n  description: 'Loads from API route'\n})\n\ntestLoader({\n  path: '/api/pages/loader',\n  router: 'next-pages',\n  description: 'Loads from API route'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/popstate-queue-reset.spec.ts",
    "content": "import { testPopstateQueueReset } from 'e2e-shared/specs/popstate-queue-reset.spec.ts'\n\ntestPopstateQueueReset({\n  path: '/app/popstate-queue-reset',\n  router: 'next-app'\n})\n\ntestPopstateQueueReset({\n  path: '/pages/popstate-queue-reset',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/app/push/useQueryState',\n  hook: 'useQueryState',\n  router: 'next-app',\n  description: 'standard route'\n})\n\ntestPush({\n  path: '/app/push/useQueryStates',\n  hook: 'useQueryStates',\n  router: 'next-app',\n  description: 'standard route'\n})\n\ntestPush({\n  path: '/pages/push/useQueryState',\n  hook: 'useQueryState',\n  router: 'next-pages',\n  description: 'standard route'\n})\n\ntestPush({\n  path: '/pages/push/useQueryStates',\n  hook: 'useQueryStates',\n  router: 'next-pages',\n  description: 'standard route'\n})\n\n// --\n\ntestPush({\n  path: '/app/push/useQueryState/dynamic/route',\n  hook: 'useQueryState',\n  router: 'next-app',\n  description: 'dynamic route'\n})\n\ntestPush({\n  path: '/app/push/useQueryStates/dynamic/route',\n  hook: 'useQueryStates',\n  router: 'next-app',\n  description: 'dynamic route'\n})\n\ntestPush({\n  path: '/pages/push/useQueryState/dynamic/route',\n  hook: 'useQueryState',\n  router: 'next-pages',\n  description: 'dynamic route'\n})\n\ntestPush({\n  path: '/pages/push/useQueryStates/dynamic/route',\n  hook: 'useQueryStates',\n  router: 'next-pages',\n  description: 'dynamic route'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/render-count.spec.ts",
    "content": "import { testRenderCount } from 'e2e-shared/specs/render-count.spec.ts'\n\nconst hooks = ['useQueryState', 'useQueryStates'] as const\nconst shallows = [true, false] as const\nconst histories = ['replace', 'push'] as const\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        for (const delay of shallow === false ? [0, 50] : [0]) {\n          testRenderCount({\n            path: `/app/render-count/${hook}/${shallow}/${history}/${startTransition}?delay=${delay}`,\n            hook,\n            props: {\n              shallow,\n              history,\n              startTransition,\n              delay\n            },\n            expected: {\n              mount: 1,\n              update: shallow === false ? 3 : 2\n            },\n            router: 'next-app'\n          })\n        }\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        for (const delay of shallow === false ? [0, 50] : [0]) {\n          testRenderCount({\n            path: `/pages/render-count/${hook}/${shallow}/${history}/${startTransition}?delay=${delay}`,\n            hook,\n            props: {\n              shallow,\n              history,\n              startTransition,\n              delay\n            },\n            expected: {\n              mount: 1,\n              update: 2\n            },\n            router: 'next-pages'\n          })\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/app/repro-1099/useQueryState',\n  hook: 'useQueryState',\n  router: 'next-app'\n})\n\ntestRepro1099({\n  path: '/app/repro-1099/useQueryStates',\n  hook: 'useQueryStates',\n  router: 'next-app'\n})\n\ntestRepro1099({\n  path: '/pages/repro-1099/useQueryState',\n  hook: 'useQueryState',\n  router: 'next-pages'\n})\n\ntestRepro1099({\n  path: '/pages/repro-1099/useQueryStates',\n  hook: 'useQueryStates',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/repro-1293.spec.ts",
    "content": "import { testRepro1293 } from 'e2e-shared/specs/repro-1293.spec.ts'\n\ntestRepro1293({\n  path: '/app/repro-1293',\n  router: 'next-app'\n})\n\ntestRepro1293({\n  path: '/pages/repro-1293',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/repro-1365.spec.ts",
    "content": "import { testRepro1365 } from 'e2e-shared/specs/repro-1365.spec.ts'\n\ntestRepro1365({\n  path: '/app/repro-1365',\n  router: 'next-app'\n})\n\ntestRepro1365({\n  path: '/pages/repro-1365',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/repro-359.spec.ts",
    "content": "import { testRepro359 } from 'e2e-shared/specs/repro-359.spec.ts'\n\ntestRepro359({\n  path: '/app/repro-359',\n  router: 'next-app'\n})\n\ntestRepro359({\n  path: '/pages/repro-359',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/repro-982.spec.ts",
    "content": "import { testRepro982 } from 'e2e-shared/specs/repro-982.spec.ts'\n\ntestRepro982({\n  path: '/app/repro-982',\n  router: 'next-app'\n})\n\ntestRepro982({\n  path: '/pages/repro-982',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/shallow.spec.ts",
    "content": "import { testShallow } from 'e2e-shared/specs/shallow.spec.ts'\n\ntestShallow({\n  path: '/app/shallow/useQueryState',\n  hook: 'useQueryState',\n  router: 'next-app'\n})\n\ntestShallow({\n  path: '/app/shallow/useQueryStates',\n  hook: 'useQueryStates',\n  router: 'next-app'\n})\n\ntestShallow({\n  path: '/pages/shallow/useQueryState',\n  hook: 'useQueryState',\n  router: 'next-pages'\n})\n\ntestShallow({\n  path: '/pages/shallow/useQueryStates',\n  hook: 'useQueryStates',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared/stitching.spec.ts",
    "content": "import { testStitching } from 'e2e-shared/specs/stitching.spec.ts'\n\ntestStitching({\n  path: '/app/stitching',\n  router: 'next-app'\n})\n\ntestStitching({\n  path: '/pages/stitching',\n  router: 'next-pages'\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests('/app', { router: 'next-app' })\nrunSharedTests('/pages', { router: 'next-pages' })\n"
  },
  {
    "path": "packages/e2e/next/specs/transitions.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('transitions', async ({ page }) => {\n  await navigateTo(page, '/app/transitions')\n  await expect(page.locator('#server-rendered')).toHaveText('{}')\n  await expect(page.locator('#server-status')).toHaveText('idle')\n\n  const button = page.locator('button')\n  await expect(button).toHaveText('0')\n  await button.click()\n  await expect(button).toHaveText('1') // Instant setState\n  await expect(page.locator('#server-rendered')).toHaveText('{}')\n  await expect(page.locator('#server-status')).toHaveText('loading')\n\n  await page.waitForTimeout(500)\n  await expect(page.locator('#server-rendered')).toHaveText('{}')\n  await expect(page.locator('#server-status')).toHaveText('loading')\n\n  await page.waitForTimeout(500)\n  await expect(page.locator('#server-rendered')).toHaveText('{\"counter\":\"1\"}')\n  await expect(page.locator('#server-status')).toHaveText('idle')\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/useQueryState.spec.ts",
    "content": "import { expect, test, type Page } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\nasync function runTest(page: Page, pathname: string) {\n  // String\n  await expect(page.locator('#string_value')).toBeEmpty()\n  await page.locator('#string_set_a').click()\n  await expect(page).toHaveURL(url => url.pathname === pathname)\n  await expect(page).toHaveURL(url => url.search === '?string=a')\n  await expect(page.locator('#string_value')).toHaveText('a')\n  await page.locator('#string_set_b').click()\n  await expect(page).toHaveURL(url => url.pathname === pathname)\n  await expect(page).toHaveURL(url => url.search === '?string=b')\n  await expect(page.locator('#string_value')).toHaveText('b')\n  await page.locator('#string_clear').click()\n  await expect(page).toHaveURL(url => url.pathname === pathname)\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#string_value')).toBeEmpty()\n\n  // Integer\n  await expect(page.locator('#int_value')).toBeEmpty()\n  await page.locator('#int_increment').click()\n  await expect(page).toHaveURL(url => url.search === '?int=1')\n  await expect(page.locator('#int_value')).toHaveText('1')\n  await page.locator('#int_increment').click()\n  await expect(page).toHaveURL(url => url.search === '?int=2')\n  await expect(page.locator('#int_value')).toHaveText('2')\n  await page.locator('#int_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?int=1')\n  await expect(page.locator('#int_value')).toHaveText('1')\n  await page.locator('#int_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?int=0')\n  await expect(page.locator('#int_value')).toHaveText('0')\n  await page.locator('#int_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?int=-1')\n  await expect(page.locator('#int_value')).toHaveText('-1')\n  await page.locator('#int_clear').click()\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#int_value')).toBeEmpty()\n\n  // Float\n  await expect(page.locator('#float_value')).toBeEmpty()\n  await page.locator('#float_increment').click()\n  await expect(page).toHaveURL(url => url.search === '?float=0.1')\n  await expect(page.locator('#float_value')).toHaveText('0.1')\n  await page.locator('#float_increment').click()\n  await expect(page).toHaveURL(url => url.search === '?float=0.2')\n  await expect(page.locator('#float_value')).toHaveText('0.2')\n  await page.locator('#float_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?float=0.1')\n  await expect(page.locator('#float_value')).toHaveText('0.1')\n  await page.locator('#float_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?float=0')\n  await expect(page.locator('#float_value')).toHaveText('0')\n  await page.locator('#float_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?float=-0.1')\n  await expect(page.locator('#float_value')).toHaveText('-0.1')\n  await page.locator('#float_clear').click()\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#float_value')).toBeEmpty()\n\n  // Bool\n  await expect(page.locator('#bool_value')).toBeEmpty()\n  await page.locator('#bool_toggle').click()\n  await expect(page).toHaveURL(url => url.search === '?bool=true')\n  await expect(page.locator('#bool_value')).toHaveText('true')\n  await page.locator('#bool_toggle').click()\n  await expect(page).toHaveURL(url => url.search === '?bool=false')\n  await expect(page.locator('#bool_value')).toHaveText('false')\n  await page.locator('#bool_clear').click()\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#bool_value')).toBeEmpty()\n\n  // Index\n  await expect(page.locator('#index_value')).toBeEmpty()\n  await page.locator('#index_increment').click()\n  await expect(page).toHaveURL(url => url.search === '?index=2')\n  await expect(page.locator('#index_value')).toHaveText('1')\n  await page.locator('#index_increment').click()\n  await expect(page).toHaveURL(url => url.search === '?index=3')\n  await expect(page.locator('#index_value')).toHaveText('2')\n  await page.locator('#index_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?index=2')\n  await expect(page.locator('#index_value')).toHaveText('1')\n  await page.locator('#index_decrement').click()\n  await expect(page).toHaveURL(url => url.search === '?index=1')\n  await expect(page.locator('#index_value')).toHaveText('0')\n  await page.locator('#index_decrement').click()\n  await page.locator('#index_clear').click()\n  await expect(page).toHaveURL(url => url.search === '')\n  await expect(page.locator('#index_value')).toBeEmpty()\n\n  // todo: Add tests for:\n  // Timestamp\n  // ISO DateTime\n  // String enum\n  // JSON object\n  // Array of strings\n  // Array of integers\n}\n\ntest.describe('useQueryState (app router)', () => {\n  test('works in standard routes', async ({ page, baseURL }) => {\n    await navigateTo(page, '/app/useQueryState')\n    const basePath = new URL(baseURL!).pathname.replace(/\\/$/, '')\n    await runTest(page, `${basePath}/app/useQueryState`)\n  })\n\n  test('works in dynamic routes', async ({ page, baseURL }) => {\n    await navigateTo(page, '/app/useQueryState/dynamic/route')\n    const basePath = new URL(baseURL!).pathname.replace(/\\/$/, '')\n    await runTest(page, `${basePath}/app/useQueryState/dynamic/route`)\n  })\n})\n\ntest.describe('useQueryState (pages router)', () => {\n  test('works in standard routes', async ({ page, baseURL }) => {\n    await navigateTo(page, '/pages/useQueryState')\n    const basePath = new URL(baseURL!).pathname.replace(/\\/$/, '')\n    await runTest(page, `${basePath}/pages/useQueryState`)\n  })\n\n  test('works in dynamic routes', async ({ page, baseURL }) => {\n    await navigateTo(page, '/pages/useQueryState/dynamic/route')\n    const basePath = new URL(baseURL!).pathname.replace(/\\/$/, '')\n    await runTest(page, `${basePath}/pages/useQueryState/dynamic/route`)\n  })\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/useQueryStates-clear-all.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('useQueryStates clear all', async ({ page }) => {\n  await navigateTo(page, '/app/useQueryStates-clear-all', '?a=foo&b=bar')\n  await page.locator('button').click()\n  await expect(page).toHaveURL(url => url.search === '')\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/useQueryStates.spec.ts",
    "content": "import { expect, test, type Page } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\nasync function runTest(page: Page) {\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":null,\"int\":null,\"float\":null,\"index\":null,\"bool\":null}'\n  )\n  await expect(page.locator('#string')).toBeEmpty()\n  await expect(page.locator('#int')).toBeEmpty()\n  await expect(page.locator('#float')).toBeEmpty()\n  await expect(page.locator('#index')).toBeEmpty()\n  await expect(page.locator('#bool')).toBeEmpty()\n  await expect(page).toHaveURL(url => url.search === '')\n\n  await page.getByText('Set string').click()\n  await expect(page).toHaveURL(url => url.search === '?string=Hello')\n  await expect(page.locator('#string')).toHaveText('Hello')\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":\"Hello\",\"int\":null,\"float\":null,\"index\":null,\"bool\":null}'\n  )\n\n  await page.getByText('Set int').click()\n  await expect(page).toHaveURL(url => url.search.includes('int=42'))\n  await expect(page.locator('#int')).toHaveText('42')\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":\"Hello\",\"int\":42,\"float\":null,\"index\":null,\"bool\":null}'\n  )\n\n  await page.getByText('Set float').click()\n  await expect(page).toHaveURL(url => url.search.includes('float=3.14159'))\n  await expect(page.locator('#float')).toHaveText('3.14159')\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":\"Hello\",\"int\":42,\"float\":3.14159,\"index\":null,\"bool\":null}'\n  )\n\n  await page.getByText('Set index').click()\n  await expect(page).toHaveURL(url => url.search.includes('index=9'))\n  await expect(page.locator('#index')).toHaveText('8')\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":\"Hello\",\"int\":42,\"float\":3.14159,\"index\":8,\"bool\":null}'\n  )\n\n  await page.getByText('Toggle bool').click()\n  await expect(page).toHaveURL(url => url.search.includes('bool=true'))\n  await expect(page.locator('#bool')).toHaveText('true')\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":\"Hello\",\"int\":42,\"float\":3.14159,\"index\":8,\"bool\":true}'\n  )\n\n  await page.getByText('Toggle bool').click()\n  await expect(page).toHaveURL(url => url.search.includes('bool=false'))\n  await expect(page.locator('#bool')).toHaveText('false')\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":\"Hello\",\"int\":42,\"float\":3.14159,\"index\":8,\"bool\":false}'\n  )\n\n  await page.locator('#clear-string').click()\n  await expect(page).toHaveURL(url => !url.search.includes('string=Hello'))\n  await expect(page.locator('#string')).toBeEmpty()\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":null,\"int\":42,\"float\":3.14159,\"index\":8,\"bool\":false}'\n  )\n\n  await page.locator('#clear').click()\n  await expect(page).toHaveURL(url => !url.search.includes('string'))\n  await expect(page).toHaveURL(url => !url.search.includes('int'))\n  await expect(page).toHaveURL(url => !url.search.includes('float'))\n  await expect(page).toHaveURL(url => !url.search.includes('index'))\n  await expect(page).toHaveURL(url => !url.search.includes('bool'))\n  await expect(page.locator('#json')).toHaveText(\n    '{\"string\":null,\"int\":null,\"float\":null,\"index\":null,\"bool\":null}'\n  )\n  await expect(page.locator('#string')).toBeEmpty()\n  await expect(page.locator('#int')).toBeEmpty()\n  await expect(page.locator('#float')).toBeEmpty()\n  await expect(page.locator('#index')).toBeEmpty()\n  await expect(page.locator('#bool')).toBeEmpty()\n  await expect(page).toHaveURL(url => url.search === '')\n}\n\ntest.describe('useQueryStates (app router)', () => {\n  test('uses string by default', async ({ page }) => {\n    await navigateTo(page, '/app/useQueryStates')\n    await runTest(page)\n  })\n\n  test('should work with dynamic routes', async ({ page }) => {\n    await navigateTo(page, '/app/useQueryStates/dynamic/route')\n    await runTest(page)\n  })\n})\n\ntest.describe('useQueryStates (pages router)', () => {\n  test('uses string by default', async ({ page }) => {\n    await navigateTo(page, '/pages/useQueryStates')\n    await runTest(page)\n  })\n\n  test('should work with dynamic routes', async ({ page }) => {\n    await navigateTo(page, '/pages/useQueryStates/dynamic/route')\n    await runTest(page)\n  })\n})\n"
  },
  {
    "path": "packages/e2e/next/specs/useSearchParams.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('useSearchParams', async ({ page }) => {\n  await navigateTo(page, '/app/useSearchParams')\n  await page.locator('input').fill('foo')\n  await expect(page.locator('#searchParams')).toHaveText('q=foo')\n  await page.locator('button').click()\n  await expect(page.locator('#searchParams')).toHaveText('q=foo&push=true')\n})\n"
  },
  {
    "path": "packages/e2e/next/src/app/api/app/loader/route.ts",
    "content": "import { loadSearchParams } from 'e2e-shared/specs/loader'\nimport { NextResponse } from 'next/server'\n\n// Needed for Next.js 14.2.0 to 14.2.3\n// (due to https://github.com/vercel/next.js/pull/66446)\nexport const dynamic = 'force-dynamic'\n\nexport async function GET(request: Request) {\n  const { test, int } = loadSearchParams(request)\n  return new NextResponse(\n    `<!doctype html>\n<html>\n<body>\n    <div id=\"hydration-marker\" style=\"display:none;\" aria-hidden>hydrated</div>\n    <pre id=\"test\">${test}</pre>\n    <pre id=\"int\">${int}</pre>\n</body>\n</html>`,\n    {\n      headers: {\n        'content-type': 'text/html'\n      }\n    }\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/basic-io/useQueryState/page.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <UseQueryStateBasicIO />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/basic-io/useQueryStates/page.tsx",
    "content": "import { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <UseQueryStatesBasicIO />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryState/page.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <ConditionalRenderingUseQueryState />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/conditional-rendering/useQueryStates/page.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <ConditionalRenderingUseQueryStates />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/debounce/other/page.tsx",
    "content": "import Link from 'next/link'\n\nexport default function Page() {\n  return <Link href=\"/app/debounce\">Back to the previous page</Link>\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/debounce/page.tsx",
    "content": "import { DebounceClient } from 'e2e-shared/specs/debounce-client'\nimport { DebounceServer } from 'e2e-shared/specs/debounce-server'\nimport { loadDemoSearchParams } from 'e2e-shared/specs/debounce.defs'\nimport { type SearchParams } from 'nuqs'\nimport { Suspense } from 'react'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Page({ searchParams }: PageProps) {\n  const serverState = await loadDemoSearchParams(searchParams)\n  return (\n    <DebounceServer state={serverState}>\n      <Suspense>\n        <DebounceClient navigateHref=\"/app/debounce/other\" />\n      </Suspense>\n    </DebounceServer>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/dynamic-segments/catch-all/[...segments]/client.tsx",
    "content": "'use client'\n\nimport { DisplaySegments } from 'e2e-shared/specs/dynamic-segments'\nimport { useParams } from 'next/navigation'\nimport { ReactNode } from 'react'\n\nexport function ClientSegment({ children }: { children?: ReactNode }) {\n  const params = useParams()\n  const segments = params?.segments as string[]\n  return (\n    <>\n      {children}\n      <DisplaySegments environment=\"client\" segments={segments} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/dynamic-segments/catch-all/[...segments]/page.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString, type SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { ClientSegment } from './client'\n\ntype PageProps = {\n  params: Promise<{ segments: string[] }>\n  searchParams: Promise<SearchParams>\n}\n\nconst loadTest = createLoader({\n  test: parseAsString\n})\n\nexport default async function DynamicPage(props: PageProps) {\n  const searchParams = await props.searchParams\n  const { segments } = await props.params\n  const { test: serverState } = loadTest(searchParams)\n  return (\n    <>\n      <Suspense>\n        <UrlControls>\n          <Display environment=\"server\" state={serverState} />\n        </UrlControls>\n      </Suspense>\n      <Suspense>\n        <ClientSegment>\n          <DisplaySegments environment=\"server\" segments={segments} />\n        </ClientSegment>\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/dynamic-segments/dynamic/[segment]/client.tsx",
    "content": "'use client'\n\nimport { DisplaySegments } from 'e2e-shared/specs/dynamic-segments'\nimport { useParams } from 'next/navigation'\nimport { ReactNode } from 'react'\n\nexport function ClientSegment({ children }: { children?: ReactNode }) {\n  const params = useParams()\n  const segment = params?.segment as string\n  return (\n    <>\n      {children}\n      <DisplaySegments environment=\"client\" segments={[segment]} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/dynamic-segments/dynamic/[segment]/page.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString, type SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { ClientSegment } from './client'\n\ntype PageProps = {\n  params: Promise<{ segment: string }>\n  searchParams: Promise<SearchParams>\n}\n\nconst loadTest = createLoader({\n  test: parseAsString\n})\n\nexport default async function DynamicPage(props: PageProps) {\n  const searchParams = await props.searchParams\n  const { segment } = await props.params\n  const { test: serverState } = loadTest(searchParams)\n  return (\n    <>\n      <Suspense>\n        <UrlControls>\n          <Display environment=\"server\" state={serverState} />\n        </UrlControls>\n      </Suspense>\n      <Suspense>\n        <ClientSegment>\n          <DisplaySegments environment=\"server\" segments={[segment]} />\n        </ClientSegment>\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/dynamic-segments/optional-catch-all/[[...segments]]/client.tsx",
    "content": "'use client'\n\nimport { DisplaySegments } from 'e2e-shared/specs/dynamic-segments'\nimport { useParams } from 'next/navigation'\nimport { ReactNode } from 'react'\n\nexport function ClientSegment({ children }: { children?: ReactNode }) {\n  const params = useParams()\n  const segments = params?.segments as string[]\n  return (\n    <>\n      {children}\n      <DisplaySegments environment=\"client\" segments={segments} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/dynamic-segments/optional-catch-all/[[...segments]]/page.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString, type SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { ClientSegment } from './client'\n\ntype PageProps = {\n  params: Promise<{ segments: string[] }>\n  searchParams: Promise<SearchParams>\n}\n\nconst loadTest = createLoader({\n  test: parseAsString\n})\n\nexport default async function DynamicPage(props: PageProps) {\n  const searchParams = await props.searchParams\n  const { segments } = await props.params\n  const { test: serverState } = loadTest(searchParams)\n  return (\n    <>\n      <Suspense>\n        <UrlControls>\n          <Display environment=\"server\" state={serverState} />\n        </UrlControls>\n      </Suspense>\n      <Suspense>\n        <ClientSegment>\n          <DisplaySegments environment=\"server\" segments={segments} />\n        </ClientSegment>\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/flush-after-navigate/useQueryState/end/page.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <FlushAfterNavigateEnd />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/flush-after-navigate/useQueryState/start/page.tsx",
    "content": "import { FlushAfterNavigateUseQueryStateStart } from 'e2e-shared/specs/flush-after-navigate'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <FlushAfterNavigateUseQueryStateStart path=\"/app/flush-after-navigate/useQueryState\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/flush-after-navigate/useQueryStates/end/page.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <FlushAfterNavigateEnd />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/flush-after-navigate/useQueryStates/start/page.tsx",
    "content": "import { FlushAfterNavigateUseQueryStatesStart } from 'e2e-shared/specs/flush-after-navigate'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <FlushAfterNavigateUseQueryStatesStart path=\"/app/flush-after-navigate/useQueryStates\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/form/useQueryState/page.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <TestFormUseQueryState />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/form/useQueryStates/page.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <TestFormUseQueryStates />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/hash-preservation/dynamic/[route]/page.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <HashPreservation />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/hash-preservation/page.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <HashPreservation />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/history-sync/page.tsx",
    "content": "import { HistorySync } from 'e2e-shared/specs/history-sync'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <HistorySync />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/json/page.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Json />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/life-and-death/page.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <LifeAndDeath />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/linking/useQueryState/other/page.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <LinkingUseQueryState path=\"/app/linking/useQueryState\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/linking/useQueryState/page.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <LinkingUseQueryState path=\"/app/linking/useQueryState\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/linking/useQueryStates/other/page.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <LinkingUseQueryStates path=\"/app/linking/useQueryStates\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/linking/useQueryStates/page.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <LinkingUseQueryStates path=\"/app/linking/useQueryStates\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/native-array/page.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <NativeArray />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/popstate-queue-reset/other/page.tsx",
    "content": "import { PopstateQueueResetOther } from 'e2e-shared/specs/popstate-queue-reset'\n\nexport default PopstateQueueResetOther\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/popstate-queue-reset/page.tsx",
    "content": "'use client'\n\nimport { PopstateQueueResetClient } from 'e2e-shared/specs/popstate-queue-reset'\nimport { useRouter } from 'next/navigation'\nimport { Suspense } from 'react'\n\nfunction PopstateQueueResetWrapper() {\n  const router = useRouter()\n  return (\n    <PopstateQueueResetClient\n      onNavigateToOther={() => router.push('/app/popstate-queue-reset/other')}\n    />\n  )\n}\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PopstateQueueResetWrapper />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/pretty-urls/page.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PrettyUrls />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/push/useQueryState/dynamic/[route]/page.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PushUseQueryState />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/push/useQueryState/page.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PushUseQueryState />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/push/useQueryStates/dynamic/[route]/page.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PushUseQueryStates />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/push/useQueryStates/page.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PushUseQueryStates />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/rate-limits/page.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <RateLimits />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/referential-stability/useQueryState/page.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <ReferentialStabilityUseQueryState />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/referential-stability/useQueryStates/page.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <ReferentialStabilityUseQueryStates />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/render-count/[hook]/[shallow]/[history]/[startTransition]/page.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport {\n  loadParams,\n  loadSearchParams\n} from 'e2e-shared/specs/render-count.params'\nimport { setTimeout } from 'node:timers/promises'\nimport { type SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\n\nexport const dynamic = 'force-dynamic'\n\ntype PageProps = {\n  params: Promise<Record<keyof ReturnType<typeof loadParams>, string>>\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Page({\n  params,\n  searchParams\n}: PageProps & { searchParams: Promise<SearchParams> }) {\n  const { hook, shallow, history, startTransition } = await loadParams(params)\n  const { delay } = await loadSearchParams(searchParams)\n  if (delay) {\n    await setTimeout(delay)\n  }\n  return (\n    <Suspense>\n      <RenderCount\n        hook={hook}\n        shallow={shallow}\n        history={history}\n        startTransition={startTransition}\n      />\n    </Suspense>\n  )\n}\n\nexport async function generateStaticParams() {\n  const hooks = ['useQueryState', 'useQueryStates']\n  const shallow = [true, false]\n  const history = ['push', 'replace']\n  return hooks.flatMap(hook =>\n    shallow.flatMap(shallow =>\n      history.map(history => ({ hook, shallow: shallow.toString(), history }))\n    )\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/repro-1365/page.tsx",
    "content": "import { Repro1365 } from 'e2e-shared/specs/repro-1365'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro1365 />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/repro-359/page.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro359 />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/repro-982/page.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro982 />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/routing/useQueryState/other/page.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <RoutingUseQueryState path=\"/app/routing/useQueryState\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/routing/useQueryState/page.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <RoutingUseQueryState path=\"/app/routing/useQueryState\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/routing/useQueryStates/other/page.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <RoutingUseQueryStates path=\"/app/routing/useQueryStates\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/routing/useQueryStates/page.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <RoutingUseQueryStates path=\"/app/routing/useQueryStates\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/scroll/page.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Scroll />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/shallow/useQueryState/page.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\nimport {\n  createSearchParamsCache,\n  parseAsString,\n  type SearchParams\n} from 'nuqs/server'\nimport { Suspense } from 'react'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nconst cache = createSearchParamsCache(\n  {\n    state: parseAsString\n  },\n  {\n    urlKeys: {\n      state: 'test'\n    }\n  }\n)\n\nexport default async function Page({ searchParams }: PageProps) {\n  await cache.parse(searchParams)\n  return (\n    <>\n      <Suspense>\n        <ShallowUseQueryState />\n      </Suspense>\n      <Display environment=\"server\" state={cache.get('state')} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/shallow/useQueryStates/page.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\nimport {\n  createSearchParamsCache,\n  parseAsString,\n  type SearchParams\n} from 'nuqs/server'\nimport { Suspense } from 'react'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nconst cache = createSearchParamsCache(\n  {\n    state: parseAsString\n  },\n  {\n    urlKeys: {\n      state: 'test'\n    }\n  }\n)\n\nexport default async function Page({ searchParams }: PageProps) {\n  await cache.parse(searchParams)\n  return (\n    <>\n      <Suspense>\n        <ShallowUseQueryStates />\n      </Suspense>\n      <Display environment=\"server\" state={cache.get('state')} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/(shared)/stitching/page.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Stitching />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/agnostic/basic-io/page.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <UseQueryStateBasicIO />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/agnostic/layout.tsx",
    "content": "import { NuqsAdapter } from 'nuqs/adapters/next'\nimport type { ReactNode } from 'react'\n\nexport default function RouterAgnosticLayout({\n  children\n}: {\n  children: ReactNode\n}) {\n  return <NuqsAdapter>{children}</NuqsAdapter>\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/cache/all.tsx",
    "content": "import { cache } from './searchParams'\n\nexport function All() {\n  const { bool, num, str, def, nope, idx } = cache.all()\n  return (\n    <>\n      <h2>From all:</h2>\n      <p style={{ display: 'flex', gap: '1rem' }}>\n        <span id=\"all-str\">{str}</span>\n        <span id=\"all-num\">{num}</span>\n        <span id=\"all-idx\">{String(idx)}</span>\n        <span id=\"all-bool\">{String(bool)}</span>\n        <span id=\"all-def\">{def}</span>\n        <span id=\"all-nope\">{String(nope)}</span>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/cache/get.tsx",
    "content": "import { cache } from './searchParams'\n\nexport function Get() {\n  const bool = cache.get('bool')\n  const num = cache.get('num')\n  const idx = cache.get('idx')\n  const str = cache.get('str')\n  const def = cache.get('def')\n  const nope = cache.get('nope')\n  return (\n    <>\n      <h2>From get:</h2>\n      <p style={{ display: 'flex', gap: '1rem' }}>\n        <span id=\"get-str\">{str}</span>\n        <span id=\"get-num\">{num}</span>\n        <span id=\"get-idx\">{String(idx)}</span>\n        <span id=\"get-bool\">{String(bool)}</span>\n        <span id=\"get-def\">{def}</span>\n        <span id=\"get-nope\">{String(nope)}</span>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/cache/layout.tsx",
    "content": "import React from 'react'\nimport { cache } from './searchParams'\n\nexport default function Layout({ children }: { children: React.ReactNode }) {\n  let result = ''\n  try {\n    result = JSON.stringify(cache.all())\n  } catch (error) {\n    result = String(error)\n  }\n  return (\n    <>\n      {children}\n      <h2>Layout</h2>\n      <p id=\"layout-result\">{result}</p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/cache/page.tsx",
    "content": "import type { SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { All } from './all'\nimport { Get } from './get'\nimport { cache } from './searchParams'\nimport { Set } from './set'\n\ntype Props = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Page({ searchParams }: Props) {\n  const { str, bool, num, def, nope, idx } = await cache.parse(searchParams)\n  return (\n    <>\n      <h1>Root page</h1>\n      <h2>From parse:</h2>\n      <p style={{ display: 'flex', gap: '1rem' }}>\n        <span id=\"parse-str\">{str}</span>\n        <span id=\"parse-num\">{num}</span>\n        <span id=\"parse-idx\">{String(idx)}</span>\n        <span id=\"parse-bool\">{String(bool)}</span>\n        <span id=\"parse-def\">{def}</span>\n        <span id=\"parse-nope\">{String(nope)}</span>\n      </p>\n      <All />\n      <Get />\n      <Suspense>\n        <Set />\n      </Suspense>\n    </>\n  )\n}\n\nexport async function generateMetadata({ searchParams }: Props) {\n  // parse here too to ensure we can idempotently parse the same search params as the page in the same request\n  const { str } = await cache.parse(searchParams)\n  return {\n    title: `metadata-title-str:${str}`\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/cache/searchParams.ts",
    "content": "import {\n  createSearchParamsCache,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsIndex,\n  parseAsString\n} from 'nuqs/server'\n\nexport const parsers = {\n  str: parseAsString,\n  num: parseAsInteger,\n  idx: parseAsIndex,\n  bool: parseAsBoolean,\n  def: parseAsString.withDefault('default'),\n  nope: parseAsString\n}\n\nexport const cache = createSearchParamsCache(parsers)\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/cache/set.tsx",
    "content": "'use client'\n\nimport { useQueryStates } from 'nuqs'\nimport { parsers } from './searchParams'\n\nexport function Set() {\n  const [{ bool, num, str, def, nope, idx }, set] = useQueryStates(parsers, {\n    shallow: false\n  })\n  return (\n    <>\n      <h2>Set</h2>\n      <button onClick={() => set({ str: 'from-set', num: 42, bool: true })}>\n        Set\n      </button>\n      <p style={{ display: 'flex', gap: '1rem' }}>\n        <span id=\"set-str\">{str}</span>\n        <span id=\"set-num\">{num}</span>\n        <span id=\"set-idx\">{String(idx)}</span>\n        <span id=\"set-bool\">{String(bool)}</span>\n        <span id=\"set-def\">{def}</span>\n        <span id=\"set-nope\">{String(nope)}</span>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/deferred/page.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\nimport { Suspense, useCallback } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <DeferredForm />\n      <InternalState />\n    </Suspense>\n  )\n}\n\nfunction DeferredForm() {\n  const [a, setA] = useQueryState('a', {\n    defaultValue: '',\n    throttleMs: Infinity\n  })\n  const [b, setB] = useQueryState('b', {\n    defaultValue: '',\n    throttleMs: Infinity\n  })\n\n  const onSubmit = useCallback(() => {\n    // Apply current values to the URL\n    setA(passThru, { throttleMs: 0 })\n    setB(passThru, { throttleMs: 0 })\n  }, [])\n\n  return (\n    <>\n      <input id=\"input-a\" value={a} onChange={e => setA(e.target.value)} />\n      <input id=\"input-b\" value={b} onChange={e => setB(e.target.value)} />\n      <button onClick={onSubmit}>Write to URL</button>\n    </>\n  )\n}\n\nfunction InternalState() {\n  const [a] = useQueryState('a', { defaultValue: '' })\n  const [b] = useQueryState('b', { defaultValue: '' })\n  return (\n    <>\n      <p>This is the internal state:</p>\n      <ul>\n        <li id=\"state-a\">{a}</li>\n        <li id=\"state-b\">{b}</li>\n      </ul>\n    </>\n  )\n}\n\n// --\n\nfunction passThru<T>(x: T): T {\n  return x\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/loader/page.tsx",
    "content": "import { LoaderRenderer, loadSearchParams } from 'e2e-shared/specs/loader'\nimport type { SearchParams } from 'nuqs/server'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Page({ searchParams }: PageProps) {\n  const serverValues = await loadSearchParams(searchParams)\n  return <LoaderRenderer serverValues={serverValues} />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/multitenant/[tenant]/client-tenant.tsx",
    "content": "'use client'\n\nimport { useParams, usePathname } from 'next/navigation'\n\nexport function TenantClient() {\n  const params = useParams()\n  const pathname = usePathname()\n  return (\n    <>\n      <p id=\"client-tenant\">{params?.tenant}</p>\n      <p id=\"router-pathname\">{pathname}</p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/multitenant/[tenant]/page.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\nimport {\n  createSearchParamsCache,\n  parseAsString,\n  type SearchParams\n} from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { TenantClient } from './client-tenant'\n\ntype PageProps = {\n  params: Promise<{ tenant: string }>\n  searchParams: Promise<SearchParams>\n}\n\nconst cache = createSearchParamsCache(\n  {\n    state: parseAsString\n  },\n  {\n    urlKeys: {\n      state: 'test'\n    }\n  }\n)\n\nexport default async function TenantPage({ params, searchParams }: PageProps) {\n  const { tenant } = await params\n  if (!tenant) {\n    return <div>Error: Tenant not found.</div>\n  }\n  await cache.parse(searchParams)\n\n  return (\n    <>\n      <Suspense>\n        <ShallowUseQueryState />\n      </Suspense>\n      <Display environment=\"server\" state={cache.get('state')} />\n      <p id=\"server-tenant\">{tenant}</p>\n      <Suspense>\n        <TenantClient />\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/persist-across-navigation/a/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { Client } from '../client'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client page=\"a\" target=\"b\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/persist-across-navigation/b/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { Client } from '../client'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client page=\"b\" target=\"a\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/persist-across-navigation/client.tsx",
    "content": "import Link from 'next/link'\nimport {\n  createSerializer,\n  parseAsBoolean,\n  parseAsString,\n  useQueryStates\n} from 'nuqs'\n\ntype Page = 'a' | 'b'\n\ntype ClientProps = {\n  page: Page\n  target: Page\n}\n\nconst searchParams = {\n  q: parseAsString.withDefault(''),\n  checked: parseAsBoolean.withDefault(false)\n}\nconst serialize = createSerializer(searchParams)\n\nexport function Client({ page, target }: ClientProps) {\n  const [{ q, checked }, setParams] = useQueryStates(searchParams)\n  const href = serialize(`/app/persist-across-navigation/${target}`, {\n    q,\n    checked\n  })\n  return (\n    <>\n      <h1>Page {page}</h1>\n      <input\n        type=\"text\"\n        value={q}\n        onChange={e => setParams({ q: e.target.value })}\n      />\n      <input\n        type=\"checkbox\"\n        checked={checked}\n        onChange={e => setParams({ checked: e.target.checked })}\n      />\n      <Link href={href}>Go to page {target}</Link>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/push/client.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\nimport { parser } from './searchParams'\n\nexport function Client() {\n  const [client, setClient] = useQueryState('client', parser)\n  const [server, setServer] = useQueryState(\n    'server',\n    parser.withOptions({ shallow: false })\n  )\n  return (\n    <>\n      <p>\n        Client: <span id=\"client\">{client}</span>\n      </p>\n      <p>\n        Server: <span id=\"server\">{server}</span>\n      </p>\n      <button id=\"client-incr\" onClick={() => setClient(c => c + 1)}>\n        Client Incr\n      </button>\n      <button id=\"server-incr\" onClick={() => setServer(c => c + 1)}>\n        Server Incr\n      </button>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/push/page.tsx",
    "content": "import type { SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { Client } from './client'\nimport { searchParamsCache } from './searchParams'\n\nexport default async function Page({\n  searchParams\n}: {\n  searchParams: Promise<SearchParams>\n}) {\n  const { server } = await searchParamsCache.parse(searchParams)\n  return (\n    <>\n      <p>\n        Server side: <span id=\"server-side\">{server}</span>\n      </p>\n      <Suspense>\n        <Client />\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/push/searchParams.ts",
    "content": "import { createSearchParamsCache, parseAsInteger } from 'nuqs/server'\n\nexport const parser = parseAsInteger.withDefault(0).withOptions({\n  history: 'push'\n})\nexport const searchParamsCache = createSearchParamsCache({\n  server: parser\n})\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/referential-equality/page.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { parseAsJson, useQueryState, useQueryStates } from 'nuqs'\nimport { Suspense, useEffect, useState } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nconst defaultValue = { x: 0 }\ntype Value = typeof defaultValue\n\nfunction increment(value: Value): Value {\n  return { x: value.x + 1 }\n}\n\nconst makeLoggingSpy =\n  (key: string) =>\n  (value: unknown): Value => {\n    console.log(`[%s]: Parser running with value %O`, key, value)\n    return value as Value\n  }\n\nfunction Client() {\n  const [aRefCount, setARefCount] = useState(0)\n  const [bRefCount, setBRefCount] = useState(0)\n  const [a, setA] = useQueryState(\n    'a',\n    parseAsJson<Value>(makeLoggingSpy('a')).withDefault(defaultValue)\n  )\n  const [{ b }, setB] = useQueryStates({\n    b: parseAsJson<Value>(makeLoggingSpy('b')).withDefault(defaultValue)\n  })\n\n  useEffect(() => {\n    setARefCount(old => old + 1)\n  }, [a])\n  useEffect(() => {\n    setBRefCount(old => old + 1)\n  }, [b])\n\n  return (\n    <>\n      <div>\n        <button id=\"increment-a\" onClick={() => setA(increment)}>\n          Increment A\n        </button>\n        <button id=\"idempotent-a\" onClick={() => setA(x => x)}>\n          Itempotent A\n        </button>\n        <button id=\"clear-a\" onClick={() => setA(null)}>\n          Clear A\n        </button>\n        <span>\n          Refs seen: <span id=\"ref-a\">{aRefCount}</span>\n        </span>\n      </div>\n      <div>\n        <button\n          id=\"increment-b\"\n          onClick={() =>\n            setB(old => ({\n              b: increment(old.b)\n            }))\n          }\n        >\n          Increment B\n        </button>\n        <button id=\"idempotent-b\" onClick={() => setB(x => x)}>\n          Itempotent B\n        </button>\n        <button\n          id=\"clear-b\"\n          onClick={() =>\n            setB({\n              b: null\n            })\n          }\n        >\n          Clear B\n        </button>\n        <span>\n          Refs seen: <span id=\"ref-b\">{bRefCount}</span>\n        </span>\n      </div>\n      <div>\n        <Link href=\"#\" id=\"link\">\n          Link to #\n        </Link>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/remapped-keys/page.tsx",
    "content": "'use client'\n\nimport {\n  parseAsArrayOf,\n  parseAsInteger,\n  parseAsString,\n  useQueryStates\n} from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const [{ searchQuery, pageNumber, activeTags }, setURLState] = useQueryStates(\n    {\n      searchQuery: parseAsString.withDefault(''),\n      pageNumber: parseAsInteger.withDefault(1),\n      activeTags: parseAsArrayOf(parseAsString).withDefault([])\n    },\n    {\n      urlKeys: {\n        searchQuery: 'q',\n        pageNumber: 'page',\n        activeTags: 'tags'\n      }\n    }\n  )\n\n  return (\n    <>\n      <label style={{ display: 'block' }}>\n        <input\n          id=\"search\"\n          value={searchQuery}\n          onChange={e => setURLState({ searchQuery: e.target.value })}\n        />\n        <span>Search</span>\n      </label>\n      <label style={{ display: 'block' }}>\n        <input\n          id=\"page\"\n          type=\"number\"\n          min={1}\n          max={5}\n          step={1}\n          value={pageNumber}\n          onChange={e => setURLState({ pageNumber: e.target.valueAsNumber })}\n        />\n        <span>Page</span>\n      </label>\n      <label style={{ display: 'block' }}>\n        <label>\n          <input\n            id=\"react\"\n            type=\"checkbox\"\n            checked={activeTags.includes('react')}\n            onChange={e =>\n              setURLState(old => ({\n                activeTags: e.target.checked\n                  ? [...old.activeTags, 'react']\n                  : old.activeTags.filter(tag => !tag.includes('react'))\n              }))\n            }\n          />\n          React SPA\n        </label>\n        <label>\n          <input\n            id=\"nextjs\"\n            type=\"checkbox\"\n            checked={activeTags.includes('next.js')}\n            onChange={e =>\n              setURLState(old => ({\n                activeTags: e.target.checked\n                  ? [...old.activeTags, 'next.js']\n                  : old.activeTags.filter(tag => !tag.includes('next.js'))\n              }))\n            }\n          />\n          Next.js\n        </label>\n      </label>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-1099/useQueryState/page.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro1099UseQueryState />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-1099/useQueryStates/page.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro1099UseQueryStates />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-1293/a/page.tsx",
    "content": "import { Repro1293PageA } from 'e2e-shared/specs/repro-1293'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro1293PageA linkHref=\"/app/repro-1293/b\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-1293/b/page.tsx",
    "content": "import { Repro1293PageB } from 'e2e-shared/specs/repro-1293'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Repro1293PageB />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-388/page.tsx",
    "content": "'use client'\n\nimport { PrefetchKind } from 'next/dist/client/components/router-reducer/router-reducer-types'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { parseAsInteger, useQueryState } from 'nuqs'\nimport React, { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const router = useRouter()\n  const [counter, setCounter] = useQueryState(\n    'counter',\n    parseAsInteger.withDefault(0)\n  )\n  const [mounted, setMounted] = React.useState(false)\n  const manualPrefetch = React.useCallback(() => {\n    router.prefetch('/', { kind: PrefetchKind.FULL })\n  }, [router])\n\n  return (\n    <>\n      <button id=\"start\" onClick={() => setCounter(1)}>\n        Start\n      </button>\n      <button id=\"manual-prefetch\" onClick={() => manualPrefetch()}>\n        Manual prefetch\n      </button>\n      <>\n        <p>\n          The counter is set but only in the History API, the Next.js router\n          doesn't know about it. When we prefetch by hovering the link (or\n          mounting it), it will reset the querystring in the URL and reset the\n          counter. This only happens in production (as prefetching is disabled\n          in development).\n        </p>\n        <p id=\"counter\">Counter: {counter}</p>\n        <button id=\"toggle\" onClick={() => setMounted(x => !x)}>\n          Toggle mount other link\n        </button>\n        <Link\n          id=\"hover-me\"\n          href=\"/\"\n          style={{\n            display: 'inline-block',\n            paddingInline: '1rem',\n            marginLeft: '1rem',\n            border: 'solid 1px gray'\n          }}\n        >\n          Hover me\n        </Link>\n        {mounted && (\n          <Link\n            href=\"/app/useQueryState\"\n            style={{\n              display: 'inline-block',\n              paddingInline: '1rem',\n              marginLeft: '1rem',\n              border: 'solid 1px gray'\n            }}\n          >\n            I'm mounted\n          </Link>\n        )}\n      </>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-498/page.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <main>\n      <a id=\"start\" href=\"#section\">\n        Apply fragment\n      </a>\n      <Suspense>\n        <Client />\n      </Suspense>\n    </main>\n  )\n}\n\nfunction Client() {\n  const [, set] = useQueryState('q')\n  return <button onClick={() => set('test')}>Set query</button>\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-542/a/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { Client } from '../client'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client page=\"a\" linkTo=\"b\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-542/b/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { Client } from '../client'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client page=\"b\" linkTo=\"a\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-542/client.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport React from 'react'\n\ntype ClientProps = {\n  page: 'a' | 'b'\n  linkTo: 'a' | 'b'\n}\n\ntype Ref = {\n  q: string | null\n  r: string | null\n}\n\nexport function Client({ page, linkTo }: ClientProps) {\n  console.log(\n    'rendering page %s, url: %s',\n    page,\n    typeof location !== 'undefined' ? location.href : 'ssr'\n  )\n  const [q] = useQueryState('q')\n  const [{ r }] = useQueryStates({ r: parseAsString })\n  const initial = React.useRef<Ref | null>(null)\n  if (initial.current === null) {\n    initial.current = { q, r }\n  }\n  return (\n    <>\n      <div id=\"q\">{q}</div>\n      <div id=\"r\">{r}</div>\n      <div id=\"initial\">{JSON.stringify(initial.current)}</div>\n      <Link href={`/app/repro-542/${linkTo}`}>\n        Go to page {page.toUpperCase()}\n      </Link>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-630/page.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client id=\"1\" />\n      <Client id=\"2\" />\n      <Clients id=\"3\" />\n      <Clients id=\"4\" />\n    </Suspense>\n  )\n}\n\ntype ClientProps = {\n  id: string\n}\n\nfunction Client({ id }: ClientProps) {\n  const [a, setA] = useQueryState(\n    'a',\n    parseAsString.withOptions({ history: 'push' })\n  )\n  const [b, setB] = useQueryState(\n    'b',\n    parseAsString.withOptions({ history: 'push' })\n  )\n\n  return (\n    <>\n      <p>useQueryState {id}</p>\n      <pre data-testid={`${id}-pre`}>{JSON.stringify({ a, b })}</pre>\n      <button\n        data-testid={`${id}-set`}\n        onClick={() => {\n          setA('1')\n          setB('2')\n        }}\n      >\n        Set\n      </button>\n      <button\n        data-testid={`${id}-clear`}\n        onClick={() => {\n          setA(null)\n          setB(null)\n        }}\n      >\n        Clear\n      </button>\n      <hr />\n    </>\n  )\n}\n\nfunction Clients({ id }: ClientProps) {\n  const [params, setParams] = useQueryStates(\n    {\n      a: parseAsString,\n      b: parseAsString\n    },\n    { history: 'push' }\n  )\n  return (\n    <>\n      <p>useQueryStates {id}</p>\n      <pre data-testid={`${id}-pre`}>{JSON.stringify(params)}</pre>\n      <button\n        data-testid={`${id}-set`}\n        onClick={() => setParams({ a: '1', b: '2' })}\n      >\n        Set\n      </button>\n      <button data-testid={`${id}-clear`} onClick={() => setParams(null)}>\n        Clear\n      </button>\n      <hr />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-758/page.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryStates } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const [{ query }, setSearchParams] = useQueryStates(\n    {\n      query: parseAsString\n    },\n    {\n      history: 'push',\n      urlKeys: {\n        query: 'q'\n      }\n    }\n  )\n  return (\n    <>\n      <button onClick={() => setSearchParams({ query: 'test' })}>\n        Set query\n      </button>\n      <p id=\"state\">{query}</p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-760/page.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { Suspense, useState } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <DynamicUseQueryState />\n      <DynamicUseQueryStates />\n    </Suspense>\n  )\n}\n\nfunction DynamicUseQueryState() {\n  const [defaultValue, setDefaultValue] = useState('a')\n  const [value] = useQueryState('a', parseAsString.withDefault(defaultValue))\n  return (\n    <section>\n      <button id=\"trigger-a\" onClick={() => setDefaultValue('pass')}>\n        Trigger\n      </button>\n      <span id=\"value-a\">{value}</span>\n    </section>\n  )\n}\n\nfunction DynamicUseQueryStates() {\n  const [defaultValue, setDefaultValue] = useState('b')\n  const [{ value }] = useQueryStates(\n    {\n      value: parseAsString.withDefault(defaultValue)\n    },\n    { urlKeys: { value: 'b' } }\n  )\n  return (\n    <section>\n      <button id=\"trigger-b\" onClick={() => setDefaultValue('pass')}>\n        Trigger\n      </button>\n      <span id=\"value-b\">{value}</span>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/repro-774/page.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { parseAsString, useQueryStates } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Home() {\n  return (\n    <>\n      <nav>\n        <Link id=\"link\" href=\"/app/repro-774\">\n          Reset\n        </Link>\n      </nav>\n      <Suspense>\n        <Client />\n      </Suspense>\n    </>\n  )\n}\n\nconst searchParams = {\n  a: parseAsString.withDefault(''),\n  b: parseAsString.withDefault('')\n}\n\nfunction Client() {\n  const [{ a, b }, setSearchParams] = useQueryStates(searchParams)\n  return (\n    <>\n      <button onClick={() => setSearchParams({ a: 'a' })} id=\"trigger-a\">\n        Set A\n      </button>\n      <button onClick={() => setSearchParams({ b: 'b' })} id=\"trigger-b\">\n        Set B\n      </button>\n      <span id=\"value-a\">{a}</span>\n      <span id=\"value-b\">{b}</span>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/rewrites/destination/client.tsx",
    "content": "'use client'\n\nimport { useQueryStates } from 'nuqs'\nimport { searchParams } from './searchParams'\n\nexport function RewriteDestinationClient() {\n  const [{ injected, through }] = useQueryStates(searchParams)\n  return (\n    <>\n      <p>\n        Injected (client): <span id=\"injected-client\">{injected}</span>\n      </p>\n      <p>\n        Through (client): <span id=\"through-client\">{through}</span>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/rewrites/destination/page.tsx",
    "content": "import type { SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { RewriteDestinationClient } from './client'\nimport { cache } from './searchParams'\n\nexport default async function RewriteDestinationPage({\n  searchParams\n}: {\n  searchParams: Promise<SearchParams>\n}) {\n  const { injected, through } = await cache.parse(searchParams)\n  return (\n    <>\n      <p>\n        Injected (server): <span id=\"injected-server\">{injected}</span>\n      </p>\n      <p>\n        Through (server): <span id=\"through-server\">{through}</span>\n      </p>\n      <Suspense>\n        <RewriteDestinationClient />\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/rewrites/destination/searchParams.ts",
    "content": "import { createSearchParamsCache, parseAsString } from 'nuqs/server'\n\nexport const searchParams = {\n  injected: parseAsString.withDefault('null'),\n  through: parseAsString.withDefault('null')\n}\n\nexport const cache = createSearchParamsCache(searchParams)\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/_components/parsers.ts",
    "content": "import { parseAsInteger, parseAsString } from 'nuqs/server'\n\nexport const counterParser = parseAsInteger.withDefault(0)\nexport const fromParser = parseAsString\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/_components/view.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { useQueryState } from 'nuqs'\nimport { counterParser, fromParser } from './parsers'\n\ntype RoutingTourViewProps = {\n  thisPage: string\n  nextPage: string\n}\n\nexport const RoutingTourView: React.FC<RoutingTourViewProps> = ({\n  thisPage,\n  nextPage\n}) => {\n  const [counter] = useQueryState('counter', counterParser)\n  const [from] = useQueryState('from', fromParser)\n  return (\n    <>\n      <Link\n        href={`/app/routing-tour/${nextPage}?from=${thisPage}&counter=${\n          counter + 1\n        }`}\n      >\n        Next\n      </Link>\n      <p>\n        Came from: <span id=\"from\">{from}</span>\n      </p>\n      <p>\n        This page: <span id=\"this\">{thisPage}</span>\n      </p>\n      <p>\n        Next page: <span id=\"next\">{nextPage}</span>\n      </p>\n      <p>\n        Counter: <span id=\"counter\">{counter}</span>\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/a/page.tsx",
    "content": "import { Suspense } from 'react'\nimport { RoutingTourView } from '../_components/view'\n\nexport default function PageA() {\n  return (\n    <Suspense>\n      <RoutingTourView thisPage=\"a\" nextPage=\"b\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/b/page.tsx",
    "content": "import { Suspense } from 'react'\nimport { RoutingTourView } from '../_components/view'\n\nexport default function PageB() {\n  return (\n    <Suspense>\n      <RoutingTourView thisPage=\"b\" nextPage=\"c\" />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/c/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { RoutingTourView } from '../_components/view'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PageC />\n    </Suspense>\n  )\n}\n\nfunction PageC() {\n  return <RoutingTourView thisPage=\"c\" nextPage=\"d\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/d/page.tsx",
    "content": "'use client'\n\nimport { Suspense } from 'react'\nimport { RoutingTourView } from '../_components/view'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <PageD />\n    </Suspense>\n  )\n}\n\nfunction PageD() {\n  return <RoutingTourView thisPage=\"d\" nextPage=\"a\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/start/client/page.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <ClientStartPage />\n    </Suspense>\n  )\n}\n\nfunction ClientStartPage() {\n  return (\n    <ul>\n      <li>\n        <Link href=\"/app/routing-tour/a?from=start.client\">\n          a (server, prefetch)\n        </Link>\n      </li>\n      <li>\n        <Link href=\"/app/routing-tour/b?from=start.client\" prefetch={false}>\n          b (server, no prefetch)\n        </Link>\n      </li>\n      <li>\n        <Link href=\"/app/routing-tour/c?from=start.client\">\n          c (client, prefetch)\n        </Link>\n      </li>\n      <li>\n        <Link href=\"/app/routing-tour/d?from=start.client\" prefetch={false}>\n          d (client, no prefetch)\n        </Link>\n      </li>\n    </ul>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/routing-tour/start/server/page.tsx",
    "content": "import Link from 'next/link'\n\nexport default function ServerStartPage() {\n  return (\n    <ul>\n      <li>\n        <Link href=\"/app/routing-tour/a?from=start.server\">\n          a (server, prefetch)\n        </Link>\n      </li>\n      <li>\n        <Link href=\"/app/routing-tour/b?from=start.server\" prefetch={false}>\n          b (server, no prefetch)\n        </Link>\n      </li>\n      <li>\n        <Link href=\"/app/routing-tour/c?from=start.server\">\n          c (client, prefetch)\n        </Link>\n      </li>\n      <li>\n        <Link href=\"/app/routing-tour/d?from=start.server\" prefetch={false}>\n          d (client, no prefetch)\n        </Link>\n      </li>\n    </ul>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/template/page.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const [state, setState] = useQueryState('state', {\n    defaultValue: ''\n  })\n  return (\n    <>\n      <input value={state} onChange={e => setState(e.target.value)} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/transitions/client.tsx",
    "content": "'use client'\n\nimport { parseAsInteger, useQueryState } from 'nuqs'\nimport { useTransition } from 'react'\n\nexport function Client() {\n  const [isLoading, startTransition] = useTransition()\n  const [counter, setCounter] = useQueryState(\n    'counter',\n    parseAsInteger.withDefault(0).withOptions({\n      shallow: false,\n      startTransition\n    })\n  )\n  return (\n    <>\n      <p id=\"server-status\">{isLoading ? 'loading' : 'idle'}</p>\n      <button onClick={() => setCounter(counter + 1)}>{counter}</button>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/transitions/page.tsx",
    "content": "import { setTimeout } from 'node:timers/promises'\nimport type { SearchParams } from 'nuqs/server'\nimport { Suspense } from 'react'\nimport { Client } from './client'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Page({ searchParams }: PageProps) {\n  await setTimeout(1000)\n  return (\n    <>\n      <h1>Transitions</h1>\n      <pre id=\"server-rendered\">{JSON.stringify(await searchParams)}</pre>\n      <Suspense>\n        <Client />\n      </Suspense>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/useQueryState/dynamic/[route]/page.tsx",
    "content": "'use client'\n\nimport IntegrationPage from '../../page'\nexport default IntegrationPage\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/useQueryState/page.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport {\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsIndex,\n  parseAsInteger,\n  parseAsString,\n  useQueryState\n} from 'nuqs'\nimport React, { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <IntegrationPage />\n    </Suspense>\n  )\n}\n\nfunction IntegrationPage() {\n  const [numPanes, setNumPanes] = React.useState(1)\n  return (\n    <main>\n      <Link href=\"/\">⬅️ Home</Link>\n      <h1>useQueryState integration test</h1>\n      <nav>\n        <button onClick={() => setNumPanes(n => n + 1)}>+</button>\n        <button onClick={() => setNumPanes(n => n - 1)}>-</button>\n      </nav>\n      <div style={{ display: 'flex', gap: '2rem', flexWrap: 'wrap' }}>\n        {Array.from({ length: numPanes }).map((_, i) => (\n          <Pane key={i} />\n        ))}\n      </div>\n    </main>\n  )\n}\n\nconst Pane = () => {\n  const router = useRouter()\n  return (\n    <div>\n      <nav>\n        Links&nbsp;\n        <Link href=\"?float=0.42\" replace scroll={false}>\n          <button>Link 0.42</button>\n        </Link>\n        <Link href=\"?float=0.47\" replace scroll={false}>\n          <button>Link 0.47</button>\n        </Link>\n        <button\n          onClick={() => router.replace('?float=0.42', { scroll: false })}\n        >\n          Router 0.42\n        </button>\n        <button\n          onClick={() => router.replace('?float=0.47', { scroll: false })}\n        >\n          Router 0.47\n        </button>\n      </nav>\n      <StringSection />\n      <IntSection />\n      <FloatSection />\n      <IndexSection />\n      <BoolSection />\n      <TextSection />\n    </div>\n  )\n}\n\n// --\n\nconst StringSection = () => {\n  const [string, setString] = useQueryState('string')\n  return (\n    <section>\n      <h2>String</h2>\n      <button id=\"string_set_a\" onClick={() => setString('a')}>\n        Set A\n      </button>\n      <button id=\"string_set_b\" onClick={() => setString('b')}>\n        Set B\n      </button>\n      <button id=\"string_clear\" onClick={() => setString(null)}>\n        Clear\n      </button>\n      <p id=\"string_value\">{string}</p>\n    </section>\n  )\n}\n\nconst IntSection = () => {\n  const [int, setInt] = useQueryState('int', parseAsInteger)\n  return (\n    <section>\n      <h2>Integer</h2>\n      <button id=\"int_decrement\" onClick={() => setInt(old => (old ?? 0) - 1)}>\n        -1\n      </button>\n      <button id=\"int_increment\" onClick={() => setInt(old => (old ?? 0) + 1)}>\n        +1\n      </button>\n      <button id=\"int_clear\" onClick={() => setInt(null)}>\n        Clear\n      </button>\n      <p id=\"int_value\">{int}</p>\n    </section>\n  )\n}\n\nconst FloatSection = () => {\n  const [float, setFloat] = useQueryState('float', parseAsFloat)\n  return (\n    <section>\n      <h2>Float</h2>\n      <input\n        style={{ display: 'block' }}\n        type=\"range\"\n        value={float ?? 0}\n        onChange={e => setFloat(e.target.valueAsNumber)}\n        min={-1}\n        max={1}\n        step={0.0001}\n      />\n      <button\n        id=\"float_decrement\"\n        onClick={() => setFloat(x => (x ?? 0) - 0.1)}\n      >\n        -0.1\n      </button>\n      <button\n        id=\"float_increment\"\n        onClick={() => setFloat(x => (x ?? 0) + 0.1)}\n      >\n        +0.1\n      </button>\n      <button id=\"float_clear\" onClick={() => setFloat(null)}>\n        Clear\n      </button>\n      <p id=\"float_value\">{float}</p>\n    </section>\n  )\n}\n\nconst IndexSection = () => {\n  const [index, setIndex] = useQueryState('index', parseAsIndex)\n  return (\n    <section>\n      <h2>Index</h2>\n      <button\n        id=\"index_decrement\"\n        onClick={() => setIndex(old => (old ?? 0) - 1)}\n      >\n        -1\n      </button>\n      <button\n        id=\"index_increment\"\n        onClick={() => setIndex(old => (old ?? 0) + 1)}\n      >\n        +1\n      </button>\n      <button id=\"index_clear\" onClick={() => setIndex(null)}>\n        Clear\n      </button>\n      <p id=\"index_value\">{index}</p>\n    </section>\n  )\n}\n\nconst BoolSection = () => {\n  const [bool, setBool] = useQueryState('bool', parseAsBoolean)\n  return (\n    <section>\n      <h2>Boolean</h2>\n      <button id=\"bool_toggle\" onClick={() => setBool(old => !old)}>\n        Toggle\n      </button>\n      <button id=\"bool_clear\" onClick={() => setBool(null)}>\n        Clear\n      </button>\n      <p id=\"bool_value\">{bool === null ? null : bool ? 'true' : 'false'}</p>\n    </section>\n  )\n}\n\nconst TextSection = () => {\n  const [text, setText] = useQueryState(\n    'text',\n    parseAsString.withDefault('Hello, world!')\n  )\n  return (\n    <section>\n      <h2>Text</h2>\n      <input type=\"text\" value={text} onChange={e => setText(e.target.value)} />\n      <p>{text}</p>\n    </section>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/useQueryStates/dynamic/[route]/page.tsx",
    "content": "'use client'\n\nimport IntegrationPage from '../../page'\nexport default IntegrationPage\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/useQueryStates/page.tsx",
    "content": "'use client'\n\nimport Link from 'next/link'\nimport {\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsIndex,\n  parseAsInteger,\n  parseAsString,\n  useQueryStates\n} from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <IntegrationPage />\n    </Suspense>\n  )\n}\n\nfunction IntegrationPage() {\n  const [state, setState] = useQueryStates({\n    string: parseAsString,\n    int: parseAsInteger,\n    float: parseAsFloat,\n    index: parseAsIndex,\n    bool: parseAsBoolean\n  })\n  return (\n    <>\n      <button onClick={() => setState({ string: 'Hello' })}>Set string</button>\n      <button onClick={() => setState({ int: 42 })}>Set int</button>\n      <button onClick={() => setState({ float: 3.14159 })}>Set float</button>\n      <button onClick={() => setState({ index: 8 })}>Set index</button>\n      <button onClick={() => setState(old => ({ bool: !old.bool }))}>\n        Toggle bool\n      </button>\n      <button id=\"clear-string\" onClick={() => setState({ string: null })}>\n        Clear string\n      </button>\n      <button\n        id=\"clear\"\n        onClick={() =>\n          setState({\n            string: null,\n            int: null,\n            float: null,\n            index: null,\n            bool: null\n          })\n        }\n      >\n        Clear\n      </button>\n      <nav>\n        <Link href=\"?string=Hello&int=42\">Link A</Link>\n        <Link href=\"?string=World&int=47\">Link B</Link>\n      </nav>\n      <p id=\"json\">{JSON.stringify(state)}</p>\n      <p id=\"string\">{state.string}</p>\n      <p id=\"int\">{state.int}</p>\n      <p id=\"float\">{state.float}</p>\n      <p id=\"index\">{state.index}</p>\n      <p id=\"bool\">\n        {state.bool === null ? null : state.bool ? 'true' : 'false'}\n      </p>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/useQueryStates-clear-all/page.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryStates } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const [, setValues] = useQueryStates({\n    a: parseAsString.withDefault(''),\n    b: parseAsString.withDefault('')\n  })\n  return (\n    <>\n      <button onClick={() => setValues(null)}>Clear</button>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/app/useSearchParams/page.tsx",
    "content": "'use client'\n\nimport { useSearchParams } from 'next/navigation'\nimport { parseAsBoolean, useQueryState } from 'nuqs'\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <Suspense>\n      <Client />\n    </Suspense>\n  )\n}\n\nfunction Client() {\n  const [q, setQ] = useQueryState('q', { defaultValue: '' })\n  const [, setPush] = useQueryState(\n    'push',\n    parseAsBoolean.withDefault(false).withOptions({ history: 'push' })\n  )\n  const searchParams = useSearchParams()\n  return (\n    <div>\n      <input type=\"text\" value={q} onChange={e => setQ(e.target.value)} />\n      <pre id=\"searchParams\">{searchParams?.toString() ?? 'null'}</pre>\n      <button onClick={() => setPush(true)}>Push</button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/layout.tsx",
    "content": "import { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport { Fragment, type ReactNode, Suspense } from 'react'\nimport { Providers } from './providers'\n\nexport const metadata = {\n  title: 'nuqs e2e test bench'\n}\n\nconst SuspenseIfCacheComponents =\n  process.env.CACHE_COMPONENTS === 'true' ? Suspense : Fragment\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n  return (\n    <html>\n      <body>\n        <Suspense>\n          <HydrationMarker />\n        </Suspense>\n        <NuqsAdapter>\n          <Providers>\n            <SuspenseIfCacheComponents>{children}</SuspenseIfCacheComponents>\n          </Providers>\n        </NuqsAdapter>\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/page.tsx",
    "content": "import Link from 'next/link'\n\nexport default function IndexPage() {\n  return (\n    <main>\n      <h1>End-to-end integration tests</h1>\n      <p>⚠️ Don't change these routes without updating integration tests.</p>\n      <h2>App router</h2>\n      <ul>\n        <li>\n          <Link href=\"/app/useQueryState\">[static] useQueryState</Link>\n        </li>\n        <li>\n          <Link href=\"/app/useQueryState/dynamic/foo\">\n            [dynamic] useQueryState\n          </Link>\n        </li>\n        <li>\n          <Link href=\"/app/useQueryStates\">[static] useQueryStates</Link>\n        </li>\n        <li>\n          <Link href=\"/app/useQueryStates/dynamic/foo\">\n            [dynamic] useQueryStates\n          </Link>\n        </li>\n        <li>\n          <Link href=\"/app/routing-tour/start/server\">\n            Routing tour starting with server index\n          </Link>\n        </li>\n        <li>\n          <Link href=\"/app/routing-tour/start/client\">\n            Routing tour starting with client index\n          </Link>\n        </li>\n      </ul>\n      <h2>Pages router</h2>\n      <ul>\n        <li>\n          <Link href=\"/pages/useQueryState\">[static] useQueryState</Link>\n        </li>\n        <li>\n          <Link href=\"/pages/useQueryState/dynamic/foo\">\n            [dynamic] useQueryState\n          </Link>\n        </li>\n        <li>\n          <Link href=\"/pages/useQueryStates\">[static] useQueryStates</Link>\n        </li>\n        <li>\n          <Link href=\"/pages/useQueryStates/dynamic/foo\">\n            [dynamic] useQueryStates\n          </Link>\n        </li>\n      </ul>\n    </main>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/app/providers.tsx",
    "content": "'use client'\n\nimport { LinkProvider } from 'e2e-shared/components/link'\nimport { type Router, RouterProvider } from 'e2e-shared/components/router'\nimport Link from 'next/link'\nimport { useRouter as useNextRouter } from 'next/navigation'\nimport { type ReactNode } from 'react'\n\nexport function Providers({ children }: { children: ReactNode }) {\n  return (\n    <LinkProvider Link={Link}>\n      <RouterProvider useRouter={useRouter}>{children}</RouterProvider>\n    </LinkProvider>\n  )\n}\n\nfunction useRouter(): Router {\n  const router = useNextRouter()\n  return {\n    replace(url, { shallow }) {\n      if (shallow) {\n        history.replaceState(null, '', url)\n      } else {\n        router.replace(url)\n      }\n    },\n    push(url, { shallow }) {\n      if (shallow) {\n        history.pushState(null, '', url)\n      } else {\n        router.push(url)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/components/pages-ready-wrapper.tsx",
    "content": "import { useRouter } from 'next/router'\n\nexport function withPagesReadyWrapper(Component: React.ComponentType) {\n  return function PagesReadyWrapper(props: any) {\n    const router = useRouter()\n    if (!router?.isReady) {\n      return null\n    }\n    return <Component {...props} />\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/middleware.ts",
    "content": "import { NextRequest, NextResponse } from 'next/server'\n\nexport const config = {\n  matcher: ['/app/multitenant', '/pages/multitenant']\n}\n\nexport default async function proxy(req: NextRequest) {\n  // https://media1.tenor.com/m/YrcMb6KRczsAAAAC/doctor-who-dr-who.gif\n  const tenant = 'david'\n  const pathname = req.nextUrl.pathname\n  if (pathname === '/app/multitenant') {\n    const url = new URL(\n      `${req.nextUrl.basePath}/app/multitenant/${tenant}`,\n      req.url\n    )\n    url.search = req.nextUrl.search\n    return NextResponse.rewrite(url)\n  }\n  if (pathname === '/pages/multitenant') {\n    const url = new URL(\n      `${req.nextUrl.basePath}/pages/multitenant/${tenant}`,\n      req.url\n    )\n    url.search = req.nextUrl.search\n    return NextResponse.rewrite(url)\n  }\n  return NextResponse.next()\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/_app.tsx",
    "content": "import { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider } from 'e2e-shared/components/link'\nimport { type Router, RouterProvider } from 'e2e-shared/components/router'\nimport type { AppProps } from 'next/app'\nimport Link from 'next/link'\nimport { useRouter as useNextRouter } from 'next/router'\nimport { NuqsAdapter } from 'nuqs/adapters/next/pages'\n\nexport default function MyApp({ Component, pageProps }: AppProps) {\n  return (\n    <>\n      <HydrationMarker />\n      <NuqsAdapter>\n        <LinkProvider Link={Link}>\n          <RouterProvider useRouter={useRouter}>\n            <Component {...pageProps} />\n          </RouterProvider>\n        </LinkProvider>\n      </NuqsAdapter>\n    </>\n  )\n}\n\nfunction useRouter(): Router {\n  const router = useNextRouter()\n  return {\n    replace(url, options) {\n      router.replace(url, url, { shallow: options.shallow })\n    },\n    push(url, options) {\n      router.push(url, url, { shallow: options.shallow })\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/api/pages/loader.ts",
    "content": "import { loadSearchParams } from 'e2e-shared/specs/loader'\nimport type { NextApiRequest, NextApiResponse } from 'next'\n\nexport default function handler(\n  request: NextApiRequest,\n  response: NextApiResponse\n) {\n  const { test, int } = loadSearchParams(request.query)\n  response\n    .status(200)\n    .setHeader('content-type', 'text/html')\n    .send(\n      `<!doctype html>\n<html>\n<body>\n    <div id=\"hydration-marker\" style=\"display:none;\" aria-hidden>hydrated</div>\n    <pre id=\"test\">${test}</pre>\n    <pre id=\"int\">${int}</pre>\n</body>\n</html>`\n    )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/agnostic/basic-io.tsx",
    "content": "import { withPagesReadyWrapper } from '@/components/pages-ready-wrapper'\nimport { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\nimport { NuqsAdapter } from 'nuqs/adapters/next'\n\nconst BasicIO = withPagesReadyWrapper(UseQueryStateBasicIO)\n\nexport default function Page() {\n  return (\n    <NuqsAdapter>\n      <BasicIO />\n    </NuqsAdapter>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/basic-io/useQueryState.tsx",
    "content": "import { withPagesReadyWrapper } from '@/components/pages-ready-wrapper'\nimport { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default withPagesReadyWrapper(UseQueryStateBasicIO)\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/basic-io/useQueryStates.tsx",
    "content": "import { withPagesReadyWrapper } from '@/components/pages-ready-wrapper'\nimport { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default withPagesReadyWrapper(UseQueryStatesBasicIO)\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/conditional-rendering/useQueryState.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryState\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/conditional-rendering/useQueryStates.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryStates\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/debounce/index.tsx",
    "content": "import { DebounceClient } from 'e2e-shared/specs/debounce-client'\nimport { DebounceServer } from 'e2e-shared/specs/debounce-server'\nimport {\n  DemoSearchParams,\n  loadDemoSearchParams\n} from 'e2e-shared/specs/debounce.defs'\nimport { GetServerSidePropsContext } from 'next'\n\nexport default function Page(serverState: DemoSearchParams) {\n  return (\n    <DebounceServer state={serverState}>\n      <DebounceClient navigateHref=\"/pages/debounce/other\" />\n    </DebounceServer>\n  )\n}\n\nexport async function getServerSideProps(ctx: GetServerSidePropsContext) {\n  const serverState = loadDemoSearchParams(ctx.query)\n  return {\n    props: serverState\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/debounce/other.tsx",
    "content": "import Link from 'next/link'\n\nexport default function Page() {\n  return <Link href=\"/pages/debounce\">Back to the previous page</Link>\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/dynamic-segments/catch-all/[...segments].tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'\nimport { useRouter } from 'next/router'\n\ntype Props = {\n  serverState: string | null\n  serverSegments: string[]\n}\n\nexport default function Page({ serverState, serverSegments }: Props) {\n  const router = useRouter()\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments\n        environment=\"client\"\n        segments={router.query.segments as string[]}\n      />\n    </>\n  )\n}\n\nexport function getServerSideProps(\n  ctx: GetServerSidePropsContext\n): GetServerSidePropsResult<Props> {\n  const serverState = (ctx.query.test as string) ?? null\n  const serverSegments = ctx.params?.segments as string[]\n  return {\n    props: {\n      serverState,\n      serverSegments\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/dynamic-segments/dynamic/[segment].tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'\nimport { useRouter } from 'next/router'\n\ntype Props = {\n  serverState: string | null\n  serverSegments: string[]\n}\n\nexport default function Page({ serverState, serverSegments }: Props) {\n  const router = useRouter()\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments\n        environment=\"client\"\n        segments={[router.query.segment as string]}\n      />\n    </>\n  )\n}\n\nexport function getServerSideProps(\n  ctx: GetServerSidePropsContext\n): GetServerSidePropsResult<Props> {\n  const serverState = (ctx.query.test as string) ?? null\n  const serverSegments = [ctx.params?.segment as string]\n  return {\n    props: {\n      serverState,\n      serverSegments\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/dynamic-segments/optional-catch-all/[[...segments]].tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'\nimport { useRouter } from 'next/router'\n\ntype Props = {\n  serverState: string | null\n  serverSegments: string[] | null\n}\n\nexport default function Page({ serverState, serverSegments }: Props) {\n  const router = useRouter()\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments\n        environment=\"server\"\n        segments={serverSegments ?? undefined}\n      />\n      <DisplaySegments\n        environment=\"client\"\n        segments={router.query.segments as string[]}\n      />\n    </>\n  )\n}\n\nexport function getServerSideProps(\n  ctx: GetServerSidePropsContext\n): GetServerSidePropsResult<Props> {\n  const serverState = (ctx.query.test as string) ?? null\n  const serverSegments = (ctx.params?.segments as string[]) ?? null // Can't serialize undefined\n  return {\n    props: {\n      serverState,\n      serverSegments\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/flush-after-navigate/useQueryState/end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/flush-after-navigate/useQueryState/start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStateStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStateStart path=\"/pages/flush-after-navigate/useQueryState\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/flush-after-navigate/useQueryStates/end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/flush-after-navigate/useQueryStates/start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStatesStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStatesStart path=\"/pages/flush-after-navigate/useQueryStates\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/form/useQueryState.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryState\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/form/useQueryStates.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryStates\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/hash-preservation/dynamic/[route]/index.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/hash-preservation/index.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/history-sync.tsx",
    "content": "import { withPagesReadyWrapper } from '@/components/pages-ready-wrapper'\nimport { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport default withPagesReadyWrapper(HistorySync)\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/json.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\n\nexport default Json\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/life-and-death.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport default LifeAndDeath\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/linking/useQueryState/index.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/pages/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/linking/useQueryState/other.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/pages/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/linking/useQueryStates/index.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/pages/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/linking/useQueryStates/other.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/pages/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/loader.tsx",
    "content": "import {\n  type SearchParams,\n  LoaderRenderer,\n  loadSearchParams\n} from 'e2e-shared/specs/loader'\nimport type { GetServerSidePropsContext } from 'next'\n\ntype PageProps = {\n  serverValues: SearchParams\n}\n\nexport default function Page({ serverValues }: PageProps) {\n  return <LoaderRenderer serverValues={serverValues} />\n}\n\nexport async function getServerSideProps({ query }: GetServerSidePropsContext) {\n  return {\n    props: {\n      serverValues: loadSearchParams(query)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/middleware.tsx",
    "content": "export default function MiddlewarePage() {\n  return (\n    <div>\n      <h1>Client</h1>\n      <p>Pages router</p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/multitenant/[tenant].tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\nimport type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'\nimport { useParams } from 'next/navigation'\nimport { useRouter } from 'next/router'\n\ntype Props = {\n  serverState: string | null\n  tenant: string | null\n}\n\nexport default function Page({ serverState, tenant }: Props) {\n  const params = useParams()\n  const router = useRouter()\n  return (\n    <>\n      <ShallowUseQueryState />\n      <Display environment=\"server\" state={serverState} />\n      <p id=\"server-tenant\">{tenant}</p>\n      <p id=\"client-tenant\">{params?.tenant}</p>\n      <p id=\"router-pathname\">{router.pathname}</p>\n    </>\n  )\n}\n\nexport function getServerSideProps(\n  ctx: GetServerSidePropsContext\n): GetServerSidePropsResult<Props> {\n  const tenant = ctx.params?.tenant as string | null\n\n  if (!tenant) {\n    return {\n      notFound: true\n    }\n  }\n\n  const serverState = (ctx.query.test as string) ?? null\n\n  return {\n    props: {\n      serverState,\n      tenant\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/native-array.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\n\nexport default NativeArray\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/popstate-queue-reset/index.tsx",
    "content": "import { PopstateQueueResetClient } from 'e2e-shared/specs/popstate-queue-reset'\nimport { useRouter } from 'next/router'\n\nexport default function Page() {\n  const router = useRouter()\n  return (\n    <PopstateQueueResetClient\n      onNavigateToOther={() => router.push('/pages/popstate-queue-reset/other')}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/popstate-queue-reset/other.tsx",
    "content": "import { PopstateQueueResetOther } from 'e2e-shared/specs/popstate-queue-reset'\n\nexport default PopstateQueueResetOther\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/pretty-urls.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport default PrettyUrls\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/push/index.tsx",
    "content": "import { GetServerSideProps } from 'next'\nimport { Client } from '../../../app/app/push/client'\nimport { parser } from '../../../app/app/push/searchParams'\n\nexport default function Page({ server }: { server: number }) {\n  return (\n    <>\n      <p>\n        Server side: <span id=\"server-side\">{server}</span>\n      </p>\n      <Client />\n    </>\n  )\n}\n\nexport const getServerSideProps = (async ctx => {\n  const server = parser.parseServerSide(ctx.query.server)\n  return {\n    props: {\n      server\n    }\n  }\n}) satisfies GetServerSideProps<{\n  server: number\n}>\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/push/useQueryState/dynamic/[route]/index.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/push/useQueryState/index.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/push/useQueryStates/dynamic/[route]/index.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/push/useQueryStates/index.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/rate-limits.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\n\nexport default RateLimits\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/referential-stability/useQueryState.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryState\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/referential-stability/useQueryStates.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryStates\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/render-count/[hook]/[shallow]/[history]/[startTransition]/index.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport {\n  loadParams,\n  loadSearchParams,\n  type Params\n} from 'e2e-shared/specs/render-count.params'\nimport { GetServerSideProps } from 'next'\nimport { setTimeout } from 'node:timers/promises'\n\nexport default RenderCount\n\n// We need SSR to get the correct initial render counts\n// otherwise with SSG we get at least one extra render for hydration.\nexport const getServerSideProps: GetServerSideProps<Params> = async ({\n  params,\n  query\n}) => {\n  const { delay } = loadSearchParams(query)\n  if (delay) {\n    await setTimeout(delay)\n  }\n  return {\n    props: loadParams(params!)\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-1099/useQueryState.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryState\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-1099/useQueryStates.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryStates\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-1293/a.tsx",
    "content": "import { Repro1293PageA } from 'e2e-shared/specs/repro-1293'\n\nexport default function Page() {\n  return <Repro1293PageA linkHref=\"/pages/repro-1293/b\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-1293/b.tsx",
    "content": "import { Repro1293PageB } from 'e2e-shared/specs/repro-1293'\n\nexport default Repro1293PageB\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-1365.tsx",
    "content": "import { Repro1365 } from 'e2e-shared/specs/repro-1365'\n\nexport default Repro1365\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-359.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\n\nexport default Repro359\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/repro-982.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\n\nexport default Repro982\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/routing/useQueryState/index.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/pages/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/routing/useQueryState/other.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/pages/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/routing/useQueryStates/index.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/pages/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/routing/useQueryStates/other.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/pages/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/scroll.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\n\nexport default Scroll\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/shallow/useQueryState.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\nimport type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'\n\ntype Props = {\n  serverState: string | null\n}\n\nexport default function Page({ serverState }: Props) {\n  return (\n    <>\n      <ShallowUseQueryState />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n\nexport function getServerSideProps(\n  ctx: GetServerSidePropsContext\n): GetServerSidePropsResult<Props> {\n  return {\n    props: {\n      serverState: (ctx.query.test ?? null) as string | null\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/shallow/useQueryStates.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\nimport type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'\n\ntype Props = {\n  serverState: string | null\n}\n\nexport default function Page({ serverState }: Props) {\n  return (\n    <>\n      <ShallowUseQueryStates />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n\nexport function getServerSideProps(\n  ctx: GetServerSidePropsContext\n): GetServerSidePropsResult<Props> {\n  return {\n    props: {\n      serverState: (ctx.query.test ?? null) as string | null\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/stitching.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\n\nexport default Stitching\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/useQueryState/dynamic/[route]/index.tsx",
    "content": "import IntegrationPage, { getServerSideProps } from '../../index'\nexport default IntegrationPage\nexport { getServerSideProps }\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/useQueryState/index.tsx",
    "content": "import { GetServerSideProps } from 'next'\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\nimport {\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsIndex,\n  parseAsInteger,\n  parseAsString,\n  useQueryState\n} from 'nuqs'\n\nexport const getServerSideProps = (async ctx => {\n  const string = parseAsString.parseServerSide(ctx.query.string)\n  return {\n    props: {\n      string\n    }\n  }\n}) satisfies GetServerSideProps<{\n  string: string | null\n}>\n\nconst IntegrationPage = () => {\n  const [string, setString] = useQueryState('string')\n  const [int, setInt] = useQueryState('int', parseAsInteger)\n  const [float, setFloat] = useQueryState('float', parseAsFloat)\n  const [index, setIndex] = useQueryState('index', parseAsIndex)\n  const [bool, setBool] = useQueryState('bool', parseAsBoolean)\n  const [text, setText] = useQueryState(\n    'text',\n    parseAsString.withDefault('Hello, world!')\n  )\n  const pathname = usePathname()\n  return (\n    <main>\n      <h1>useQueryState</h1>\n      <nav>\n        Links&nbsp;\n        <Link\n          href={{\n            pathname,\n            query: {\n              float: 0.42\n            }\n          }}\n        >\n          <button>0.42</button>\n        </Link>\n        <Link href=\"?float=0.47\">\n          <button>0.47</button>\n        </Link>\n      </nav>\n      <section>\n        <h2>String</h2>\n        <button\n          id=\"string_set_a\"\n          onClick={() => setString('a', { shallow: false })}\n        >\n          Set A\n        </button>\n        <button id=\"string_set_b\" onClick={() => setString('b')}>\n          Set B\n        </button>\n        <button id=\"string_clear\" onClick={() => setString(null)}>\n          Clear\n        </button>\n        <p id=\"string_value\">{string}</p>\n      </section>\n      <section>\n        <h2>Integer</h2>\n        <button\n          id=\"int_increment\"\n          onClick={() => setInt(old => (old ?? 0) + 1)}\n        >\n          Increment\n        </button>\n        <button\n          id=\"int_decrement\"\n          onClick={() => setInt(old => (old ?? 0) - 1)}\n        >\n          Decrement\n        </button>\n        <button id=\"int_clear\" onClick={() => setInt(null)}>\n          Clear\n        </button>\n        <p id=\"int_value\">{int}</p>\n      </section>\n      <section>\n        <h2>Float</h2>\n        <button\n          id=\"float_increment\"\n          onClick={() => setFloat(old => (old ?? 0) + 0.1)}\n        >\n          Increment by 0.1\n        </button>\n        <button\n          id=\"float_decrement\"\n          onClick={() => setFloat(old => (old ?? 0) - 0.1)}\n        >\n          Decrement by 0.1\n        </button>\n        <button id=\"float_clear\" onClick={() => setFloat(null)}>\n          Clear\n        </button>\n        <input\n          type=\"range\"\n          value={float ?? 0}\n          onChange={e => setFloat(e.target.valueAsNumber)}\n          min={-1}\n          max={1}\n          step={0.0001}\n        />\n        <p id=\"float_value\">{float}</p>\n      </section>\n      <section>\n        <h2>Index</h2>\n        <button\n          id=\"index_increment\"\n          onClick={() => setIndex(old => (old ?? 0) + 1)}\n        >\n          Increment\n        </button>\n        <button\n          id=\"index_decrement\"\n          onClick={() => setIndex(old => (old ?? 0) - 1)}\n        >\n          Decrement\n        </button>\n        <button id=\"index_clear\" onClick={() => setIndex(null)}>\n          Clear\n        </button>\n        <p id=\"index_value\">{index}</p>\n      </section>\n      <section>\n        <h2>Boolean</h2>\n        <button id=\"bool_toggle\" onClick={() => setBool(old => !old)}>\n          Toggle\n        </button>\n        <button id=\"bool_clear\" onClick={() => setBool(null)}>\n          Clear\n        </button>\n        <p id=\"bool_value\">{bool === null ? null : bool ? 'true' : 'false'}</p>\n      </section>\n      <section>\n        <h2>Text</h2>\n        <input\n          type=\"text\"\n          value={text}\n          onChange={e => setText(e.target.value)}\n        />\n        <p>{text}</p>\n      </section>\n    </main>\n  )\n}\n\nexport default IntegrationPage\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/useQueryStates/dynamic/[route]/index.tsx",
    "content": "import IntegrationPage from '../../index'\nexport default IntegrationPage\n"
  },
  {
    "path": "packages/e2e/next/src/pages/pages/useQueryStates/index.tsx",
    "content": "import Link from 'next/link'\nimport {\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsIndex,\n  parseAsInteger,\n  parseAsString,\n  useQueryStates\n} from 'nuqs'\n\nconst IntegrationPage = () => {\n  const [state, setState] = useQueryStates({\n    string: parseAsString,\n    int: parseAsInteger,\n    float: parseAsFloat,\n    index: parseAsIndex,\n    bool: parseAsBoolean\n  })\n  return (\n    <>\n      <button onClick={() => setState({ string: 'Hello' })}>Set string</button>\n      <button onClick={() => setState({ int: 42 })}>Set int</button>\n      <button onClick={() => setState({ float: 3.14159 })}>Set float</button>\n      <button onClick={() => setState({ index: 8 })}>Set index</button>\n      <button onClick={() => setState(old => ({ bool: !old.bool }))}>\n        Toggle bool\n      </button>\n      <button\n        id=\"clear-string\"\n        onClick={() => setState(() => ({ string: null }))}\n      >\n        Clear string\n      </button>\n      <button\n        id=\"clear\"\n        onClick={() =>\n          setState(() => ({\n            string: null,\n            int: null,\n            float: null,\n            index: null,\n            bool: null\n          }))\n        }\n      >\n        Clear\n      </button>\n      <nav>\n        <Link href=\"?string=Hello&int=42\">Link A</Link>\n        <Link href=\"?string=World&int=47\">Link B</Link>\n      </nav>\n      <p id=\"json\">{JSON.stringify(state)}</p>\n      <p id=\"string\">{state.string}</p>\n      <p id=\"int\">{state.int}</p>\n      <p id=\"float\">{state.float}</p>\n      <p id=\"index\">{state.index}</p>\n      <p id=\"bool\">\n        {state.bool === null ? null : state.bool ? 'true' : 'false'}\n      </p>\n    </>\n  )\n}\n\nexport default IntegrationPage\n"
  },
  {
    "path": "packages/e2e/next/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    // Type checking\n    \"strict\": true,\n    \"alwaysStrict\": false, // Don't emit \"use strict\" to avoid conflicts with \"use client\"\n    // Modules\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    // Language & Environment\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    // Emit\n    \"noEmit\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"jsx\": \"react-jsx\",\n    // Interop\n    \"allowJs\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"allowImportingTsExtensions\": true,\n    // Misc\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"incremental\": true,\n    \"tsBuildInfoFile\": \".next/cache/.tsbuildinfo\",\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/e2e/next/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\".next/**\", \"!.next/cache/**\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\n        \"BASE_PATH\",\n        \"REACT_COMPILER\",\n        \"CACHE_COMPONENTS\",\n        \"E2E_NO_CACHE_ON_RERUN\"\n      ]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\n        \"BASE_PATH\",\n        \"REACT_COMPILER\",\n        \"CACHE_COMPONENTS\",\n        \"E2E_NO_CACHE_ON_RERUN\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/package.json",
    "content": "{\n  \"name\": \"e2e\",\n  \"version\": \"0.0.0\",\n  \"private\": true\n}"
  },
  {
    "path": "packages/e2e/react/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Playwright\n.playwright/"
  },
  {
    "path": "packages/e2e/react/README.md",
    "content": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.\n\nCurrently, two official plugins are available:\n\n- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh\n- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh\n\n## Expanding the ESLint configuration\n\nIf you are developing a production application, we recommend updating the configuration to enable type aware lint rules:\n\n- Configure the top-level `parserOptions` property like this:\n\n```js\nexport default tseslint.config({\n  languageOptions: {\n    // other options...\n    parserOptions: {\n      project: ['./tsconfig.node.json', './tsconfig.app.json'],\n      tsconfigRootDir: import.meta.dirname,\n    },\n  },\n})\n```\n\n- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`\n- Optionally add `...tseslint.configs.stylisticTypeChecked`\n- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:\n\n```js\n// eslint.config.js\nimport react from 'eslint-plugin-react'\n\nexport default tseslint.config({\n  // Set the react version\n  settings: { react: { version: '18.3' } },\n  plugins: {\n    // Add the react plugin\n    react,\n  },\n  rules: {\n    // other rules...\n    // Enable its recommended rules\n    ...react.configs.recommended.rules,\n    ...react.configs['jsx-runtime'].rules,\n  },\n})\n```\n"
  },
  {
    "path": "packages/e2e/react/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React + TS</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/e2e/react/package.json",
    "content": "{\n  \"name\": \"e2e-react\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --port 3002\",\n    \"build\": \"vite build\",\n    \"start\": \"vite preview --port 3002\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:types\": \"tsc -b\",\n    \"test:playwright\": \"playwright test --project=chromium\"\n  },\n  \"dependencies\": {\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@vitejs/plugin-react\": \"^5.1.3\",\n    \"e2e-shared\": \"workspace:*\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'pnpm run start',\n  port: 3002\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/key-isolation.spec.ts",
    "content": "import { testKeyIsolation } from 'e2e-shared/specs/key-isolation.spec.ts'\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/push/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestPush({\n  path: '/push/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/referential-stability.spec.ts",
    "content": "import { testReferentialStability } from 'e2e-shared/specs/referential-stability.spec.ts'\n\ntestReferentialStability({\n  path: '/referential-stability/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestReferentialStability({\n  path: '/referential-stability/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/render-count.spec.ts",
    "content": "import { testRenderCount } from 'e2e-shared/specs/render-count.spec.ts'\n\nconst hooks = ['useQueryState', 'useQueryStates'] as const\nconst shallows = [true, false] as const\nconst histories = ['replace', 'push'] as const\n\n// FPN makes render count checking unreliable\nif (process.env.FULL_PAGE_NAV_ON_SHALLOW_FALSE !== 'true') {\n  for (const hook of hooks) {\n    for (const shallow of shallows) {\n      for (const history of histories) {\n        for (const startTransition of [false, true]) {\n          testRenderCount({\n            path: `/render-count/${hook}/${shallow}/${history}/${startTransition}`,\n            hook,\n            props: {\n              shallow,\n              history,\n              startTransition\n            },\n            expected: {\n              mount: 1,\n              update: 2\n            }\n          })\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/repro-1099/useQueryState',\n  hook: 'useQueryState',\n  shallowOptions: [true]\n})\n\ntestRepro1099({\n  path: '/repro-1099/useQueryStates',\n  hook: 'useQueryStates',\n  shallowOptions: [true]\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/repro-1365.spec.ts",
    "content": "import { testRepro1365 } from 'e2e-shared/specs/repro-1365.spec.ts'\n\ntestRepro1365({\n  path: '/repro-1365'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/repro-359.spec.ts",
    "content": "import { testRepro359 } from 'e2e-shared/specs/repro-359.spec.ts'\n\ntestRepro359({\n  path: '/repro-359'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/repro-982.spec.ts",
    "content": "import { testRepro982 } from 'e2e-shared/specs/repro-982.spec.ts'\n\ntestRepro982({\n  path: '/repro-982'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/routing.spec.ts",
    "content": "import { testRouting } from 'e2e-shared/specs/routing.spec.ts'\n\ntestRouting({\n  path: '/routing/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRouting({\n  path: '/routing/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/scroll.spec.ts",
    "content": "import { testScroll } from 'e2e-shared/specs/scroll.spec.ts'\n\ntestScroll({ path: '/scroll' })\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/shallow.spec.ts",
    "content": "import { testShallow } from 'e2e-shared/specs/shallow.spec.ts'\n\ntestShallow({\n  path: '/shallow/useQueryState',\n  hook: 'useQueryState',\n  supportsSSR: false\n})\n\ntestShallow({\n  path: '/shallow/useQueryStates',\n  hook: 'useQueryStates',\n  supportsSSR: false\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared/stitching.spec.ts",
    "content": "import { testStitching } from 'e2e-shared/specs/stitching.spec.ts'\n\ntestStitching({\n  path: '/stitching',\n  enableShallowFalse: process.env.FULL_PAGE_NAV_ON_SHALLOW_FALSE !== 'true'\n})\n"
  },
  {
    "path": "packages/e2e/react/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests()\n"
  },
  {
    "path": "packages/e2e/react/src/layout.tsx",
    "content": "import { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider, type LinkProps } from 'e2e-shared/components/link'\nimport { RouterProvider, type Router } from 'e2e-shared/components/router'\nimport type { ReactNode } from 'react'\n\nfunction Link({ href, ...props }: LinkProps) {\n  return <a href={href} {...props} />\n}\n\nexport function RootLayout({ children }: { children: ReactNode }) {\n  return (\n    <>\n      <HydrationMarker />\n      <LinkProvider Link={Link}>\n        <RouterProvider useRouter={useRouter}>{children}</RouterProvider>\n      </LinkProvider>\n    </>\n  )\n}\n\nfunction useRouter(): Router {\n  return {\n    replace(url, options) {\n      if (options.shallow) {\n        history.replaceState(history.state, '', url)\n      } else {\n        location.replace(url)\n      }\n    },\n    push(url, options) {\n      if (options.shallow) {\n        history.pushState(history.state, '', url)\n      } else {\n        location.assign(url)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react/src/main.tsx",
    "content": "import { NuqsAdapter, enableHistorySync } from 'nuqs/adapters/react'\nimport { createRoot } from 'react-dom/client'\nimport { RootLayout } from './layout'\nimport { Router } from './routes'\n\nenableHistorySync()\n\ncreateRoot(document.getElementById('root')!).render(\n  // <StrictMode>\n  <NuqsAdapter\n    fullPageNavigationOnShallowFalseUpdates={\n      process.env.FULL_PAGE_NAV_ON_SHALLOW_FALSE === 'true'\n    }\n  >\n    <RootLayout>\n      <Router />\n    </RootLayout>\n  </NuqsAdapter>\n  // </StrictMode>\n)\n"
  },
  {
    "path": "packages/e2e/react/src/routes/basic-io.useQueryState.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStateBasicIO\n"
  },
  {
    "path": "packages/e2e/react/src/routes/basic-io.useQueryStates.tsx",
    "content": "import { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStatesBasicIO\n"
  },
  {
    "path": "packages/e2e/react/src/routes/conditional-rendering.useQueryState.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/conditional-rendering.useQueryStates.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/form.useQueryState.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/form.useQueryStates.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/hash-preservation.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/react/src/routes/history-sync.tsx",
    "content": "import { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport default HistorySync\n"
  },
  {
    "path": "packages/e2e/react/src/routes/json.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\n\nexport default Json\n"
  },
  {
    "path": "packages/e2e/react/src/routes/key-isolation.useQueryState.tsx",
    "content": "import { KeyIsolationUseQueryState } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/key-isolation.useQueryStates.tsx",
    "content": "import { KeyIsolationUseQueryStates } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/life-and-death.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport default LifeAndDeath\n"
  },
  {
    "path": "packages/e2e/react/src/routes/linking.useQueryState.other.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/linking.useQueryState.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/linking.useQueryStates.other.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/linking.useQueryStates.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/native-array.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\n\nexport default NativeArray\n"
  },
  {
    "path": "packages/e2e/react/src/routes/pretty-urls.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport default PrettyUrls\n"
  },
  {
    "path": "packages/e2e/react/src/routes/push.useQueryState.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/push.useQueryStates.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/rate-limits.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\n\nexport default RateLimits\n"
  },
  {
    "path": "packages/e2e/react/src/routes/referential-stability.useQueryState.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/referential-stability.useQueryStates.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/render-count.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\nimport { useMemo } from 'react'\n\nexport default function Page() {\n  const params = useMemo(() => {\n    const [_, hook, shallow, history, startTransition] =\n      location.pathname.split('/')\n    return loadParams({ hook, shallow, history, startTransition })\n  }, [])\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/repro-1099.useQueryState.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/repro-1099.useQueryStates.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/repro-1365.tsx",
    "content": "import { Repro1365 } from 'e2e-shared/specs/repro-1365'\n\nexport default Repro1365\n"
  },
  {
    "path": "packages/e2e/react/src/routes/repro-359.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\n\nexport default Repro359\n"
  },
  {
    "path": "packages/e2e/react/src/routes/repro-982.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\n\nexport default Repro982\n"
  },
  {
    "path": "packages/e2e/react/src/routes/routing.useQueryState.other.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/routing.useQueryState.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/routing.useQueryStates.other.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/routing.useQueryStates.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/routes/scroll.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\n\nexport default Scroll\n"
  },
  {
    "path": "packages/e2e/react/src/routes/shallow.useQueryState.tsx",
    "content": "import { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\n\nexport default ShallowUseQueryState\n"
  },
  {
    "path": "packages/e2e/react/src/routes/shallow.useQueryStates.tsx",
    "content": "import { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\n\nexport default ShallowUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react/src/routes/stitching.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\n\nexport default Stitching\n"
  },
  {
    "path": "packages/e2e/react/src/routes.tsx",
    "content": "import { JSX, lazy } from 'react'\n\n// prettier-ignore\nconst routes: Record<string, React.LazyExoticComponent<() => JSX.Element>> = {\n  // Shared E2E tests\n  '/basic-io/useQueryState':                lazy(() => import('./routes/basic-io.useQueryState')),\n  '/basic-io/useQueryStates':               lazy(() => import('./routes/basic-io.useQueryStates')),\n  '/conditional-rendering/useQueryState':   lazy(() => import('./routes/conditional-rendering.useQueryState')),\n  '/conditional-rendering/useQueryStates':  lazy(() => import('./routes/conditional-rendering.useQueryStates')),\n  '/form/useQueryState':                    lazy(() => import('./routes/form.useQueryState')),\n  '/form/useQueryStates':                   lazy(() => import('./routes/form.useQueryStates')),\n  '/hash-preservation':                     lazy(() => import('./routes/hash-preservation')),\n  '/history-sync':                          lazy(() => import('./routes/history-sync')),\n  '/json':                                  lazy(() => import('./routes/json')),\n  '/life-and-death':                        lazy(() => import('./routes/life-and-death')),\n  '/linking/useQueryState':                 lazy(() => import('./routes/linking.useQueryState')),\n  '/linking/useQueryState/other':           lazy(() => import('./routes/linking.useQueryState.other')),\n  '/linking/useQueryStates':                lazy(() => import('./routes/linking.useQueryStates')),\n  '/linking/useQueryStates/other':          lazy(() => import('./routes/linking.useQueryStates.other')),\n  '/native-array':                          lazy(() => import('./routes/native-array')),\n  '/pretty-urls':                           lazy(() => import('./routes/pretty-urls')),\n  '/referential-stability/useQueryState':   lazy(() => import('./routes/referential-stability.useQueryState')),\n  '/referential-stability/useQueryStates':  lazy(() => import('./routes/referential-stability.useQueryStates')),\n  '/routing/useQueryState':                 lazy(() => import('./routes/routing.useQueryState')),\n  '/routing/useQueryState/other':           lazy(() => import('./routes/routing.useQueryState.other')),\n  '/routing/useQueryStates':                lazy(() => import('./routes/routing.useQueryStates')),\n  '/routing/useQueryStates/other':          lazy(() => import('./routes/routing.useQueryStates.other')),\n  '/scroll':                                lazy(() => import('./routes/scroll')),\n\n  // Local tests\n  '/key-isolation/useQueryState':           lazy(() => import('./routes/key-isolation.useQueryState')),\n  '/key-isolation/useQueryStates':          lazy(() => import('./routes/key-isolation.useQueryStates')),\n  '/push/useQueryState':                    lazy(() => import('./routes/push.useQueryState')),\n  '/push/useQueryStates':                   lazy(() => import('./routes/push.useQueryStates')),\n  '/rate-limits':                           lazy(() => import('./routes/rate-limits')),\n  '/shallow/useQueryState':                 lazy(() => import('./routes/shallow.useQueryState')),\n  '/shallow/useQueryStates':                lazy(() => import('./routes/shallow.useQueryStates')),\n  '/stitching':                             lazy(() => import('./routes/stitching')),\n\n  // Render count\n  '/render-count/useQueryState/true/replace/false':   lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/true/replace/true':    lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/true/push/false':      lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/true/push/true':       lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/false/replace/false':  lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/false/replace/true':   lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/false/push/false':     lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryState/false/push/true':      lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/true/replace/false':  lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/true/replace/true':   lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/true/push/false':     lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/true/push/true':      lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/false/replace/false': lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/false/replace/true':  lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/false/push/false':    lazy(() => import('./routes/render-count')),\n  '/render-count/useQueryStates/false/push/true':     lazy(() => import('./routes/render-count')),\n\n  // Reproductions\n  '/repro-359':                 lazy(() => import('./routes/repro-359')),\n  '/repro-982':                 lazy(() => import('./routes/repro-982')),\n  '/repro-1099/useQueryState':  lazy(() => import('./routes/repro-1099.useQueryState')),\n  '/repro-1099/useQueryStates': lazy(() => import('./routes/repro-1099.useQueryStates')),\n  '/repro-1365':                lazy(() => import('./routes/repro-1365')),\n}\n\nexport function Router() {\n  const Route = routes[location.pathname]\n  if (!Route) {\n    return <>404 not found</>\n  }\n  return <Route />\n}\n"
  },
  {
    "path": "packages/e2e/react/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "packages/e2e/react/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"node\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/e2e/react/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \".\",\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ]\n}\n"
  },
  {
    "path": "packages/e2e/react/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2023\"],\n    \"types\": [\"node\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"vite.config.ts\", \"playwright.config.ts\", \"specs/**/*.ts\"]\n}\n"
  },
  {
    "path": "packages/e2e/react/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"dist/**\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\n        \"FULL_PAGE_NAV_ON_SHALLOW_FALSE\",\n        \"REACT_COMPILER\",\n        \"E2E_NO_CACHE_ON_RERUN\"\n      ]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\n        \"FULL_PAGE_NAV_ON_SHALLOW_FALSE\",\n        \"REACT_COMPILER\",\n        \"E2E_NO_CACHE_ON_RERUN\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig, loadEnv } from 'vite'\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  const env = loadEnv(mode, process.cwd(), '')\n  return {\n    plugins: [react()],\n    build: {\n      target: 'es2022',\n      sourcemap: true\n    },\n    define: {\n      'process.env.FULL_PAGE_NAV_ON_SHALLOW_FALSE': JSON.stringify(\n        env.FULL_PAGE_NAV_ON_SHALLOW_FALSE\n      )\n    }\n  }\n})\n"
  },
  {
    "path": "packages/e2e/react-router/package.json",
    "content": "{\n  \"name\": \"e2e-react-router\",\n  \"version\": \"0.0.0\",\n  \"private\": true\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Playwright\n.playwright/\n"
  },
  {
    "path": "packages/e2e/react-router/v5/README.md",
    "content": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.\n\nCurrently, two official plugins are available:\n\n- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh\n- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh\n\n## Expanding the ESLint configuration\n\nIf you are developing a production application, we recommend updating the configuration to enable type aware lint rules:\n\n- Configure the top-level `parserOptions` property like this:\n\n```js\nexport default tseslint.config({\n  languageOptions: {\n    // other options...\n    parserOptions: {\n      project: ['./tsconfig.node.json', './tsconfig.app.json'],\n      tsconfigRootDir: import.meta.dirname,\n    },\n  },\n})\n```\n\n- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`\n- Optionally add `...tseslint.configs.stylisticTypeChecked`\n- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:\n\n```js\n// eslint.config.js\nimport react from 'eslint-plugin-react'\n\nexport default tseslint.config({\n  // Set the react version\n  settings: { react: { version: '18.3' } },\n  plugins: {\n    // Add the react plugin\n    react,\n  },\n  rules: {\n    // other rules...\n    // Enable its recommended rules\n    ...react.configs.recommended.rules,\n    ...react.configs['jsx-runtime'].rules,\n  },\n})\n```\n"
  },
  {
    "path": "packages/e2e/react-router/v5/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React Router + TS</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/e2e/react-router/v5/package.json",
    "content": "{\n  \"name\": \"e2e-react-router-v5\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --port 3005\",\n    \"build\": \"vite build\",\n    \"start\": \"vite preview --port 3005\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:types\": \"tsc -b\",\n    \"test:playwright\": \"playwright test --project=chromium\"\n  },\n  \"dependencies\": {\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"react-router-dom\": \"^5.3.4\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@types/react-router-dom\": \"^5.3.3\",\n    \"@vitejs/plugin-react\": \"^5.1.3\",\n    \"e2e-shared\": \"workspace:*\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'pnpm run start',\n  port: 3005\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/fog-of-war.spec.ts",
    "content": "import { testFogOfWar } from 'e2e-shared/specs/react-router/fog-of-war.spec.ts'\n\ntestFogOfWar({\n  path: '/fog-of-war'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/push/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestPush({\n  path: '/push/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/render-count.spec.ts",
    "content": "import { testRenderCount } from 'e2e-shared/specs/render-count.spec.ts'\n\nconst hooks = ['useQueryState', 'useQueryStates'] as const\n// const shallows = [true, false] as const\nconst histories = ['replace', 'push'] as const\n\nconst shallow = true\nconst startTransition = false\n\nfor (const hook of hooks) {\n  // for (const shallow of shallows) {\n  for (const history of histories) {\n    // for (const startTransition of [false, true]) {\n    testRenderCount({\n      path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/no-loader`,\n      description: 'no loader',\n      hook,\n      props: {\n        shallow,\n        history,\n        startTransition\n      },\n      expected: {\n        mount: 1,\n        update: 2\n      }\n    })\n  }\n  // }\n  // }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/repro-1099/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRepro1099({\n  path: '/repro-1099/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/repro-359.spec.ts",
    "content": "import { testRepro359 } from 'e2e-shared/specs/repro-359.spec.ts'\n\ntestRepro359({\n  path: '/repro-359'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/repro-982.spec.ts",
    "content": "import { testRepro982 } from 'e2e-shared/specs/repro-982.spec.ts'\n\ntestRepro982({\n  path: '/repro-982'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared/stitching.spec.ts",
    "content": "import { testStitching } from 'e2e-shared/specs/stitching.spec.ts'\n\ntestStitching({\n  path: '/stitching'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v5/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests()\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/adapter.ts",
    "content": "import {\n  type unstable_AdapterInterface as AdapterInterface,\n  unstable_createAdapterProvider as createAdapterProvider,\n  renderQueryString,\n  type unstable_UpdateUrlFunction as UpdateUrlFunction\n} from 'nuqs/adapters/custom'\nimport { useCallback, useMemo } from 'react'\nimport { useHistory, useLocation } from 'react-router-dom'\n\nfunction useNuqsReactRouterV5Adapter(): AdapterInterface {\n  // todo: Shallow (using the History API)\n  // todo: Key isolation\n  const history = useHistory()\n  const location = useLocation()\n  const searchParams = useMemo(() => {\n    return new URLSearchParams(location.search)\n  }, [location.search])\n\n  const updateUrl = useCallback<UpdateUrlFunction>(\n    (search, options) => {\n      const queryString = renderQueryString(search)\n      if (options.history === 'push') {\n        history.push({\n          search: queryString,\n          hash: window.location.hash\n        })\n      } else {\n        history.replace({\n          search: queryString,\n          hash: window.location.hash\n        })\n      }\n      if (options.scroll) {\n        window.scrollTo(0, 0)\n      }\n    },\n    [history.push, history.replace]\n  )\n  return {\n    searchParams,\n    updateUrl\n  }\n}\n\nexport const NuqsAdapter = createAdapterProvider(useNuqsReactRouterV5Adapter)\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/layout.tsx",
    "content": "import { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider, type LinkProps } from 'e2e-shared/components/link'\nimport { RouterProvider, type Router } from 'e2e-shared/components/router'\nimport { type ReactNode } from 'react'\nimport { Link as ReactRouterLink, useHistory } from 'react-router-dom'\n\nfunction Link({ href, ...props }: LinkProps) {\n  return <ReactRouterLink to={href} {...props} />\n}\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n  return (\n    <>\n      <HydrationMarker />\n      <LinkProvider Link={Link}>\n        <RouterProvider useRouter={useRouter}>{children}</RouterProvider>\n      </LinkProvider>\n    </>\n  )\n}\n\nfunction useRouter(): Router {\n  const history = useHistory()\n  return {\n    replace(url) {\n      history.replace(url)\n    },\n    push(url) {\n      history.push(url)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/main.tsx",
    "content": "import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { ReactRouter } from './react-router'\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <ReactRouter />\n  </StrictMode>\n)\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/react-router.tsx",
    "content": "import { lazy, Suspense } from 'react'\nimport { BrowserRouter, Route, Switch } from 'react-router-dom'\nimport { NuqsAdapter } from './adapter'\nimport RootLayout from './layout'\n\n// prettier-ignore\nconst routes = {\n  // Shared E2E tests\n  '/basic-io/useQueryState':                    lazy(() => import('./routes/basic-io.useQueryState')),\n  '/basic-io/useQueryStates':                   lazy(() => import('./routes/basic-io.useQueryStates')),\n  '/conditional-rendering/useQueryState':       lazy(() => import('./routes/conditional-rendering.useQueryState')),\n  '/conditional-rendering/useQueryStates':      lazy(() => import('./routes/conditional-rendering.useQueryStates')),\n  '/form/useQueryState':                        lazy(() => import('./routes/form.useQueryState')),\n  '/form/useQueryStates':                       lazy(() => import('./routes/form.useQueryStates')),\n  '/hash-preservation':                         lazy(() => import('./routes/hash-preservation')),\n  '/history-sync':                              lazy(() => import('./routes/history-sync')),\n  '/json':                                      lazy(() => import('./routes/json')),\n  '/life-and-death':                            lazy(() => import('./routes/life-and-death')),\n  '/linking/useQueryState':                     lazy(() => import('./routes/linking.useQueryState')),\n  '/linking/useQueryState/other':               lazy(() => import('./routes/linking.useQueryState.other')),\n  '/linking/useQueryStates':                    lazy(() => import('./routes/linking.useQueryStates')),\n  '/linking/useQueryStates/other':              lazy(() => import('./routes/linking.useQueryStates.other')),\n  '/native-array':                              lazy(() => import('./routes/native-array')),\n  '/pretty-urls':                               lazy(() => import('./routes/pretty-urls')),\n  '/referential-stability/useQueryState':       lazy(() => import('./routes/referential-stability.useQueryState')),\n  '/referential-stability/useQueryStates':      lazy(() => import('./routes/referential-stability.useQueryStates')),\n  '/routing/useQueryState':                     lazy(() => import('./routes/routing.useQueryState')),\n  '/routing/useQueryState/other':               lazy(() => import('./routes/routing.useQueryState.other')),\n  '/routing/useQueryStates':                    lazy(() => import('./routes/routing.useQueryStates')),\n  '/routing/useQueryStates/other':              lazy(() => import('./routes/routing.useQueryStates.other')),\n  '/scroll':                                    lazy(() => import('./routes/scroll')),\n\n  // Local tests\n  '/fog-of-war':                                lazy(() => import('./routes/fog-of-war._index')),\n  '/fog-of-war/result':                         lazy(() => import('./routes/fog-of-war.result')),\n  '/key-isolation/useQueryState':               lazy(() => import('./routes/key-isolation.useQueryState')),\n  '/key-isolation/useQueryStates':              lazy(() => import('./routes/key-isolation.useQueryStates')),\n  '/push/useQueryState':                        lazy(() => import('./routes/push.useQueryState')),\n  '/push/useQueryStates':                       lazy(() => import('./routes/push.useQueryStates')),\n  '/rate-limits':                               lazy(() => import('./routes/rate-limits')),\n  '/stitching':                                 lazy(() => import('./routes/stitching')),\n\n  // Render Count\n  '/render-count/:hook/:shallow/:history/:startTransition/no-loader': lazy(() => import('./routes/render-count.$hook.$shallow.$history.$startTransition.no-loader')),\n\n  // Reproductions\n  '/repro-359':                 lazy(() => import('./routes/repro-359')),\n  '/repro-982':                 lazy(() => import('./routes/repro-982')),\n  '/repro-1099/useQueryState':  lazy(() => import('./routes/repro-1099.useQueryState')),\n  '/repro-1099/useQueryStates': lazy(() => import('./routes/repro-1099.useQueryStates')),\n}\n\nexport function ReactRouter() {\n  return (\n    <NuqsAdapter>\n      <BrowserRouter>\n        <RootLayout>\n          <Suspense fallback={null}>\n            <Switch>\n              {Object.entries(routes).map(([path, Component]) => (\n                <Route key={path} exact path={path} component={Component} />\n              ))}\n            </Switch>\n          </Suspense>\n        </RootLayout>\n      </BrowserRouter>\n    </NuqsAdapter>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/basic-io.useQueryState.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStateBasicIO\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/basic-io.useQueryStates.tsx",
    "content": "import { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStatesBasicIO\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/conditional-rendering.useQueryState.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/conditional-rendering.useQueryStates.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/fog-of-war._index.tsx",
    "content": "import { FogOfWarStartPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default function FogOfWarStart() {\n  return <FogOfWarStartPage resultHref=\"/fog-of-war/result\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/fog-of-war.result.tsx",
    "content": "import { FogOfWarResultPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default FogOfWarResultPage\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/form.useQueryState.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/form.useQueryStates.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/hash-preservation.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/history-sync.tsx",
    "content": "import { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport default HistorySync\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/json.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\n\nexport default Json\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/key-isolation.useQueryState.tsx",
    "content": "import { KeyIsolationUseQueryState } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/key-isolation.useQueryStates.tsx",
    "content": "import { KeyIsolationUseQueryStates } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/life-and-death.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport default LifeAndDeath\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/linking.useQueryState.other.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/linking.useQueryState.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/linking.useQueryStates.other.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/linking.useQueryStates.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/native-array.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\n\nexport default NativeArray\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/pretty-urls.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport default PrettyUrls\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/push.useQueryState.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/push.useQueryStates.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/rate-limits.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\n\nexport default RateLimits\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/referential-stability.useQueryState.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/referential-stability.useQueryStates.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\nimport { useParams } from 'react-router-dom'\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/repro-1099.useQueryState.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/repro-1099.useQueryStates.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/repro-359.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\n\nexport default Repro359\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/repro-982.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\n\nexport default Repro982\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/routing.useQueryState.other.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/routing.useQueryState.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/routing.useQueryStates.other.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/routing.useQueryStates.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/scroll.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\n\nexport default Scroll\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/routes/stitching.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\n\nexport default Stitching\n"
  },
  {
    "path": "packages/e2e/react-router/v5/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "packages/e2e/react-router/v5/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"node\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \".\",\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ]\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2023\"],\n    \"types\": [\"node\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"vite.config.ts\", \"playwright.config.ts\"]\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"dist/**\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v5/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\n// https://vitejs.dev/config/\nexport default defineConfig(() => ({\n  plugins: [react()],\n  build: {\n    target: 'es2022',\n    sourcemap: true\n  }\n}))\n"
  },
  {
    "path": "packages/e2e/react-router/v6/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Playwright\n.playwright/\n\n"
  },
  {
    "path": "packages/e2e/react-router/v6/README.md",
    "content": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.\n\nCurrently, two official plugins are available:\n\n- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh\n- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh\n\n## Expanding the ESLint configuration\n\nIf you are developing a production application, we recommend updating the configuration to enable type aware lint rules:\n\n- Configure the top-level `parserOptions` property like this:\n\n```js\nexport default tseslint.config({\n  languageOptions: {\n    // other options...\n    parserOptions: {\n      project: ['./tsconfig.node.json', './tsconfig.app.json'],\n      tsconfigRootDir: import.meta.dirname,\n    },\n  },\n})\n```\n\n- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`\n- Optionally add `...tseslint.configs.stylisticTypeChecked`\n- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:\n\n```js\n// eslint.config.js\nimport react from 'eslint-plugin-react'\n\nexport default tseslint.config({\n  // Set the react version\n  settings: { react: { version: '18.3' } },\n  plugins: {\n    // Add the react plugin\n    react,\n  },\n  rules: {\n    // other rules...\n    // Enable its recommended rules\n    ...react.configs.recommended.rules,\n    ...react.configs['jsx-runtime'].rules,\n  },\n})\n```\n"
  },
  {
    "path": "packages/e2e/react-router/v6/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React Router + TS</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/e2e/react-router/v6/package.json",
    "content": "{\n  \"name\": \"e2e-react-router-v6\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --port 3006\",\n    \"build\": \"vite build\",\n    \"start\": \"vite preview --port 3006\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:types\": \"tsc -b\",\n    \"test:playwright\": \"playwright test --project=chromium\"\n  },\n  \"dependencies\": {\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"react-router-dom\": \"6.30.3\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@vitejs/plugin-react\": \"^5.1.3\",\n    \"e2e-shared\": \"workspace:*\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'pnpm run start',\n  port: 3006\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/repro-839.spec.ts",
    "content": "import { testRepro839LocationStatePersistence } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence.spec.ts'\n\ntestRepro839LocationStatePersistence({\n  path: '/repro-839'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/debounce.spec.ts",
    "content": "import { testDebounce } from 'e2e-shared/specs/debounce.spec.ts'\n\ntestDebounce({ path: '/debounce' })\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/dynamic-segments.spec.ts",
    "content": "import { testDynamicSegments } from 'e2e-shared/specs/dynamic-segments.spec.ts'\n\ntestDynamicSegments({\n  path: '/dynamic-segments/dynamic/foo',\n  expectedSegments: ['foo']\n})\n\ntestDynamicSegments({\n  path: '/dynamic-segments/catch-all',\n  expectedSegments: ['']\n})\n\ntestDynamicSegments({\n  path: '/dynamic-segments/catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c']\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/flush-after-navigate.spec.ts",
    "content": "import { testFlushAfterNavigate } from 'e2e-shared/specs/flush-after-navigate.spec.ts'\n\ntestFlushAfterNavigate({\n  path: '/flush-after-navigate/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestFlushAfterNavigate({\n  path: '/flush-after-navigate/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/fog-of-war.spec.ts",
    "content": "import { testFogOfWar } from 'e2e-shared/specs/react-router/fog-of-war.spec.ts'\n\ntestFogOfWar({\n  path: '/fog-of-war'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/key-isolation.spec.ts",
    "content": "import { testKeyIsolation } from 'e2e-shared/specs/key-isolation.spec.ts'\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/loader.spec.ts",
    "content": "import { testLoader } from 'e2e-shared/specs/loader.spec.ts'\n\ntestLoader({ path: '/loader' })\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/popstate-queue-reset.spec.ts",
    "content": "import { testPopstateQueueReset } from 'e2e-shared/specs/popstate-queue-reset.spec.ts'\n\ntestPopstateQueueReset({\n  path: '/popstate-queue-reset'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/push/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestPush({\n  path: '/push/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/render-count.spec.ts",
    "content": "import { testRenderCount } from 'e2e-shared/specs/render-count.spec.ts'\n\nconst hooks = ['useQueryState', 'useQueryStates'] as const\nconst shallows = [true, false] as const\nconst histories = ['replace', 'push'] as const\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/no-loader`,\n          description: 'no loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2 + (shallow === false ? (startTransition ? 0 : 1) : 0)\n          }\n        })\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/sync-loader`,\n          description: 'sync loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2 + (shallow === false ? 1 : 0)\n          }\n        })\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/async-loader`,\n          description: 'async loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2 + (shallow === false ? 1 : 0)\n          }\n        })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/repro-1099/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRepro1099({\n  path: '/repro-1099/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/repro-1293.spec.ts",
    "content": "import { testRepro1293 } from 'e2e-shared/specs/repro-1293.spec.ts'\n\ntestRepro1293({\n  path: '/repro-1293'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/repro-1365.spec.ts",
    "content": "import { testRepro1365 } from 'e2e-shared/specs/repro-1365.spec.ts'\n\ntestRepro1365({\n  path: '/repro-1365'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/repro-359.spec.ts",
    "content": "import { testRepro359 } from 'e2e-shared/specs/repro-359.spec.ts'\n\ntestRepro359({\n  path: '/repro-359'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/repro-982.spec.ts",
    "content": "import { testRepro982 } from 'e2e-shared/specs/repro-982.spec.ts'\n\ntestRepro982({\n  path: '/repro-982'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/shallow.spec.ts",
    "content": "import { testShallow } from 'e2e-shared/specs/shallow.spec.ts'\n\ntestShallow({\n  path: '/shallow/useQueryState',\n  hook: 'useQueryState',\n  supportsSSR: false\n})\n\ntestShallow({\n  path: '/shallow/useQueryStates',\n  hook: 'useQueryStates',\n  supportsSSR: false\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared/stitching.spec.ts",
    "content": "import { testStitching } from 'e2e-shared/specs/stitching.spec.ts'\n\ntestStitching({\n  path: '/stitching'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v6/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests()\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/layout.tsx",
    "content": "import { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider, type LinkProps } from 'e2e-shared/components/link'\nimport { RouterProvider, type Router } from 'e2e-shared/components/router'\nimport { Outlet, Link as ReactRouterLink, useNavigate } from 'react-router-dom'\n\nfunction Link({ href, ...props }: LinkProps) {\n  return <ReactRouterLink to={href} {...props} />\n}\n\nexport default function RootLayout() {\n  return (\n    <>\n      <HydrationMarker />\n      <LinkProvider Link={Link}>\n        <RouterProvider useRouter={useRouter}>\n          <Outlet />\n        </RouterProvider>\n      </LinkProvider>\n    </>\n  )\n}\n\nfunction useRouter(): Router {\n  const navigate = useNavigate()\n  return {\n    replace(url, options) {\n      if (options.shallow) {\n        history.replaceState(history.state, '', url)\n      } else {\n        navigate(url, { replace: true })\n      }\n    },\n    push(url, options) {\n      if (options.shallow) {\n        history.pushState(history.state, '', url)\n      } else {\n        navigate(url, { replace: false })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/main.tsx",
    "content": "import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { ReactRouter } from './react-router'\n\ncreateRoot(document.getElementById('root')!).render(\n  <StrictMode>\n    <ReactRouter />\n  </StrictMode>\n)\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/react-router.tsx",
    "content": "import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'\nimport {\n  createBrowserRouter,\n  createRoutesFromElements,\n  Route,\n  RouterProvider\n} from 'react-router-dom'\nimport RootLayout from './layout'\n\n// Adapt the RRv7 / Remix default export for component into a Component export for v6\nfunction load(mod: Promise<{ default: any; [otherExports: string]: any }>) {\n  return () =>\n    mod.then(({ default: Component, ...otherExports }) => ({\n      Component,\n      ...otherExports\n    }))\n}\n\n// prettier-ignore\nconst router = createBrowserRouter(\n  createRoutesFromElements(\n    <Route path=\"/\" element={<RootLayout/>} >\n      {/* Shared E2E tests */}\n      <Route path=\"basic-io/useQueryState\"                    lazy={load(import('./routes/basic-io.useQueryState'))} />\n      <Route path=\"basic-io/useQueryStates\"                   lazy={load(import('./routes/basic-io.useQueryStates'))} />\n      <Route path=\"conditional-rendering/useQueryState\"       lazy={load(import('./routes/conditional-rendering.useQueryState'))} />\n      <Route path=\"conditional-rendering/useQueryStates\"      lazy={load(import('./routes/conditional-rendering.useQueryStates'))} />\n      <Route path=\"form/useQueryState\"                        lazy={load(import('./routes/form.useQueryState'))} />\n      <Route path=\"form/useQueryStates\"                       lazy={load(import('./routes/form.useQueryStates'))} />\n      <Route path=\"hash-preservation\"                         lazy={load(import('./routes/hash-preservation'))} />\n      <Route path=\"history-sync\"                              lazy={load(import('./routes/history-sync'))} />\n      <Route path=\"json\"                                      lazy={load(import('./routes/json'))} />\n      <Route path=\"life-and-death\"                            lazy={load(import('./routes/life-and-death'))} />\n      <Route path=\"linking/useQueryState\"                     lazy={load(import('./routes/linking.useQueryState'))} />\n      <Route path=\"linking/useQueryState/other\"               lazy={load(import('./routes/linking.useQueryState.other'))} />\n      <Route path=\"linking/useQueryStates\"                    lazy={load(import('./routes/linking.useQueryStates'))} />\n      <Route path=\"linking/useQueryStates/other\"              lazy={load(import('./routes/linking.useQueryStates.other'))} />\n      <Route path=\"native-array\"                              lazy={load(import('./routes/native-array'))} />\n      <Route path=\"pretty-urls\"                               lazy={load(import('./routes/pretty-urls'))} />\n      <Route path=\"referential-stability/useQueryState\"       lazy={load(import('./routes/referential-stability.useQueryState'))} />\n      <Route path=\"referential-stability/useQueryStates\"      lazy={load(import('./routes/referential-stability.useQueryStates'))} />\n      <Route path=\"routing/useQueryState\"                     lazy={load(import('./routes/routing.useQueryState'))} />\n      <Route path=\"routing/useQueryState/other\"               lazy={load(import('./routes/routing.useQueryState.other'))} />\n      <Route path=\"routing/useQueryStates\"                    lazy={load(import('./routes/routing.useQueryStates'))} />\n      <Route path=\"routing/useQueryStates/other\"              lazy={load(import('./routes/routing.useQueryStates.other'))} />\n      <Route path=\"scroll\"                                    lazy={load(import('./routes/scroll'))} />\n\n      {/* Local tests */}\n      <Route path=\"debounce\"                                  lazy={load(import('./routes/debounce'))} />\n      <Route path=\"debounce/other\"                            lazy={load(import('./routes/debounce.other'))} />\n      <Route path=\"dynamic-segments/catch-all?*\"              lazy={load(import('./routes/dynamic-segments.catch-all.$'))} />\n      <Route path=\"dynamic-segments/dynamic/:segment\"         lazy={load(import('./routes/dynamic-segments.dynamic.$segment'))} />\n      <Route path=\"flush-after-navigate/useQueryState/end\"    lazy={load(import('./routes/flush-after-navigate.useQueryState.end'))} />\n      <Route path=\"flush-after-navigate/useQueryState/start\"  lazy={load(import('./routes/flush-after-navigate.useQueryState.start'))} />\n      <Route path=\"flush-after-navigate/useQueryStates/end\"   lazy={load(import('./routes/flush-after-navigate.useQueryStates.end'))} />\n      <Route path=\"flush-after-navigate/useQueryStates/start\" lazy={load(import('./routes/flush-after-navigate.useQueryStates.start'))} />\n      <Route path=\"fog-of-war\"                                lazy={load(import('./routes/fog-of-war._index'))} />\n      <Route path=\"fog-of-war/result\"                         lazy={load(import('./routes/fog-of-war.result'))} />\n      <Route path=\"key-isolation/useQueryState\"               lazy={load(import('./routes/key-isolation.useQueryState'))} />\n      <Route path=\"key-isolation/useQueryStates\"              lazy={load(import('./routes/key-isolation.useQueryStates'))} />\n      <Route path=\"loader\"                                    lazy={load(import('./routes/loader'))} />\n      <Route path=\"popstate-queue-reset\"                      lazy={load(import('./routes/popstate-queue-reset'))} />\n      <Route path=\"popstate-queue-reset/other\"                lazy={load(import('./routes/popstate-queue-reset.other'))} />\n      <Route path=\"push/useQueryState\"                        lazy={load(import('./routes/push.useQueryState'))} />\n      <Route path=\"push/useQueryState\"                        lazy={load(import('./routes/push.useQueryState'))} />\n      <Route path=\"push/useQueryStates\"                       lazy={load(import('./routes/push.useQueryStates'))} />\n      <Route path=\"push/useQueryStates\"                       lazy={load(import('./routes/push.useQueryStates'))} />\n      <Route path=\"rate-limits\"                               lazy={load(import('./routes/rate-limits'))} />\n      <Route path=\"shallow/useQueryState\"                     lazy={load(import('./routes/shallow.useQueryState'))} />\n      <Route path=\"shallow/useQueryStates\"                    lazy={load(import('./routes/shallow.useQueryStates'))} />\n      <Route path=\"stitching\"                                 lazy={load(import('./routes/stitching'))} />\n\n      {/* Render Count */}\n      <Route path=\"render-count/:hook/:shallow/:history/:startTransition/no-loader\"     lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.no-loader'))} />\n      <Route path=\"render-count/:hook/:shallow/:history/:startTransition/sync-loader\"   lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader'))} />\n      <Route path=\"render-count/:hook/:shallow/:history/:startTransition/async-loader\"  lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.async-loader'))} />\n\n      {/* Reproductions */}\n      <Route path=\"repro-359\"                 lazy={load(import('./routes/repro-359'))} />\n      <Route path=\"repro-839\"                 lazy={load(import('./routes/repro-839'))} />\n      <Route path=\"repro-982\"                 lazy={load(import('./routes/repro-982'))} />\n      <Route path=\"repro-1099/useQueryState\"  lazy={load(import('./routes/repro-1099.useQueryState'))} />\n      <Route path=\"repro-1099/useQueryStates\" lazy={load(import('./routes/repro-1099.useQueryStates'))} />\n      <Route path=\"repro-1293/a\"              lazy={load(import('./routes/repro-1293.a'))} />\n      <Route path=\"repro-1293/b\"              lazy={load(import('./routes/repro-1293.b'))} />\n      <Route path=\"repro-1365\"                lazy={load(import('./routes/repro-1365'))} />\n    </Route>\n  ))\n\nexport function ReactRouter() {\n  return (\n    <NuqsAdapter>\n      <RouterProvider router={router} />\n    </NuqsAdapter>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/basic-io.useQueryState.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStateBasicIO\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/basic-io.useQueryStates.tsx",
    "content": "import { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStatesBasicIO\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryState.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/conditional-rendering.useQueryStates.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/debounce.other.tsx",
    "content": "import { Link } from 'react-router-dom'\n\nexport default function Page() {\n  return <Link to=\"/debounce\">Go back to the previous page</Link>\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/debounce.tsx",
    "content": "import { DebounceClient } from 'e2e-shared/specs/debounce-client'\nimport { DebounceServer } from 'e2e-shared/specs/debounce-server'\nimport {\n  type DemoSearchParams,\n  loadDemoSearchParams\n} from 'e2e-shared/specs/debounce.defs'\nimport { LoaderFunctionArgs, useLoaderData } from 'react-router-dom'\n\nexport function loader({ request }: LoaderFunctionArgs) {\n  return loadDemoSearchParams(request)\n}\n\nexport default function Page() {\n  const serverState = useLoaderData() as DemoSearchParams\n  return (\n    <DebounceServer state={serverState}>\n      <DebounceClient navigateHref=\"/debounce/other\" />\n    </DebounceServer>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/dynamic-segments.catch-all.$.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString } from 'nuqs'\nimport {\n  useLoaderData,\n  useParams,\n  type LoaderFunctionArgs\n} from 'react-router-dom'\n\nconst loadSearchParams = createLoader({\n  test: parseAsString\n})\n\nexport function loader({ request, params }: LoaderFunctionArgs) {\n  const { test: serverState } = loadSearchParams(request)\n  return {\n    serverState,\n    serverSegments: params['*']?.split('/') ?? []\n  }\n}\n\nexport default function Page() {\n  const { serverState, serverSegments } = useLoaderData() as ReturnType<\n    typeof loader\n  >\n  const clientSegments = useParams()['*']?.split('/') ?? []\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments environment=\"client\" segments={clientSegments} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/dynamic-segments.dynamic.$segment.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString } from 'nuqs'\nimport {\n  useLoaderData,\n  useParams,\n  type LoaderFunctionArgs\n} from 'react-router-dom'\n\nconst loadSearchParams = createLoader({\n  test: parseAsString\n})\n\nexport function loader({ request, params }: LoaderFunctionArgs) {\n  const { test: serverState } = loadSearchParams(request)\n  return {\n    serverState,\n    serverSegments: [params.segment as string]\n  }\n}\n\nexport default function Page() {\n  const { serverState, serverSegments } = useLoaderData() as ReturnType<\n    typeof loader\n  >\n  const params = useParams()\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments\n        environment=\"client\"\n        segments={[params.segment as string]}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/flush-after-navigate.useQueryState.end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/flush-after-navigate.useQueryState.start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStateStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStateStart path=\"/flush-after-navigate/useQueryState\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/flush-after-navigate.useQueryStates.end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/flush-after-navigate.useQueryStates.start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStatesStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStatesStart path=\"/flush-after-navigate/useQueryStates\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/fog-of-war._index.tsx",
    "content": "import { FogOfWarStartPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default function FogOfWarStart() {\n  return <FogOfWarStartPage resultHref=\"/fog-of-war/result\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/fog-of-war.result.tsx",
    "content": "import { FogOfWarResultPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default FogOfWarResultPage\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/form.useQueryState.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/form.useQueryStates.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/hash-preservation.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/history-sync.tsx",
    "content": "import { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport default HistorySync\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/json.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\n\nexport default Json\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/key-isolation.useQueryState.tsx",
    "content": "import { KeyIsolationUseQueryState } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/key-isolation.useQueryStates.tsx",
    "content": "import { KeyIsolationUseQueryStates } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/life-and-death.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport default LifeAndDeath\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/linking.useQueryState.other.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/linking.useQueryState.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/linking.useQueryStates.other.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/linking.useQueryStates.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/loader.tsx",
    "content": "import { LoaderRenderer, loadSearchParams } from 'e2e-shared/specs/loader'\nimport { useLoaderData, type LoaderFunctionArgs } from 'react-router-dom'\n\nexport function loader({ request }: LoaderFunctionArgs) {\n  return loadSearchParams(request)\n}\n\nexport default function Page() {\n  const serverValues = useLoaderData() as Awaited<ReturnType<typeof loader>>\n  return <LoaderRenderer serverValues={serverValues} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/native-array.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\n\nexport default NativeArray\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/popstate-queue-reset.other.tsx",
    "content": "import { PopstateQueueResetOther } from 'e2e-shared/specs/popstate-queue-reset'\n\nexport default PopstateQueueResetOther\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/popstate-queue-reset.tsx",
    "content": "import { PopstateQueueResetClient } from 'e2e-shared/specs/popstate-queue-reset'\nimport { useNavigate } from 'react-router-dom'\n\nexport default function Page() {\n  const navigate = useNavigate()\n  return (\n    <PopstateQueueResetClient\n      onNavigateToOther={() => navigate('/popstate-queue-reset/other')}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/pretty-urls.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport default PrettyUrls\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/push.useQueryState.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/push.useQueryStates.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/rate-limits.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\n\nexport default RateLimits\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/referential-stability.useQueryState.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/referential-stability.useQueryStates.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport {\n  loadParams,\n  loadSearchParams\n} from 'e2e-shared/specs/render-count.params'\n\nimport { useParams, type LoaderFunctionArgs } from 'react-router-dom'\n\nconst wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const { delay } = loadSearchParams(request)\n  if (delay) {\n    await wait(delay)\n  }\n  return null\n}\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\nimport { useParams } from 'react-router-dom'\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\nimport { useParams } from 'react-router-dom'\n\nexport function loader() {\n  return null\n}\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-1099.useQueryState.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-1099.useQueryStates.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-1293.a.tsx",
    "content": "import { Repro1293PageA } from 'e2e-shared/specs/repro-1293'\n\nexport default function Page() {\n  return <Repro1293PageA linkHref=\"/repro-1293/b\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-1293.b.tsx",
    "content": "import { Repro1293PageB } from 'e2e-shared/specs/repro-1293'\n\nexport default function Page() {\n  return <Repro1293PageB />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-1365.tsx",
    "content": "import { Repro1365 } from 'e2e-shared/specs/repro-1365'\n\nexport default Repro1365\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-359.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\n\nexport default Repro359\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-839.tsx",
    "content": "import { Repro839 } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence'\nimport { useLocation, useNavigate } from 'react-router-dom'\n\nexport default function Page() {\n  return <Repro839 useLocation={useLocation} useNavigate={useNavigate} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/repro-982.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\n\nexport default Repro982\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/routing.useQueryState.other.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/routing.useQueryState.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/routing.useQueryStates.other.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/routing.useQueryStates.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/scroll.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\n\nexport default Scroll\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/shallow.useQueryState.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\nimport { useLoaderData, type LoaderFunctionArgs } from 'react-router-dom'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url)\n  return {\n    serverState: url.searchParams.get('test')\n  }\n}\n\nexport default function Page() {\n  const { serverState } = useLoaderData() as Awaited<ReturnType<typeof loader>>\n  return (\n    <>\n      <ShallowUseQueryState />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/shallow.useQueryStates.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\nimport { useLoaderData, type LoaderFunctionArgs } from 'react-router-dom'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url)\n  return {\n    serverState: url.searchParams.get('test')\n  }\n}\n\nexport default function Page() {\n  const { serverState } = useLoaderData() as Awaited<ReturnType<typeof loader>>\n  return (\n    <>\n      <ShallowUseQueryStates />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/routes/stitching.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\n\nexport default Stitching\n"
  },
  {
    "path": "packages/e2e/react-router/v6/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "packages/e2e/react-router/v6/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"node\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"baseUrl\": \".\",\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ]\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2023\"],\n    \"types\": [\"node\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"vite.config.ts\", \"playwright.config.ts\"]\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"dist/**\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v6/vite.config.ts",
    "content": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\n// https://vitejs.dev/config/\nexport default defineConfig(() => ({\n  plugins: [react()],\n  build: {\n    target: 'es2022',\n    sourcemap: true\n  }\n}))\n"
  },
  {
    "path": "packages/e2e/react-router/v7/.gitignore",
    "content": ".DS_Store\n/node_modules/\n\n# React Router\n/.react-router/\n/build/\n\n# Playwright\n.playwright/\n\n"
  },
  {
    "path": "packages/e2e/react-router/v7/README.md",
    "content": "# Welcome to React Router!\n\nA modern, production-ready template for building full-stack React applications using React Router.\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)\n\n## Features\n\n- 🚀 Server-side rendering\n- ⚡️ Hot Module Replacement (HMR)\n- 📦 Asset bundling and optimization\n- 🔄 Data loading and mutations\n- 🔒 TypeScript by default\n- 🎉 TailwindCSS for styling\n- 📖 [React Router docs](https://reactrouter.com/)\n\n## Getting Started\n\n### Installation\n\nInstall the dependencies:\n\n```bash\nnpm install\n```\n\n### Development\n\nStart the development server with HMR:\n\n```bash\nnpm run dev\n```\n\nYour application will be available at `http://localhost:5173`.\n\n## Building for Production\n\nCreate a production build:\n\n```bash\nnpm run build\n```\n\n## Deployment\n\n### Docker Deployment\n\nThis template includes three Dockerfiles optimized for different package managers:\n\n- `Dockerfile` - for npm\n- `Dockerfile.pnpm` - for pnpm\n- `Dockerfile.bun` - for bun\n\nTo build and run using Docker:\n\n```bash\n# For npm\ndocker build -t my-app .\n\n# For pnpm\ndocker build -f Dockerfile.pnpm -t my-app .\n\n# For bun\ndocker build -f Dockerfile.bun -t my-app .\n\n# Run the container\ndocker run -p 3000:3000 my-app\n```\n\nThe containerized application can be deployed to any platform that supports Docker, including:\n\n- AWS ECS\n- Google Cloud Run\n- Azure Container Apps\n- Digital Ocean App Platform\n- Fly.io\n- Railway\n\n### DIY Deployment\n\nIf you're familiar with deploying Node applications, the built-in app server is production-ready.\n\nMake sure to deploy the output of `npm run build`\n\n```\n├── package.json\n├── package-lock.json (or pnpm-lock.yaml, or bun.lockb)\n├── build/\n│   ├── client/    # Static assets\n│   └── server/    # Server-side code\n```\n\n## Styling\n\nThis template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer.\n\n---\n\nBuilt with ❤️ using React Router.\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/layout.tsx",
    "content": "import { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider, type LinkProps } from 'e2e-shared/components/link'\nimport { RouterProvider, type Router } from 'e2e-shared/components/router'\nimport { Outlet, Link as ReactRouterLink, useNavigate } from 'react-router'\n\nfunction Link({ href, ...props }: LinkProps) {\n  return <ReactRouterLink to={href} {...props} />\n}\n\nexport default function RootLayout() {\n  return (\n    <>\n      <HydrationMarker />\n      <LinkProvider Link={Link}>\n        <RouterProvider useRouter={useRouter}>\n          <Outlet />\n        </RouterProvider>\n      </LinkProvider>\n    </>\n  )\n}\n\nfunction useRouter(): Router {\n  const navigate = useNavigate()\n  return {\n    replace(url, options) {\n      if (options.shallow) {\n        history.replaceState(history.state, '', url)\n      } else {\n        navigate(url, { replace: true })\n      }\n    },\n    push(url, options) {\n      if (options.shallow) {\n        history.pushState(history.state, '', url)\n      } else {\n        navigate(url, { replace: false })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/root.tsx",
    "content": "import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'\nimport {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration\n} from 'react-router'\n\nimport type { Route } from './+types/root'\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  )\n}\n\nexport default function App() {\n  return (\n    <NuqsAdapter>\n      <Outlet />\n    </NuqsAdapter>\n  )\n}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = 'Oops!'\n  let details = 'An unexpected error occurred.'\n  let stack: string | undefined\n\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? '404' : 'Error'\n    details =\n      error.status === 404\n        ? 'The requested page could not be found.'\n        : error.statusText || details\n  } else if (import.meta.env.DEV && error && error instanceof Error) {\n    details = error.message\n    stack = error.stack\n  }\n\n  return (\n    <main className=\"pt-16 p-4 container mx-auto\">\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre className=\"w-full p-4 overflow-x-auto\">\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/basic-io.useQueryState.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStateBasicIO\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/basic-io.useQueryStates.tsx",
    "content": "import { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStatesBasicIO\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryState.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/conditional-rendering.useQueryStates.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/debounce.other.tsx",
    "content": "import { Link } from 'react-router'\n\nexport default function Page() {\n  return <Link to=\"/debounce\">Go back to the previous page</Link>\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/debounce.tsx",
    "content": "import { DebounceClient } from 'e2e-shared/specs/debounce-client'\nimport { DebounceServer } from 'e2e-shared/specs/debounce-server'\nimport { loadDemoSearchParams } from 'e2e-shared/specs/debounce.defs'\nimport type { LoaderFunctionArgs } from 'react-router'\nimport type { Route } from './+types/debounce'\n\nexport function loader({ request }: LoaderFunctionArgs) {\n  return loadDemoSearchParams(request)\n}\n\nexport default function Page({\n  loaderData: serverState\n}: Route.ComponentProps) {\n  return (\n    <DebounceServer state={serverState}>\n      <DebounceClient navigateHref=\"/debounce/other\" />\n    </DebounceServer>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/dynamic-segments.catch-all.$.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString } from 'nuqs'\nimport { useLoaderData, useParams, type LoaderFunctionArgs } from 'react-router'\n\nconst loadSearchParams = createLoader({\n  test: parseAsString\n})\n\nexport function loader({ request, params }: LoaderFunctionArgs) {\n  const { test: serverState } = loadSearchParams(request)\n  return {\n    serverState,\n    serverSegments: params['*']?.split('/') ?? []\n  }\n}\n\nexport default function Page() {\n  const { serverState, serverSegments } = useLoaderData<typeof loader>()\n  const clientSegments = useParams()['*']?.split('/') ?? []\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments environment=\"client\" segments={clientSegments} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/dynamic-segments.dynamic.$segment.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString } from 'nuqs'\nimport { useLoaderData, useParams, type LoaderFunctionArgs } from 'react-router'\n\nconst loadSearchParams = createLoader({\n  test: parseAsString\n})\n\nexport function loader({ request, params }: LoaderFunctionArgs) {\n  const { test: serverState } = loadSearchParams(request)\n  return {\n    serverState,\n    serverSegments: [params.segment as string]\n  }\n}\n\nexport default function Page() {\n  const { serverState, serverSegments } = useLoaderData<typeof loader>()\n  const params = useParams()\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments\n        environment=\"client\"\n        segments={[params.segment as string]}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/flush-after-navigate.useQueryState.end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/flush-after-navigate.useQueryState.start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStateStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStateStart path=\"/flush-after-navigate/useQueryState\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/flush-after-navigate.useQueryStates.end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/flush-after-navigate.useQueryStates.start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStatesStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStatesStart path=\"/flush-after-navigate/useQueryStates\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/fog-of-war._index.tsx",
    "content": "import { FogOfWarStartPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default function FogOfWarStart() {\n  return <FogOfWarStartPage resultHref=\"/fog-of-war/result\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/fog-of-war.result.tsx",
    "content": "import { FogOfWarResultPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default FogOfWarResultPage\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/form.useQueryState.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/form.useQueryStates.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/hash-preservation.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/history-sync.tsx",
    "content": "import { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport default HistorySync\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/json.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\n\nexport default Json\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/key-isolation.useQueryState.tsx",
    "content": "import { KeyIsolationUseQueryState } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/key-isolation.useQueryStates.tsx",
    "content": "import { KeyIsolationUseQueryStates } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/life-and-death.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport default LifeAndDeath\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/linking.useQueryState.other.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/linking.useQueryState.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/linking.useQueryStates.other.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/linking.useQueryStates.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/loader.tsx",
    "content": "import { LoaderRenderer, loadSearchParams } from 'e2e-shared/specs/loader'\nimport type { LoaderFunctionArgs } from 'react-router'\nimport type { Route } from './+types/loader'\n\nexport function loader({ request }: LoaderFunctionArgs) {\n  return loadSearchParams(request)\n}\n\nexport default function Page({\n  loaderData: serverValues\n}: Route.ComponentProps) {\n  return <LoaderRenderer serverValues={serverValues} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/native-array.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\n\nexport default NativeArray\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/popstate-queue-reset.other.tsx",
    "content": "import { PopstateQueueResetOther } from 'e2e-shared/specs/popstate-queue-reset'\n\nexport default PopstateQueueResetOther\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/popstate-queue-reset.tsx",
    "content": "import { PopstateQueueResetClient } from 'e2e-shared/specs/popstate-queue-reset'\nimport { useNavigate } from 'react-router'\n\nexport default function Page() {\n  const navigate = useNavigate()\n  return (\n    <PopstateQueueResetClient\n      onNavigateToOther={() => navigate('/popstate-queue-reset/other')}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/pretty-urls.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport default PrettyUrls\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/push.useQueryState.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/push.useQueryStates.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/rate-limits.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\n\nexport default RateLimits\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/referential-stability.useQueryState.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/referential-stability.useQueryStates.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport {\n  loadParams,\n  loadSearchParams\n} from 'e2e-shared/specs/render-count.params'\nimport { setTimeout } from 'node:timers/promises'\nimport { useParams, type LoaderFunctionArgs } from 'react-router'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const { delay } = loadSearchParams(request)\n  if (delay) {\n    await setTimeout(delay)\n  }\n  return null\n}\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\nimport { useParams } from 'react-router'\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader.tsx",
    "content": "import { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\nimport { useParams } from 'react-router'\n\nexport function loader() {\n  return null\n}\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-1099.useQueryState.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryState\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-1099.useQueryStates.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryStates\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-1293.a.tsx",
    "content": "import { Repro1293PageA } from 'e2e-shared/specs/repro-1293'\n\nexport default function Page() {\n  return <Repro1293PageA linkHref=\"/repro-1293/b\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-1293.b.tsx",
    "content": "import { Repro1293PageB } from 'e2e-shared/specs/repro-1293'\n\nexport default function Page() {\n  return <Repro1293PageB />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-1365.tsx",
    "content": "import { Repro1365 } from 'e2e-shared/specs/repro-1365'\n\nexport default Repro1365\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-359.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\n\nexport default Repro359\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-839.tsx",
    "content": "import { Repro839 } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence'\nimport { useLocation, useNavigate } from 'react-router'\n\nexport default function Page() {\n  return <Repro839 useLocation={useLocation} useNavigate={useNavigate} />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/repro-982.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\n\nexport default Repro982\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/routing.useQueryState.other.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/routing.useQueryState.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/routing.useQueryStates.other.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/routing.useQueryStates.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/scroll.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\n\nexport default Scroll\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/shallow.useQueryState.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\nimport { useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url)\n  return {\n    serverState: url.searchParams.get('test')\n  }\n}\n\nexport default function Page() {\n  const { serverState } = useLoaderData<typeof loader>()\n  return (\n    <>\n      <ShallowUseQueryState />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/shallow.useQueryStates.tsx",
    "content": "import { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\nimport { useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url)\n  return {\n    serverState: url.searchParams.get('test')\n  }\n}\n\nexport default function Page() {\n  const { serverState } = useLoaderData<typeof loader>()\n  return (\n    <>\n      <ShallowUseQueryStates />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes/stitching.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\n\nexport default Stitching\n"
  },
  {
    "path": "packages/e2e/react-router/v7/app/routes.ts",
    "content": "import { type RouteConfig, layout, route } from '@react-router/dev/routes'\n\nexport default [\n  // prettier-ignore\n  layout('layout.tsx', [\n    // Shared E2E tests\n    route('/basic-io/useQueryState',                    './routes/basic-io.useQueryState.tsx'),\n    route('/basic-io/useQueryStates',                   './routes/basic-io.useQueryStates.tsx'),\n    route('/conditional-rendering/useQueryState',       './routes/conditional-rendering.useQueryState.tsx'),\n    route('/conditional-rendering/useQueryStates',      './routes/conditional-rendering.useQueryStates.tsx'),\n    route('/form/useQueryState',                        './routes/form.useQueryState.tsx'),\n    route('/form/useQueryStates',                       './routes/form.useQueryStates.tsx'),\n    route('/hash-preservation',                         './routes/hash-preservation.tsx'),\n    route('/history-sync',                              './routes/history-sync.tsx'),\n    route('/json',                                      './routes/json.tsx'),\n    route('/life-and-death',                            './routes/life-and-death.tsx'),\n    route('/linking/useQueryState',                     './routes/linking.useQueryState.tsx'),\n    route('/linking/useQueryState/other',               './routes/linking.useQueryState.other.tsx'),\n    route('/linking/useQueryStates',                    './routes/linking.useQueryStates.tsx'),\n    route('/linking/useQueryStates/other',              './routes/linking.useQueryStates.other.tsx'),\n    route('/native-array',                              './routes/native-array.tsx'),\n    route('/pretty-urls',                               './routes/pretty-urls.tsx'),\n    route('/referential-stability/useQueryState',       './routes/referential-stability.useQueryState.tsx'),\n    route('/referential-stability/useQueryStates',      './routes/referential-stability.useQueryStates.tsx'),\n    route('/routing/useQueryState',                     './routes/routing.useQueryState.tsx'),\n    route('/routing/useQueryState/other',               './routes/routing.useQueryState.other.tsx'),\n    route('/routing/useQueryStates',                    './routes/routing.useQueryStates.tsx'),\n    route('/routing/useQueryStates/other',              './routes/routing.useQueryStates.other.tsx'),\n    route('/scroll',                                    './routes/scroll.tsx'),\n\n    // Local tests\n    route('/debounce',                                  './routes/debounce.tsx'),\n    route('/debounce/other',                            './routes/debounce.other.tsx'),\n    route('/dynamic-segments/catch-all?/*',             './routes/dynamic-segments.catch-all.$.tsx'),\n    route('/dynamic-segments/dynamic/:segment',         './routes/dynamic-segments.dynamic.$segment.tsx'),\n    route('/flush-after-navigate/useQueryState/end',    './routes/flush-after-navigate.useQueryState.end.tsx'),\n    route('/flush-after-navigate/useQueryState/start',  './routes/flush-after-navigate.useQueryState.start.tsx'),\n    route('/flush-after-navigate/useQueryStates/end',   './routes/flush-after-navigate.useQueryStates.end.tsx'),\n    route('/flush-after-navigate/useQueryStates/start', './routes/flush-after-navigate.useQueryStates.start.tsx'),\n    route('/fog-of-war',                                './routes/fog-of-war._index.tsx'),\n    route('/fog-of-war/result',                         './routes/fog-of-war.result.tsx'),\n    route('/key-isolation/useQueryState',               './routes/key-isolation.useQueryState.tsx'),\n    route('/key-isolation/useQueryStates',              './routes/key-isolation.useQueryStates.tsx'),\n    route('/loader',                                    './routes/loader.tsx'),\n    route('/popstate-queue-reset',                      './routes/popstate-queue-reset.tsx'),\n    route('/popstate-queue-reset/other',                './routes/popstate-queue-reset.other.tsx'),\n    route('/push/useQueryState',                        './routes/push.useQueryState.tsx'),\n    route('/push/useQueryStates',                       './routes/push.useQueryStates.tsx'),\n    route('/rate-limits',                               './routes/rate-limits.tsx'),\n    route('/shallow/useQueryState',                     './routes/shallow.useQueryState.tsx'),\n    route('/shallow/useQueryStates',                    './routes/shallow.useQueryStates.tsx'),\n    route('/stitching',                                 './routes/stitching.tsx'),\n\n    // Render count\n    route('/render-count/:hook/:shallow/:history/:startTransition/no-loader',    './routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx'),\n    route('/render-count/:hook/:shallow/:history/:startTransition/sync-loader',  './routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader.tsx'),\n    route('/render-count/:hook/:shallow/:history/:startTransition/async-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx'),\n\n    // Reproductions\n    route('/repro-359',                 './routes/repro-359.tsx'),\n    route('/repro-839',                 './routes/repro-839.tsx'),\n    route('/repro-982',                 './routes/repro-982.tsx'),\n    route('/repro-1099/useQueryState',  './routes/repro-1099.useQueryState.tsx'),\n    route('/repro-1099/useQueryStates', './routes/repro-1099.useQueryStates.tsx'),\n    route('/repro-1293/a',              './routes/repro-1293.a.tsx'),\n    route('/repro-1293/b',              './routes/repro-1293.b.tsx'),\n    route('/repro-1365',                './routes/repro-1365.tsx'),\n  ])\n] satisfies RouteConfig\n"
  },
  {
    "path": "packages/e2e/react-router/v7/package.json",
    "content": "{\n  \"name\": \"e2e-react-router-v7\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev --port 3007\",\n    \"start\": \"cross-env NODE_ENV=production ./server.mjs\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:types\": \"react-router typegen && tsc\",\n    \"test:playwright\": \"playwright test --project=chromium\"\n  },\n  \"dependencies\": {\n    \"@react-router/node\": \"^7.13.0\",\n    \"@react-router/serve\": \"^7.13.0\",\n    \"isbot\": \"^5.1.34\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"react-router\": \"^7.13.0\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@react-router/dev\": \"^7.13.0\",\n    \"@react-router/express\": \"^7.13.0\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"compression\": \"^1.8.1\",\n    \"cross-env\": \"^10.1.0\",\n    \"e2e-shared\": \"workspace:*\",\n    \"express\": \"^4.22.1\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\",\n    \"vite-tsconfig-paths\": \"^6.0.5\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'pnpm run start',\n  port: 3007\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/react-router.config.ts",
    "content": "import type { Config } from '@react-router/dev/config'\n\nexport default {\n  // Config options...\n  // Server-side render by default, to enable SPA mode set this to `false`\n  ssr: true\n} satisfies Config\n"
  },
  {
    "path": "packages/e2e/react-router/v7/server.mjs",
    "content": "#!/usr/bin/env node\n// @ts-check\n\nimport { createRequestHandler } from '@react-router/express'\nimport compression from 'compression'\nimport express from 'express'\nimport os from 'node:os'\nimport path from 'node:path'\nimport url from 'node:url'\n\nprocess.env.NODE_ENV = process.env.NODE_ENV ?? 'production'\n\nrun()\n\nasync function run() {\n  const port = 3007\n  const buildPath = path.resolve('build/server/index.js')\n\n  /** @type {import('react-router').ServerBuild } */\n  const build = await import(url.pathToFileURL(buildPath).href)\n\n  const onListen = () => {\n    const address =\n      process.env.HOST ||\n      Object.values(os.networkInterfaces())\n        .flat()\n        .find(ip => String(ip?.family).includes('4') && !ip?.internal)?.address\n\n    if (!address) {\n      console.log(`[react-router-v7] http://localhost:${port}`)\n    } else {\n      console.log(\n        `[react-router-v7] http://localhost:${port} (http://${address}:${port})`\n      )\n    }\n  }\n\n  const app = express()\n  app.disable('x-powered-by')\n  app.use(compression())\n  app.use(\n    path.posix.join(build.publicPath, 'assets'),\n    express.static(path.join(build.assetsBuildDirectory, 'assets'), {\n      immutable: true,\n      maxAge: '1y'\n    })\n  )\n  app.use(build.publicPath, express.static(build.assetsBuildDirectory))\n  app.use(express.static('public', { maxAge: '1h' }))\n\n  app.all(\n    '*',\n    createRequestHandler({\n      build,\n      mode: process.env.NODE_ENV\n    })\n  )\n\n  const server = process.env.HOST\n    ? app.listen(port, process.env.HOST, onListen)\n    : app.listen(port, onListen)\n\n  ;['SIGTERM', 'SIGINT'].forEach(signal => {\n    process.once(signal, () => server?.close(console.error))\n  })\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/repro-839.spec.ts",
    "content": "import { testRepro839LocationStatePersistence } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence.spec.ts'\n\ntestRepro839LocationStatePersistence({\n  path: '/repro-839'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/debounce.spec.ts",
    "content": "import { testDebounce } from 'e2e-shared/specs/debounce.spec.ts'\n\ntestDebounce({ path: '/debounce' })\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/dynamic-segments.spec.ts",
    "content": "import { testDynamicSegments } from 'e2e-shared/specs/dynamic-segments.spec.ts'\n\ntestDynamicSegments({\n  path: '/dynamic-segments/dynamic/foo',\n  expectedSegments: ['foo']\n})\n\ntestDynamicSegments({\n  path: '/dynamic-segments/catch-all',\n  expectedSegments: ['']\n})\n\ntestDynamicSegments({\n  path: '/dynamic-segments/catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c']\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/flush-after-navigate.spec.ts",
    "content": "import { testFlushAfterNavigate } from 'e2e-shared/specs/flush-after-navigate.spec.ts'\n\ntestFlushAfterNavigate({\n  path: '/flush-after-navigate/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestFlushAfterNavigate({\n  path: '/flush-after-navigate/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/fog-of-war.spec.ts",
    "content": "import { testFogOfWar } from 'e2e-shared/specs/react-router/fog-of-war.spec.ts'\n\ntestFogOfWar({\n  path: '/fog-of-war'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/key-isolation.spec.ts",
    "content": "import { testKeyIsolation } from 'e2e-shared/specs/key-isolation.spec.ts'\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/loader.spec.ts",
    "content": "import { testLoader } from 'e2e-shared/specs/loader.spec.ts'\n\ntestLoader({ path: '/loader' })\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/popstate-queue-reset.spec.ts",
    "content": "import { testPopstateQueueReset } from 'e2e-shared/specs/popstate-queue-reset.spec.ts'\n\ntestPopstateQueueReset({\n  path: '/popstate-queue-reset'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/push/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestPush({\n  path: '/push/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/render-count.spec.ts",
    "content": "import { testRenderCount } from 'e2e-shared/specs/render-count.spec.ts'\n\nconst hooks = ['useQueryState', 'useQueryStates'] as const\nconst shallows = [true, false] as const\nconst histories = ['replace', 'push'] as const\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/no-loader`,\n          description: 'no loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2 + (shallow === false ? 2 : 0)\n          }\n        })\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/sync-loader`,\n          description: 'sync loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2 + (shallow === false ? 2 : 0)\n          }\n        })\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        for (const delay of shallow === false ? [0, 50] : [0]) {\n          testRenderCount({\n            path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/async-loader?delay=${delay}`,\n            description: 'async loader',\n            hook,\n            props: {\n              shallow,\n              history,\n              startTransition,\n              delay\n            },\n            expected: {\n              mount: 1,\n              update: 2 + (shallow === false ? 2 : 0)\n            }\n          })\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/repro-1099/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRepro1099({\n  path: '/repro-1099/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/repro-1293.spec.ts",
    "content": "import { testRepro1293 } from 'e2e-shared/specs/repro-1293.spec.ts'\n\ntestRepro1293({\n  path: '/repro-1293'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/repro-1365.spec.ts",
    "content": "import { testRepro1365 } from 'e2e-shared/specs/repro-1365.spec.ts'\n\ntestRepro1365({\n  path: '/repro-1365'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/repro-359.spec.ts",
    "content": "import { testRepro359 } from 'e2e-shared/specs/repro-359.spec.ts'\n\ntestRepro359({\n  path: '/repro-359'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/repro-982.spec.ts",
    "content": "import { testRepro982 } from 'e2e-shared/specs/repro-982.spec.ts'\n\ntestRepro982({\n  path: '/repro-982'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/shallow.spec.ts",
    "content": "import { testShallow } from 'e2e-shared/specs/shallow.spec.ts'\n\n// Note: React Router v7 supports SSR, so no supportsSSR: false needed\ntestShallow({\n  path: '/shallow/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestShallow({\n  path: '/shallow/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared/stitching.spec.ts",
    "content": "import { testStitching } from 'e2e-shared/specs/stitching.spec.ts'\n\ntestStitching({\n  path: '/stitching'\n})\n"
  },
  {
    "path": "packages/e2e/react-router/v7/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests()\n"
  },
  {
    "path": "packages/e2e/react-router/v7/tsconfig.json",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\".react-router/**\", \"build/**\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/react-router/v7/vite.config.ts",
    "content": "import { reactRouter } from '@react-router/dev/vite'\nimport { defineConfig } from 'vite'\nimport tsconfigPaths from 'vite-tsconfig-paths'\n\nexport default defineConfig({\n  plugins: [reactRouter(), tsconfigPaths()],\n  build: {\n    sourcemap: false, // Disable sourcemaps for e2e test apps\n    rolldownOptions: {\n      onwarn(warning, warn) {\n        // Suppress sourcemap warnings from workspace dependencies\n        if (warning.code === 'SOURCEMAP_ERROR') return\n        warn(warning)\n      }\n    }\n  }\n})\n"
  },
  {
    "path": "packages/e2e/remix/.eslintrc.cjs",
    "content": "/**\n * This is intended to be a basic starting point for linting in your app.\n * It relies on recommended configs out of the box for simplicity, but you can\n * and should modify this configuration to best suit your team's needs.\n */\n\n/** @type {import('eslint').Linter.Config} */\nmodule.exports = {\n  root: true,\n  parserOptions: {\n    ecmaVersion: \"latest\",\n    sourceType: \"module\",\n    ecmaFeatures: {\n      jsx: true,\n    },\n  },\n  env: {\n    browser: true,\n    commonjs: true,\n    es6: true,\n  },\n  ignorePatterns: [\"!**/.server\", \"!**/.client\"],\n\n  // Base config\n  extends: [\"eslint:recommended\"],\n\n  overrides: [\n    // React\n    {\n      files: [\"**/*.{js,jsx,ts,tsx}\"],\n      plugins: [\"react\", \"jsx-a11y\"],\n      extends: [\n        \"plugin:react/recommended\",\n        \"plugin:react/jsx-runtime\",\n        \"plugin:react-hooks/recommended\",\n        \"plugin:jsx-a11y/recommended\",\n      ],\n      settings: {\n        react: {\n          version: \"detect\",\n        },\n        formComponents: [\"Form\"],\n        linkComponents: [\n          { name: \"Link\", linkAttribute: \"to\" },\n          { name: \"NavLink\", linkAttribute: \"to\" },\n        ],\n        \"import/resolver\": {\n          typescript: {},\n        },\n      },\n    },\n\n    // Typescript\n    {\n      files: [\"**/*.{ts,tsx}\"],\n      plugins: [\"@typescript-eslint\", \"import\"],\n      parser: \"@typescript-eslint/parser\",\n      settings: {\n        \"import/internal-regex\": \"^~/\",\n        \"import/resolver\": {\n          node: {\n            extensions: [\".ts\", \".tsx\"],\n          },\n          typescript: {\n            alwaysTryTypes: true,\n          },\n        },\n      },\n      extends: [\n        \"plugin:@typescript-eslint/recommended\",\n        \"plugin:import/recommended\",\n        \"plugin:import/typescript\",\n      ],\n    },\n\n    // Node\n    {\n      files: [\".eslintrc.cjs\"],\n      env: {\n        node: true,\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "packages/e2e/remix/.gitignore",
    "content": "node_modules\n\n/.cache\n/build\n.env\n\n# Playwright\n.playwright/\n\n"
  },
  {
    "path": "packages/e2e/remix/README.md",
    "content": "# Welcome to Remix!\n\n- 📖 [Remix docs](https://remix.run/docs)\n\n## Development\n\nRun the dev server:\n\n```shellscript\nnpm run dev\n```\n\n## Deployment\n\nFirst, build your app for production:\n\n```sh\nnpm run build\n```\n\nThen run the app in production mode:\n\n```sh\nnpm start\n```\n\nNow you'll need to pick a host to deploy it to.\n\n### DIY\n\nIf you're familiar with deploying Node applications, the built-in Remix app server is production-ready.\n\nMake sure to deploy the output of `npm run build`\n\n- `build/server`\n- `build/client`\n\n## Styling\n\nThis template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information.\n"
  },
  {
    "path": "packages/e2e/remix/app/entry.client.tsx",
    "content": "/**\n * By default, Remix will handle hydrating your app on the client for you.\n * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨\n * For more information, see https://remix.run/file-conventions/entry.client\n */\n\nimport { RemixBrowser } from \"@remix-run/react\";\nimport { startTransition, StrictMode } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\n\nstartTransition(() => {\n  hydrateRoot(\n    document,\n    <StrictMode>\n      <RemixBrowser />\n    </StrictMode>\n  );\n});\n"
  },
  {
    "path": "packages/e2e/remix/app/entry.server.tsx",
    "content": "/**\n * By default, Remix will handle generating the HTTP Response for you.\n * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨\n * For more information, see https://remix.run/file-conventions/entry.server\n */\n\nimport { PassThrough } from \"node:stream\";\n\nimport type { AppLoadContext, EntryContext } from \"@remix-run/node\";\nimport { createReadableStreamFromReadable } from \"@remix-run/node\";\nimport { RemixServer } from \"@remix-run/react\";\nimport { isbot } from \"isbot\";\nimport { renderToPipeableStream } from \"react-dom/server\";\n\nconst ABORT_DELAY = 5_000;\n\nexport default function handleRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext,\n  // This is ignored so we can keep it in the template for visibility.  Feel\n  // free to delete this parameter in your app if you're not using it!\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  loadContext: AppLoadContext\n) {\n  return isbot(request.headers.get(\"user-agent\") || \"\")\n    ? handleBotRequest(\n        request,\n        responseStatusCode,\n        responseHeaders,\n        remixContext\n      )\n    : handleBrowserRequest(\n        request,\n        responseStatusCode,\n        responseHeaders,\n        remixContext\n      );\n}\n\nfunction handleBotRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  return new Promise((resolve, reject) => {\n    let shellRendered = false;\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n        abortDelay={ABORT_DELAY}\n      />,\n      {\n        onAllReady() {\n          shellRendered = true;\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n\n          pipe(body);\n        },\n        onShellError(error: unknown) {\n          reject(error);\n        },\n        onError(error: unknown) {\n          responseStatusCode = 500;\n          // Log streaming rendering errors from inside the shell.  Don't log\n          // errors encountered during initial shell rendering since they'll\n          // reject and get logged in handleDocumentRequest.\n          if (shellRendered) {\n            console.error(error);\n          }\n        },\n      }\n    );\n\n    setTimeout(abort, ABORT_DELAY);\n  });\n}\n\nfunction handleBrowserRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  return new Promise((resolve, reject) => {\n    let shellRendered = false;\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n        abortDelay={ABORT_DELAY}\n      />,\n      {\n        onShellReady() {\n          shellRendered = true;\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n\n          pipe(body);\n        },\n        onShellError(error: unknown) {\n          reject(error);\n        },\n        onError(error: unknown) {\n          responseStatusCode = 500;\n          // Log streaming rendering errors from inside the shell.  Don't log\n          // errors encountered during initial shell rendering since they'll\n          // reject and get logged in handleDocumentRequest.\n          if (shellRendered) {\n            console.error(error);\n          }\n        },\n      }\n    );\n\n    setTimeout(abort, ABORT_DELAY);\n  });\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/layout.tsx",
    "content": "import { Outlet, Link as RemixLink, useNavigate } from '@remix-run/react'\nimport { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider, type LinkProps } from 'e2e-shared/components/link'\nimport { RouterProvider, type Router } from 'e2e-shared/components/router'\n\nfunction Link({ href, ...props }: LinkProps) {\n  return <RemixLink to={href} {...props} />\n}\n\nexport default function RootLayout() {\n  return (\n    <>\n      <HydrationMarker />\n      <LinkProvider Link={Link}>\n        <RouterProvider useRouter={useRouter}>\n          <Outlet />\n        </RouterProvider>\n      </LinkProvider>\n    </>\n  )\n}\n\nfunction useRouter(): Router {\n  const navigate = useNavigate()\n  return {\n    replace(url, options) {\n      if (options.shallow) {\n        history.replaceState(history.state, '', url)\n      } else {\n        navigate(url, { replace: true })\n      }\n    },\n    push(url, options) {\n      if (options.shallow) {\n        history.pushState(history.state, '', url)\n      } else {\n        navigate(url, { replace: false })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/root.tsx",
    "content": "import { Links, Meta, Scripts, ScrollRestoration } from '@remix-run/react'\nimport { NuqsAdapter } from 'nuqs/adapters/remix'\nimport RootLayout from './layout'\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  )\n}\n\nexport default function App() {\n  return (\n    <NuqsAdapter>\n      <RootLayout />\n    </NuqsAdapter>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/basic-io.useQueryState.tsx",
    "content": "import { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStateBasicIO\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/basic-io.useQueryStates.tsx",
    "content": "import { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport default UseQueryStatesBasicIO\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/conditional-rendering.useQueryState.tsx",
    "content": "import { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryState\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/conditional-rendering.useQueryStates.tsx",
    "content": "import { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport default ConditionalRenderingUseQueryStates\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/debounce-other.tsx",
    "content": "import { Link } from '@remix-run/react'\n\nexport default function Page() {\n  return <Link to=\"/debounce\">Go back to the previous page</Link>\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/debounce.tsx",
    "content": "import type { LoaderFunctionArgs } from '@remix-run/node'\nimport { useLoaderData } from '@remix-run/react'\nimport { DebounceClient } from 'e2e-shared/specs/debounce-client'\nimport { DebounceServer } from 'e2e-shared/specs/debounce-server'\nimport {\n  type DemoSearchParams,\n  loadDemoSearchParams\n} from 'e2e-shared/specs/debounce.defs'\n\nexport function loader({ request }: LoaderFunctionArgs) {\n  return loadDemoSearchParams(request)\n}\n\nexport default function Page() {\n  const serverState = useLoaderData<DemoSearchParams>()\n  return (\n    <DebounceServer state={serverState}>\n      <DebounceClient navigateHref=\"/debounce-other\" />\n    </DebounceServer>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/dynamic-segments.catch-all.$.tsx",
    "content": "import type { LoaderFunctionArgs } from '@remix-run/node'\nimport { useLoaderData, useParams } from '@remix-run/react'\nimport { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString } from 'nuqs'\n\nconst loadSearchParams = createLoader({\n  test: parseAsString\n})\n\nexport function loader({ request, params }: LoaderFunctionArgs) {\n  const { test: serverState } = loadSearchParams(request)\n  return {\n    serverState,\n    serverSegments: params['*']?.split('/') ?? []\n  }\n}\n\nexport default function Page() {\n  const { serverState, serverSegments } = useLoaderData<typeof loader>()\n  const clientSegments = useParams()['*']?.split('/') ?? []\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments environment=\"client\" segments={clientSegments} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/dynamic-segments.dynamic.$segment.tsx",
    "content": "import type { LoaderFunctionArgs } from '@remix-run/node'\nimport { useLoaderData, useParams } from '@remix-run/react'\nimport { Display } from 'e2e-shared/components/display'\nimport { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'\nimport { createLoader, parseAsString } from 'nuqs'\n\nconst loadSearchParams = createLoader({\n  test: parseAsString\n})\n\nexport function loader({ request, params }: LoaderFunctionArgs) {\n  const { test: serverState } = loadSearchParams(request)\n  return {\n    serverState,\n    serverSegments: [params.segment]\n  }\n}\n\nexport default function Page() {\n  const { serverState, serverSegments } = useLoaderData<typeof loader>()\n  const params = useParams()\n  return (\n    <>\n      <UrlControls>\n        <Display environment=\"server\" state={serverState} />\n      </UrlControls>\n      <DisplaySegments environment=\"server\" segments={serverSegments} />\n      <DisplaySegments\n        environment=\"client\"\n        segments={[params.segment as string]}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/flush-after-navigate.useQueryState.end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/flush-after-navigate.useQueryState.start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStateStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStateStart path=\"/flush-after-navigate/useQueryState\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/flush-after-navigate.useQueryStates.end.tsx",
    "content": "import { FlushAfterNavigateEnd } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default FlushAfterNavigateEnd\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/flush-after-navigate.useQueryStates.start.tsx",
    "content": "import { FlushAfterNavigateUseQueryStatesStart } from 'e2e-shared/specs/flush-after-navigate'\n\nexport default function Page() {\n  return (\n    <FlushAfterNavigateUseQueryStatesStart path=\"/flush-after-navigate/useQueryStates\" />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/fog-of-war._index.tsx",
    "content": "import { FogOfWarStartPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default function FogOfWarStart() {\n  return <FogOfWarStartPage resultHref=\"/fog-of-war/result\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/fog-of-war.result.tsx",
    "content": "import { FogOfWarResultPage } from 'e2e-shared/specs/react-router/fog-of-war'\n\nexport default FogOfWarResultPage\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/form.useQueryState.tsx",
    "content": "import { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryState\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/form.useQueryStates.tsx",
    "content": "import { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport default TestFormUseQueryStates\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/hash-preservation.tsx",
    "content": "import { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport default HashPreservation\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/history-sync.tsx",
    "content": "import { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport default HistorySync\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/json.tsx",
    "content": "import { Json } from 'e2e-shared/specs/json'\n\nexport default Json\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/key-isolation.useQueryState.tsx",
    "content": "import { KeyIsolationUseQueryState } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryState\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/key-isolation.useQueryStates.tsx",
    "content": "import { KeyIsolationUseQueryStates } from 'e2e-shared/specs/key-isolation'\n\nexport default KeyIsolationUseQueryStates\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/life-and-death.tsx",
    "content": "import { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport default LifeAndDeath\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/linking.useQueryState.other.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/linking.useQueryState.tsx",
    "content": "import { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/linking.useQueryStates.other.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/linking.useQueryStates.tsx",
    "content": "import { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/loader.tsx",
    "content": "import type { LoaderFunctionArgs } from '@remix-run/node'\nimport { useLoaderData } from '@remix-run/react'\nimport { LoaderRenderer, loadSearchParams } from 'e2e-shared/specs/loader'\n\nexport function loader({ request }: LoaderFunctionArgs) {\n  return loadSearchParams(request)\n}\n\nexport default function Page() {\n  const serverValues = useLoaderData<typeof loader>()\n  return <LoaderRenderer serverValues={serverValues} />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/native-array.tsx",
    "content": "import { NativeArray } from 'e2e-shared/specs/native-array'\n\nexport default NativeArray\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/popstate-queue-reset-other.tsx",
    "content": "import { PopstateQueueResetOther } from 'e2e-shared/specs/popstate-queue-reset'\n\nexport default PopstateQueueResetOther\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/popstate-queue-reset.tsx",
    "content": "import { PopstateQueueResetClient } from 'e2e-shared/specs/popstate-queue-reset'\nimport { useNavigate } from '@remix-run/react'\n\nexport default function Page() {\n  const navigate = useNavigate()\n  return (\n    <PopstateQueueResetClient\n      onNavigateToOther={() => navigate('/popstate-queue-reset-other')}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/pretty-urls.tsx",
    "content": "import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport default PrettyUrls\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/push.useQueryState.tsx",
    "content": "import { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryState\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/push.useQueryStates.tsx",
    "content": "import { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport default PushUseQueryStates\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/rate-limits.tsx",
    "content": "import { RateLimits } from 'e2e-shared/specs/rate-limits'\n\nexport default RateLimits\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/referential-stability.useQueryState.tsx",
    "content": "import { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryState\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/referential-stability.useQueryStates.tsx",
    "content": "import { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport default ReferentialStabilityUseQueryStates\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx",
    "content": "import { LoaderFunctionArgs } from '@remix-run/node'\nimport { useParams } from '@remix-run/react'\nimport { RenderCount } from 'e2e-shared/specs/render-count'\nimport {\n  loadParams,\n  loadSearchParams\n} from 'e2e-shared/specs/render-count.params'\nimport { setTimeout } from 'node:timers/promises'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const { delay } = loadSearchParams(request)\n  if (delay) {\n    await setTimeout(delay)\n  }\n  return null\n}\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx",
    "content": "import { useParams } from '@remix-run/react'\nimport { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader.tsx",
    "content": "import { useParams } from '@remix-run/react'\nimport { RenderCount } from 'e2e-shared/specs/render-count'\nimport { loadParams } from 'e2e-shared/specs/render-count.params'\n\nexport function loader() {\n  return null\n}\n\nexport default function Page() {\n  const params = loadParams(useParams())\n  return <RenderCount {...params} />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-1099.useQueryState.tsx",
    "content": "import { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryState\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-1099.useQueryStates.tsx",
    "content": "import { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport default Repro1099UseQueryStates\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-1293.a.tsx",
    "content": "import { Repro1293PageA } from 'e2e-shared/specs/repro-1293'\n\nexport default function Page() {\n  return <Repro1293PageA linkHref=\"/repro-1293/b\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-1293.b.tsx",
    "content": "import { Repro1293PageB } from 'e2e-shared/specs/repro-1293'\n\nexport default Repro1293PageB\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-1365.tsx",
    "content": "import { Repro1365 } from 'e2e-shared/specs/repro-1365'\n\nexport default Repro1365\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-359.tsx",
    "content": "import { Repro359 } from 'e2e-shared/specs/repro-359'\n\nexport default Repro359\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-839.tsx",
    "content": "import { useLocation, useNavigate } from '@remix-run/react'\nimport { Repro839 } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence'\n\nexport default function Page() {\n  return <Repro839 useLocation={useLocation} useNavigate={useNavigate} />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/repro-982.tsx",
    "content": "import { Repro982 } from 'e2e-shared/specs/repro-982'\n\nexport default Repro982\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/routing.useQueryState.other.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/routing.useQueryState.tsx",
    "content": "import { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/routing.useQueryStates.other.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/routing.useQueryStates.tsx",
    "content": "import { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport default function Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/scroll.tsx",
    "content": "import { Scroll } from 'e2e-shared/specs/scroll'\n\nexport default Scroll\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/shallow.useQueryState.tsx",
    "content": "import type { LoaderFunctionArgs } from '@remix-run/node'\nimport { useLoaderData } from '@remix-run/react'\nimport { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url)\n  return {\n    serverState: url.searchParams.get('test')\n  }\n}\n\nexport default function Page() {\n  const { serverState } = useLoaderData<typeof loader>()\n  return (\n    <>\n      <ShallowUseQueryState />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/shallow.useQueryStates.tsx",
    "content": "import type { LoaderFunctionArgs } from '@remix-run/node'\nimport { useLoaderData } from '@remix-run/react'\nimport { Display } from 'e2e-shared/components/display'\nimport { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const url = new URL(request.url)\n  return {\n    serverState: url.searchParams.get('test')\n  }\n}\n\nexport default function Page() {\n  const { serverState } = useLoaderData<typeof loader>()\n  return (\n    <>\n      <ShallowUseQueryStates />\n      <Display environment=\"server\" state={serverState} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/remix/app/routes/stitching.tsx",
    "content": "import { Stitching } from 'e2e-shared/specs/stitching'\n\nexport default Stitching\n"
  },
  {
    "path": "packages/e2e/remix/package.json",
    "content": "{\n  \"name\": \"e2e-remix\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"remix vite:build\",\n    \"dev\": \"remix vite:dev --port 3003\",\n    \"start\": \"cross-env NODE_ENV=production PORT=3003 remix-serve ./build/server/index.js\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:types\": \"tsc\",\n    \"test:playwright\": \"playwright test --project=chromium\"\n  },\n  \"dependencies\": {\n    \"@remix-run/node\": \"^2.17.4\",\n    \"@remix-run/react\": \"^2.17.4\",\n    \"@remix-run/serve\": \"^2.17.4\",\n    \"isbot\": \"^5.1.34\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@remix-run/dev\": \"^2.17.4\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"cross-env\": \"^10.1.0\",\n    \"e2e-shared\": \"workspace:*\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\",\n    \"vite-tsconfig-paths\": \"^6.0.5\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/remix/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'pnpm run start',\n  port: 3003\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/popstate-queue-reset.spec.ts",
    "content": "import { testPopstateQueueReset } from 'e2e-shared/specs/popstate-queue-reset.spec.ts'\n\ntestPopstateQueueReset({\n  path: '/popstate-queue-reset',\n  otherPath: '/popstate-queue-reset-other'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/repro-839.spec.ts",
    "content": "import { testRepro839LocationStatePersistence } from 'e2e-shared/specs/react-router/repro-839-location-state-persistence.spec.ts'\n\ntestRepro839LocationStatePersistence({\n  path: '/repro-839'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/debounce.spec.ts",
    "content": "import { testDebounce } from 'e2e-shared/specs/debounce.spec.ts'\n\ntestDebounce({ path: '/debounce', otherPath: '/debounce-other' })\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/dynamic-segments.spec.ts",
    "content": "import { testDynamicSegments } from 'e2e-shared/specs/dynamic-segments.spec.ts'\n\ntestDynamicSegments({\n  path: '/dynamic-segments/dynamic/foo',\n  expectedSegments: ['foo']\n})\n\ntestDynamicSegments({\n  path: '/dynamic-segments/catch-all',\n  expectedSegments: ['']\n})\n\ntestDynamicSegments({\n  path: '/dynamic-segments/catch-all/a/b/c',\n  expectedSegments: ['a', 'b', 'c']\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/flush-after-navigate.spec.ts",
    "content": "import { testFlushAfterNavigate } from 'e2e-shared/specs/flush-after-navigate.spec.ts'\n\ntestFlushAfterNavigate({\n  path: '/flush-after-navigate/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestFlushAfterNavigate({\n  path: '/flush-after-navigate/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/fog-of-war.spec.ts",
    "content": "import { testFogOfWar } from 'e2e-shared/specs/react-router/fog-of-war.spec.ts'\n\ntestFogOfWar({\n  path: '/fog-of-war'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/key-isolation.spec.ts",
    "content": "import { testKeyIsolation } from 'e2e-shared/specs/key-isolation.spec.ts'\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/loader.spec.ts",
    "content": "import { testLoader } from 'e2e-shared/specs/loader.spec.ts'\n\ntestLoader({ path: '/loader' })\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/push/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestPush({\n  path: '/push/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/render-count.spec.ts",
    "content": "import { testRenderCount } from 'e2e-shared/specs/render-count.spec.ts'\n\nconst hooks = ['useQueryState', 'useQueryStates'] as const\nconst shallows = [true, false] as const\nconst histories = ['replace', 'push'] as const\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/no-loader`,\n          description: 'no loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2\n          }\n        })\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        testRenderCount({\n          path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/sync-loader`,\n          description: 'sync loader',\n          hook,\n          props: {\n            shallow,\n            history,\n            startTransition\n          },\n          expected: {\n            mount: 1,\n            update: 2 + (shallow === false ? 1 : 0)\n          }\n        })\n      }\n    }\n  }\n}\n\nfor (const hook of hooks) {\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      for (const startTransition of [false, true]) {\n        for (const delay of shallow === false ? [0, 50] : [0]) {\n          testRenderCount({\n            path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/async-loader?delay=${delay}`,\n            description: 'async loader',\n            hook,\n            props: {\n              shallow,\n              history,\n              startTransition,\n              delay\n            },\n            expected: {\n              mount: 1,\n              update: 2 + (shallow === false ? 1 : 0)\n            }\n          })\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/repro-1099/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRepro1099({\n  path: '/repro-1099/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/repro-1293.spec.ts",
    "content": "import { testRepro1293 } from 'e2e-shared/specs/repro-1293.spec.ts'\n\ntestRepro1293({\n  path: '/repro-1293'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/repro-1365.spec.ts",
    "content": "import { testRepro1365 } from 'e2e-shared/specs/repro-1365.spec.ts'\n\ntestRepro1365({\n  path: '/repro-1365'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/repro-359.spec.ts",
    "content": "import { testRepro359 } from 'e2e-shared/specs/repro-359.spec.ts'\n\ntestRepro359({\n  path: '/repro-359'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/repro-982.spec.ts",
    "content": "import { testRepro982 } from 'e2e-shared/specs/repro-982.spec.ts'\n\ntestRepro982({\n  path: '/repro-982'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/shallow.spec.ts",
    "content": "import { testShallow } from 'e2e-shared/specs/shallow.spec.ts'\n\n// Note: Remix supports SSR, so no supportsSSR: false needed\ntestShallow({\n  path: '/shallow/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestShallow({\n  path: '/shallow/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared/stitching.spec.ts",
    "content": "import { testStitching } from 'e2e-shared/specs/stitching.spec.ts'\n\ntestStitching({\n  path: '/stitching'\n})\n"
  },
  {
    "path": "packages/e2e/remix/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests()\n"
  },
  {
    "path": "packages/e2e/remix/tsconfig.json",
    "content": "{\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \"**/.server/**/*.ts\",\n    \"**/.server/**/*.tsx\",\n    \"**/.client/**/*.ts\",\n    \"**/.client/**/*.tsx\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"@remix-run/node\", \"vite/client\"],\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react-jsx\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"target\": \"ES2022\",\n    \"strict\": true,\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n\n    // Vite takes care of building everything, not tsc.\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "packages/e2e/remix/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"build/**\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/remix/vite.config.ts",
    "content": "import { vitePlugin as remix } from '@remix-run/dev'\nimport { defineConfig } from 'vite'\nimport tsconfigPaths from 'vite-tsconfig-paths'\n\nexport default defineConfig({\n  plugins: [\n    remix({\n      future: {\n        v3_fetcherPersist: true,\n        v3_relativeSplatPath: true,\n        v3_throwAbortReason: true\n      }\n    }),\n    tsconfigPaths()\n  ],\n  build: {\n    sourcemap: false, // Disable sourcemaps for e2e test apps\n    rolldownOptions: {\n      onwarn(warning, warn) {\n        // Suppress sourcemap warnings from workspace dependencies\n        if (warning.code === 'SOURCEMAP_ERROR') return\n        warn(warning)\n      }\n    }\n  }\n})\n"
  },
  {
    "path": "packages/e2e/shared/components/display.tsx",
    "content": "import type { ReactNode } from 'react'\n\nexport type DisplayProps = {\n  environment: 'client' | 'server'\n  target?: string\n  state: ReactNode\n}\n\nexport function Display({\n  state,\n  environment,\n  target = 'state'\n}: DisplayProps) {\n  return <pre id={`${environment}-${target}`}>{state}</pre>\n}\n"
  },
  {
    "path": "packages/e2e/shared/components/hydration-marker.tsx",
    "content": "'use client'\n\nimport React from 'react'\n\nexport const HydrationMarker = () => {\n  const [hydrated, setHydrated] = React.useState(false)\n  React.useEffect(() => setHydrated(true), [])\n  if (!hydrated) {\n    return null\n  }\n  return (\n    <div id=\"hydration-marker\" style={{ display: 'none' }} aria-hidden>\n      hydrated\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/components/link.tsx",
    "content": "import { type ComponentProps, createContext, useContext } from 'react'\n\nexport type LinkProps = ComponentProps<'a'> & {\n  href: string\n  children: React.ReactNode\n  replace?: boolean\n}\n\ntype LinkContextValue = {\n  Link: React.ComponentType<LinkProps>\n}\n\nconst LinkContext = createContext<LinkContextValue>({\n  Link: () => {\n    throw new Error('Link component was not provided')\n  }\n})\n\nexport function useLink() {\n  return useContext(LinkContext).Link\n}\n\nexport function LinkProvider({\n  children,\n  Link\n}: {\n  children: React.ReactNode\n  Link: React.ComponentType<LinkProps>\n}) {\n  return (\n    <LinkContext.Provider value={{ Link }}>{children}</LinkContext.Provider>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/components/null-detector.tsx",
    "content": "import { type ReactNode, useEffect, useState } from 'react'\n\ntype NullDetectorProps = {\n  state: ReactNode\n  id?: string\n  throwOnNull?: boolean\n  enabled?: boolean\n}\n\nexport function NullDetector({\n  state,\n  id = 'null-detector',\n  throwOnNull = false,\n  enabled = true\n}: NullDetectorProps) {\n  const [hasBeenNullAtSomePoint, set] = useState(() =>\n    enabled ? state === null : false\n  )\n  useEffect(() => {\n    if (state !== null || !enabled) {\n      return\n    }\n    if (throwOnNull) {\n      throw new Error(`Null detected in <NullDetector id=\"${id}\">`)\n    }\n    console.error(`<NullDetector id=\"${id}\">: NULL DETECTED`)\n    set(true)\n  }, [enabled, state])\n  return <pre id={id}>{hasBeenNullAtSomePoint ? 'fail' : 'pass'}</pre>\n}\n"
  },
  {
    "path": "packages/e2e/shared/components/router.tsx",
    "content": "import { createContext, useContext } from 'react'\n\ntype RouterOptions = {\n  shallow?: boolean\n}\n\nexport type Router = {\n  replace(url: string, options: RouterOptions): void\n  push(url: string, options: RouterOptions): void\n}\n\ntype RouterContextValue = () => Router\n\nconst RouterContext = createContext<RouterContextValue>(\n  function useAbstractRouter() {\n    throw new Error('Router was not provided')\n  }\n)\n\nexport function useRouter() {\n  return useContext(RouterContext)()\n}\n\nexport function RouterProvider({\n  children,\n  useRouter\n}: {\n  children: React.ReactNode\n  useRouter: () => Router\n}) {\n  return (\n    <RouterContext.Provider value={useRouter}>\n      {children}\n    </RouterContext.Provider>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/define-test.ts",
    "content": "import { test } from '@playwright/test'\n\nexport type TestConfig = {\n  path: string\n  hook?: 'useQueryState' | 'useQueryStates'\n  router?:\n    | 'next-app'\n    | 'next-pages'\n    | 'react-spa'\n    | 'react-router-v6'\n    | 'react-router-v7'\n    | 'remix'\n    | 'tanstack-router'\n  description?: string\n}\n\nconst routerDisplay: Record<NonNullable<TestConfig['router']>, string> = {\n  'next-app': 'app router',\n  'next-pages': 'pages router',\n  'react-spa': 'React SPA',\n  'react-router-v6': 'React Router v6',\n  'react-router-v7': 'React Router v7',\n  remix: 'Remix',\n  'tanstack-router': 'TanStack Router'\n}\n\nexport function defineTest(\n  firstArg: string | { label: string; variants: string },\n  implementation: (config: TestConfig) => void\n) {\n  return (config: TestConfig) => {\n    const label = typeof firstArg === 'string' ? firstArg : firstArg.label\n    const variants = typeof firstArg === 'string' ? null : firstArg.variants\n    const describeLabel = [\n      label,\n      config.router && routerDisplay[config.router],\n      config.hook,\n      variants,\n      config.description\n    ]\n      .filter(Boolean)\n      .join(' - ')\n    test.describe(\n      describeLabel,\n      {\n        tag: [\n          config.hook && (`@${config.hook}` as const),\n          config.router && (`@${config.router}` as const)\n        ].filter(x => !!x)\n      },\n      implementation.bind(null, config)\n    )\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/lib/options.ts",
    "content": "import {\n  createLoader,\n  createSerializer,\n  parseAsBoolean,\n  parseAsStringLiteral,\n  useQueryStates\n} from 'nuqs'\n\nexport const optionsSearchParams = {\n  shallow: parseAsBoolean.withDefault(true),\n  history: parseAsStringLiteral(['replace', 'push']).withDefault('replace')\n}\nexport const useOptions = () => useQueryStates(optionsSearchParams)[0]\nexport const getOptionsUrl = createSerializer(optionsSearchParams)\nexport const loadOptions = createLoader(optionsSearchParams)\n"
  },
  {
    "path": "packages/e2e/shared/package.json",
    "content": "{\n  \"name\": \"e2e-shared\",\n  \"description\": \"Shared code (fixture components & spec files) for e2e tests\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"files\": [\n    \"shared.spec.ts\",\n    \"playwright.config.ts\",\n    \"playwright/expect-url.ts\",\n    \"playwright/log-spy.ts\",\n    \"playwright/navigate.ts\",\n    \"playwright/url-spy.ts\",\n    \"playwright/agent-reporter.ts\",\n    \"define-test.ts\"\n  ],\n  \"peerDependencies\": {\n    \"nuqs\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@vercel/detect-agent\": \"^1.2.1\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"typescript\": \"^5.9.3\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright/agent-reporter.ts",
    "content": "import type {\n  FullConfig,\n  FullResult,\n  Reporter,\n  Suite,\n  TestCase,\n  TestError,\n  TestResult\n} from '@playwright/test/reporter'\n\nfunction stripAnsi(text: string) {\n  return text.replace(/\\u001b\\[[0-9;]*m/g, '')\n}\n\nfunction formatDuration(durationMs: number) {\n  if (durationMs < 1000) return `${Math.round(durationMs)}ms`\n  if (durationMs < 60_000) {\n    return `${(durationMs / 1000).toFixed(1).replace(/\\.0$/, '')}s`\n  }\n  if (durationMs < 3_600_000) {\n    return `${(durationMs / 60_000).toFixed(1).replace(/\\.0$/, '')}m`\n  }\n  return `${(durationMs / 3_600_000).toFixed(1).replace(/\\.0$/, '')}h`\n}\n\nexport default class AgentReporter implements Reporter {\n  private totalTests = 0\n  private passedTests = 0\n  private failedTests = 0\n  private skippedTests = 0\n\n  onBegin(_config: FullConfig, suite: Suite) {\n    this.totalTests = suite.allTests().length\n  }\n\n  onTestEnd(test: TestCase, result: TestResult) {\n    const isFailure = result.status === 'failed' || result.status === 'timedOut'\n    const isFinalAttempt = !isFailure || result.retry >= test.retries\n\n    if (!isFinalAttempt) return\n\n    if (isFailure) {\n      this.failedTests++\n      this.printFailure(test, result)\n    } else if (result.status === 'skipped') {\n      this.skippedTests++\n    } else {\n      this.passedTests++\n    }\n  }\n\n  onError(error: TestError) {\n    const message = stripAnsi(error.message ?? error.value ?? 'Unknown error')\n    const lines = [`GLOBAL ERROR: ${message}`]\n    if (error.stack) {\n      lines.push('  Stack:')\n      for (const line of stripAnsi(error.stack).split('\\n')) {\n        lines.push(`    ${line}`)\n      }\n    }\n    lines.push('')\n    process.stdout.write(lines.join('\\n') + '\\n')\n  }\n\n  onEnd(result: FullResult) {\n    const parts: string[] = []\n    if (this.passedTests > 0) {\n      parts.push(`${this.passedTests} passed`)\n    }\n    if (this.failedTests > 0) {\n      parts.push(`${this.failedTests} failed`)\n    }\n    if (this.skippedTests > 0) {\n      parts.push(`${this.skippedTests} skipped`)\n    }\n    const summary = `Result: ${parts.join(', ')} (total ${this.totalTests} in ${formatDuration(result.duration)})`\n    process.stdout.write(summary + '\\n')\n  }\n\n  private printFailure(test: TestCase, result: TestResult) {\n    const titlePath = test\n      .titlePath()\n      .filter(Boolean)\n      .filter(\n        part =>\n          part !== 'chromium' &&\n          !part.includes('.spec.') &&\n          !part.includes('.test.')\n      )\n      .join(' > ')\n\n    const lines = [`FAILED: ${titlePath || test.title}`]\n\n    if (test.location) {\n      lines.push(\n        `  Location: ${test.location.file}:${test.location.line}:${test.location.column}`\n      )\n    }\n    if (result.status === 'timedOut') {\n      lines.push(`  Status: timedOut`)\n    }\n    if (test.retries > 0) {\n      lines.push(`  Attempt: ${result.retry + 1}/${test.retries + 1}`)\n    }\n\n    const errors = result.errors\n    if (errors.length === 1) {\n      this.pushError(lines, errors[0])\n    } else {\n      for (let i = 0; i < errors.length; i++) {\n        lines.push(`  Error ${i + 1}/${errors.length}:`)\n        this.pushError(lines, errors[i])\n      }\n    }\n\n    lines.push('')\n    process.stdout.write(lines.join('\\n') + '\\n')\n  }\n\n  private pushError(lines: string[], error: TestError) {\n    const message = stripAnsi(\n      error.message ?? error.value ?? 'Unknown error'\n    )\n    for (const [i, line] of message.split('\\n').entries()) {\n      lines.push(i === 0 ? `  Error: ${line}` : `    ${line}`)\n    }\n    if (error.stack) {\n      lines.push('  Stack:')\n      for (const line of stripAnsi(error.stack).split('\\n')) {\n        lines.push(`    ${line}`)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright/expect-url.ts",
    "content": "import { type Page, expect } from '@playwright/test'\n\ntype ExpectUrlOptions = {\n  message?: string\n  timeout?: number\n  intervals?: number[]\n}\n\nexport function expectUrl(\n  page: Page,\n  predicate: (url: URL) => boolean,\n  options: ExpectUrlOptions = {}\n) {\n  return expect\n    .poll(() => predicate(new URL(page.url())), {\n      intervals:\n        options.intervals ??\n        Array.from({ length: (options.timeout ?? 5000) / 50 }, _ => 50),\n      timeout: options.timeout ?? 5000,\n      message: options.message\n    })\n    .toBe(true)\n}\n\nexport function expectSearch(page: Page, expected: Record<string, string>) {\n  return expectUrl(\n    page,\n    url =>\n      Object.entries(expected).every(\n        ([key, value]) => url.searchParams.get(key) === value\n      ),\n    { message: `URL search params to match ${JSON.stringify(expected)}` }\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright/log-spy.ts",
    "content": "import type { ConsoleMessage, Page } from '@playwright/test'\nimport { expect } from '@playwright/test'\n\nexport type LogSpy = {\n  logs: string[]\n  [Symbol.dispose]: () => void\n}\n\nexport function setupLogSpy(page: Page): LogSpy {\n  const logs: string[] = []\n  const handler = (msg: ConsoleMessage) => {\n    if (msg.type() === 'log') {\n      logs.push(msg.text())\n    }\n  }\n  page.on('console', handler)\n  return {\n    logs,\n    [Symbol.dispose]() {\n      page.off('console', handler)\n    }\n  }\n}\n\nexport function assertLogCount(\n  logSpy: LogSpy,\n  message: string,\n  expectedCount: number,\n  assertionMessage?: string\n) {\n  return expect\n    .poll(\n      () => logSpy.logs.filter(log => log === message).length,\n      assertionMessage\n    )\n    .toBe(expectedCount)\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright/navigate.ts",
    "content": "import type { Page } from '@playwright/test'\n\nexport async function navigateTo(page: Page, pathname: string, search = '') {\n  // Needs relative URLs for basePath support\n  const relativePathname = pathname.startsWith('.') ? pathname : `.${pathname}`\n  const relativeUrl = `${relativePathname}${search}`\n  const response = await page.goto(relativeUrl)\n  if (!response?.ok()) {\n    throw new Error(\n      `Failed to navigate to ${relativeUrl}: ${\n        response ? response.status() : 'no response'\n      }`\n    )\n  }\n  await page.waitForLoadState('networkidle')\n  await page.locator('#hydration-marker').waitFor({ state: 'hidden' })\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright/reporter.ts",
    "content": "import type {\n  FullConfig,\n  FullResult,\n  Reporter,\n  Suite,\n  TestCase,\n  TestError,\n  TestResult\n} from '@playwright/test/reporter'\nimport { styleText } from 'node:util'\n\nconst validateStream = false // always emit color codes, even when stdout is not a TTY or reports no color support (for consistent colored output in CI and captured logs)\nconst dimStyle = process.env.CI ? 'gray' : 'dim'\nconst dimYellowStyle: Parameters<typeof styleText>[0] =\n  dimStyle === 'dim' ? ['dim', 'yellow'] : ['yellow']\nconst ellipsis = '…'\n\nconst statusSymbols: Record<TestResult['status'], string> = {\n  passed: styleText('green', '✓', { validateStream }),\n  failed: styleText('red', '✗', { validateStream }),\n  skipped: styleText(dimYellowStyle, '~', { validateStream }),\n  timedOut: styleText('red', '!', { validateStream }),\n  interrupted: styleText('cyan', '?', { validateStream })\n}\n\nfunction dim(text: string) {\n  return styleText(dimStyle, text, { validateStream })\n}\n\nfunction dimYellow(text: string) {\n  return styleText(dimYellowStyle, text, { validateStream })\n}\n\nfunction stripAnsi(text: string) {\n  return text.replace(/\\u001b\\[[0-9;]*m/g, '')\n}\n\nfunction visibleWidth(text: string) {\n  return stripAnsi(text).length\n}\n\nfunction fitTitleToScreen(title: string, prefix: string, suffix: string) {\n  const width = process.stdout.columns\n  if (!width) return title\n  const available = width - visibleWidth(prefix) - visibleWidth(suffix)\n  if (available <= 0) return ''\n  if (title.length <= available) return title\n  if (available <= ellipsis.length) {\n    return title.slice(title.length - available)\n  }\n  const keep = available - ellipsis.length\n  return ellipsis + title.slice(title.length - keep)\n}\n\nfunction formatDuration(durationMs: number) {\n  if (durationMs < 1000) return `${Math.round(durationMs)}ms`\n  if (durationMs < 60_000) {\n    return `${(durationMs / 1000).toFixed(1).replace(/\\.0$/, '')}s`\n  }\n  if (durationMs < 3_600_000) {\n    return `${(durationMs / 60_000).toFixed(1).replace(/\\.0$/, '')}m`\n  }\n  return `${(durationMs / 3_600_000).toFixed(1).replace(/\\.0$/, '')}h`\n}\n\nexport default class CompactListReporter implements Reporter {\n  private resultIndex = new Map<TestCase, string>()\n  private testRows = new Map<TestCase, number>()\n  private lastRow = 0\n  private padding = 1\n  private totalTests = 0\n  private failedTests = 0\n  private outputLocked = false\n  private outputQueue: Array<() => void> = []\n  private nextIndex = 1\n\n  onBegin(config: FullConfig, suite: Suite) {\n    const jobs = config.metadata.actualWorkers ?? config.workers\n    const shardDetails = config.shard\n      ? `, shard ${config.shard.current} of ${config.shard.total}`\n      : ''\n    this.totalTests = suite.allTests().length\n    this.padding = String(this.totalTests).length\n    const log =\n      dim('Running ') +\n      this.totalTests +\n      dim(` test${this.totalTests !== 1 ? 's' : ''} using `) +\n      jobs +\n      dim(` worker${jobs !== 1 ? 's' : ''}${shardDetails}`)\n    this.writeLine('')\n    this.writeLine(log)\n  }\n\n  onTestBegin(test: TestCase, _result: TestResult) {\n    let index = this.resultIndex.get(test)\n    if (!index) {\n      index = String(this.nextIndex++).padStart(this.padding)\n      this.resultIndex.set(test, index)\n    }\n    if (!process.stdout.isTTY) return\n    const titlePath = this.formatTitlePath(test)\n    const prefix = `. ${index} `\n    const suffix = ''\n    const fittedTitle = fitTitleToScreen(titlePath, prefix, suffix)\n    const line = dim(`${prefix}${fittedTitle}${suffix}`)\n    this.updateOrAppendLine(test, line)\n  }\n\n  onTestEnd(test: TestCase, result: TestResult) {\n    const isFailureStatus = ['failed', 'timedOut', 'interrupted'].includes(\n      result.status\n    )\n    const isFinalAttempt = !isFailureStatus || result.retry >= test.retries\n    if (isFailureStatus && isFinalAttempt) {\n      this.failedTests++\n    }\n    const index = this.resultIndex.get(test) ?? ''\n    const outcomeSymbol = statusSymbols[result.status]\n    const indexStr = dim(index)\n    const titlePath = this.formatTitlePath(test)\n    const prefix = `${outcomeSymbol} ${indexStr} `\n    const suffix = this.formatResultSuffix(test, result, isFinalAttempt)\n    const fittedTitle = fitTitleToScreen(titlePath, prefix, suffix)\n    const title = styleText(\n      result.status !== 'passed' ? 'red' : [],\n      fittedTitle,\n      { validateStream }\n    )\n    const log = `${prefix}${title}${suffix}`\n    if (isFailureStatus && isFinalAttempt) {\n      const blockLines: string[] = [log]\n      if (result.errors.length > 0) {\n        for (const failure of result.errors) {\n          blockLines.push(dim('  ├── Cause:'))\n          this.pushLogLines(blockLines, failure.stack)\n          this.pushLogLines(blockLines, failure.snippet)\n        }\n        blockLines.push(dim('  └──'))\n      }\n      this.withOutputLock(() => {\n        if (process.stdout.isTTY) {\n          const row = this.testRows.get(test)\n          if (row !== undefined) {\n            this.updateLine(row, '')\n            this.testRows.delete(test)\n          }\n        }\n        this.writeBlock(blockLines)\n      })\n    } else {\n      this.updateOrAppendLine(test, log)\n    }\n    if (isFinalAttempt) {\n      this.resultIndex.delete(test)\n      this.testRows.delete(test)\n    }\n  }\n\n  onError(error: TestError) {\n    console.dir(\n      {\n        '// todo': 'Implement formatting of onError',\n        ...error\n      },\n      { depth: null }\n    )\n  }\n\n  onEnd(result: FullResult) {\n    if (result.status === 'passed') {\n      const log = [\n        styleText(['bgGreen'], ' PASS ', {\n          validateStream\n        }),\n        'All',\n        styleText('green', `${this.totalTests} tests passed`, {\n          validateStream\n        }),\n        dim(`in ${formatDuration(result.duration)}`)\n      ].join(' ')\n      return console.log(log)\n    }\n    if (result.status === 'failed') {\n      const log = [\n        styleText(['bgRed'], ' FAIL ', {\n          validateStream\n        }),\n        styleText(\n          'red',\n          `${this.failedTests} of ${this.totalTests} tests failed`,\n          { validateStream }\n        ),\n        dim(`in ${formatDuration(result.duration)}`)\n      ].join(' ')\n      return console.log(log)\n    }\n\n    // Fallback summary for other statuses like 'timedout' or 'interrupted'\n    const log = [\n      styleText(['bgYellow'], ` ${result.status.toUpperCase()} `, {\n        validateStream\n      }),\n      styleText(\n        'yellow',\n        `${this.failedTests} of ${this.totalTests} tests failed`,\n        { validateStream }\n      ),\n      dim(`in ${formatDuration(result.duration)}`)\n    ].join(' ')\n    return console.log(log)\n  }\n\n  private formatTitlePath(test: TestCase) {\n    const titlePath = test\n      .titlePath()\n      .filter(part => {\n        if (!part) return false\n        if (part === 'chromium') return false\n        return (\n          part.includes('.spec.') === false && part.includes('.test.') === false\n        )\n      })\n      .join(dim(' › '))\n    return titlePath || test.title\n  }\n\n  private updateOrAppendLine(test: TestCase, line: string) {\n    if (!process.stdout.isTTY) return this.writeLine(line)\n    if (this.outputLocked) {\n      this.outputQueue.push(() => this.updateOrAppendLine(test, line))\n      return\n    }\n    const row = this.testRows.get(test)\n    const height = process.stdout.rows ?? 0\n    if (row !== undefined && (height === 0 || this.lastRow - row < height)) {\n      this.updateLine(row, line)\n      return\n    }\n    this.testRows.set(test, this.lastRow)\n    this.appendLine(line)\n  }\n\n  private appendLine(line: string) {\n    this.writeLine(line)\n  }\n\n  private writeLine(line: string) {\n    if (this.outputLocked) {\n      this.outputQueue.push(() => this.writeLineUnlocked(line))\n      return\n    }\n    this.writeLineUnlocked(line)\n  }\n\n  private writeLineUnlocked(line: string) {\n    process.stdout.write(line)\n    process.stdout.write('\\n')\n    this.lastRow++\n  }\n\n  private writeBlock(lines: string[]) {\n    if (lines.length === 0) return\n    process.stdout.write(`${lines.join('\\n')}\\n`)\n    this.lastRow += lines.length\n  }\n\n  private pushLogLines(lines: string[], message: string | undefined) {\n    if (!message) return\n    for (const line of message.split('\\n')) {\n      lines.push(dim('  │ ') + line)\n    }\n  }\n\n  private formatResultSuffix(\n    test: TestCase,\n    result: TestResult,\n    isFinalAttempt: boolean\n  ) {\n    const parts: string[] = []\n    const stepsCount = result.steps.length\n    parts.push(dim(`${stepsCount} step${stepsCount === 1 ? '' : 's'}`))\n    const retryLabel = this.formatRetrySuffix(test, result)\n    if (retryLabel) parts.push(retryLabel)\n    if (isFinalAttempt) {\n      // Only show duration on final attempt to reduce retry clutter.\n      parts.push(dim(formatDuration(result.duration)))\n    }\n    return ` ${parts.join(dim(' • '))}`\n  }\n\n  private formatRetrySuffix(test: TestCase, result: TestResult) {\n    const totalAttempts = test.retries + 1\n    if (totalAttempts <= 1) return ''\n    if (result.retry === 0 && result.status === 'passed') return ''\n    return dimYellow(`attempt ${result.retry + 1}/${totalAttempts}`)\n  }\n\n  private withOutputLock(action: () => void) {\n    this.outputLocked = true\n    try {\n      action()\n    } finally {\n      this.outputLocked = false\n      const queued = this.outputQueue\n      this.outputQueue = []\n      for (const flush of queued) {\n        flush()\n      }\n    }\n  }\n\n  private updateLine(row: number, line: string) {\n    let output = ''\n    if (row !== this.lastRow) {\n      output += `\\u001B[${this.lastRow - row}A`\n    }\n    output += `\\u001B[2K\\u001B[0G${line}`\n    if (row !== this.lastRow) {\n      output += `\\u001B[${this.lastRow - row}E`\n    }\n    process.stdout.write(output)\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright/url-spy.ts",
    "content": "import { type Frame, type Page, expect } from '@playwright/test'\n\nexport type UrlSpy = {\n  reset(): void\n  assertSearches(expected: Array<Record<string, string>>): Promise<void>\n  [Symbol.dispose]: () => void\n}\n\nexport function setupUrlSpy(page: Page): UrlSpy {\n  const urls: string[] = []\n  const handler = (frame: Frame) => {\n    if (frame === page.mainFrame()) {\n      // ignore if the url is identical to the last record\n      if (urls.length > 0 && urls[urls.length - 1] === frame.url()) {\n        return\n      }\n      console.log('Navigated to', frame.url())\n      urls.push(frame.url())\n    }\n  }\n  page.on('framenavigated', handler)\n\n  async function assertSearches(expected: Array<Record<string, string>>) {\n    return expect\n      .poll(\n        () => {\n          if (urls.length !== expected.length) {\n            console.debug(\n              `Expected ${expected.length} navigations, but got ${urls.length}:\\n  ${urls.join('\\n  ')}`\n            )\n            return false\n          }\n          return expected.every((expectedSearch, index) => {\n            const url = new URL(urls[index])\n            return Object.entries(expectedSearch).every(([key, value]) => {\n              const expected = value\n              const received = url.searchParams.get(key)\n              if (url.searchParams.get(key) !== value) {\n                console.debug(\n                  `Expected navigation ${index} (${url}) to have search param ${key}=${expected}, but got ${received} instead`\n                )\n                return false\n              }\n              return true\n            })\n          })\n        },\n        { intervals: Array.from({ length: 40 }, _ => 50), timeout: 2000 }\n      )\n      .toBe(true)\n  }\n\n  return {\n    reset() {\n      urls.length = 0\n    },\n    assertSearches,\n    [Symbol.dispose]() {\n      page.off('framenavigated', handler)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/playwright.config.ts",
    "content": "import { determineAgent } from '@vercel/detect-agent'\nimport { defineConfig, devices } from '@playwright/test'\nimport { resolve } from 'node:path'\n\nconst { isAgent } = await determineAgent()\n\ntype ConfigurePlaywright = {\n  startCommand: string\n  port: number\n  basePath?: string\n}\n\nexport function configurePlaywright({\n  startCommand,\n  port,\n  basePath = '/'\n}: ConfigurePlaywright) {\n  const customReporter = resolve(\n    import.meta.dirname,\n    'playwright',\n    isAgent ? 'agent-reporter.ts' : 'reporter.ts'\n  )\n  return defineConfig({\n    testDir: './specs',\n    outputDir: '.playwright/test-results',\n    fullyParallel: true,\n    retries: process.env.CI ? 2 : 0,\n    workers: process.env.CI ? 3 : undefined,\n    timeout: 5_000,\n    reporter: isAgent\n      ? [[customReporter]]\n      : [\n          [customReporter],\n          ['html', { open: 'never', outputFolder: '.playwright/report' }]\n        ],\n    use: {\n      baseURL: ensureTrailingSlash(`http://localhost:${port}${basePath}`),\n      trace: 'on-first-retry',\n      screenshot: 'only-on-failure'\n    },\n    projects: [\n      {\n        name: 'chromium',\n        use: { ...devices['Desktop Chrome'] }\n      }\n    ],\n    webServer: {\n      command: startCommand,\n      url: `http://localhost:${port}${basePath}`,\n      reuseExistingServer: !process.env.CI,\n      env: {\n        PORT: String(port)\n      }\n    }\n  })\n}\n\nfunction ensureTrailingSlash(path: string) {\n  return path.endsWith('/') ? path : path + '/'\n}\n"
  },
  {
    "path": "packages/e2e/shared/shared.spec.ts",
    "content": "import type { TestConfig } from './define-test'\nimport { testBasicIO } from './specs/basic-io.spec'\nimport { testConditionalRendering } from './specs/conditional-rendering.spec'\nimport { testForm } from './specs/form.spec'\nimport { testHashPreservation } from './specs/hash-preservation.spec'\nimport { testJson } from './specs/json.spec'\nimport { testNativeArray } from './specs/native-array.spec'\nimport { testLifeAndDeath } from './specs/life-and-death.spec'\nimport { testLinking } from './specs/linking.spec'\nimport { testPrettyUrls } from './specs/pretty-urls.spec'\nimport { testReferentialStability } from './specs/referential-stability.spec'\nimport { testRouting } from './specs/routing.spec'\nimport { testHistorySync } from './specs/history-sync.spec'\nimport { testScroll } from './specs/scroll.spec'\n\nexport function runSharedTests(\n  pathPrefix: string = '',\n  config: Partial<Omit<TestConfig, 'path' | 'hook'>> = {}\n) {\n  testBasicIO({\n    path: `${pathPrefix}/basic-io/useQueryState`,\n    hook: 'useQueryState',\n    ...config\n  })\n  testBasicIO({\n    path: `${pathPrefix}/basic-io/useQueryStates`,\n    hook: 'useQueryStates',\n    ...config\n  })\n\n  // --\n\n  testJson({\n    path: `${pathPrefix}/json`,\n    ...config\n  })\n\n  // --\n\n  testNativeArray({\n    path: `${pathPrefix}/native-array`,\n    ...config\n  })\n\n  // --\n\n  testConditionalRendering({\n    path: `${pathPrefix}/conditional-rendering/useQueryState`,\n    hook: 'useQueryState',\n    ...config\n  })\n  testConditionalRendering({\n    path: `${pathPrefix}/conditional-rendering/useQueryStates`,\n    hook: 'useQueryStates',\n    ...config\n  })\n\n  // --\n\n  testForm({\n    path: `${pathPrefix}/form/useQueryState`,\n    hook: 'useQueryState',\n    ...config\n  })\n  testForm({\n    path: `${pathPrefix}/form/useQueryStates`,\n    hook: 'useQueryStates',\n    ...config\n  })\n\n  // --\n\n  testHashPreservation({\n    path: `${pathPrefix}/hash-preservation`,\n    ...config\n  })\n\n  // --\n\n  testLifeAndDeath({\n    path: `${pathPrefix}/life-and-death`,\n    ...config\n  })\n\n  // --\n\n  testLinking({\n    path: `${pathPrefix}/linking/useQueryState`,\n    hook: 'useQueryState',\n    ...config\n  })\n  testLinking({\n    path: `${pathPrefix}/linking/useQueryStates`,\n    hook: 'useQueryStates',\n    ...config\n  })\n\n  // --\n\n  testPrettyUrls({\n    path: `${pathPrefix}/pretty-urls`,\n    ...config\n  })\n\n  // --\n\n  testReferentialStability({\n    path: `${pathPrefix}/referential-stability/useQueryState`,\n    hook: 'useQueryState',\n    ...config\n  })\n  testReferentialStability({\n    path: `${pathPrefix}/referential-stability/useQueryStates`,\n    hook: 'useQueryStates',\n    ...config\n  })\n\n  // --\n\n  testRouting({\n    path: `${pathPrefix}/routing/useQueryState`,\n    hook: 'useQueryState',\n    ...config\n  })\n  testRouting({\n    path: `${pathPrefix}/routing/useQueryStates`,\n    hook: 'useQueryStates',\n    ...config\n  })\n\n  // --\n\n  testHistorySync({\n    path: `${pathPrefix}/history-sync`,\n    ...config\n  })\n\n  // --\n\n  testScroll({\n    path: `${pathPrefix}/scroll`,\n    ...config\n  })\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/basic-io.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testBasicIO = defineTest('Basic I/O', ({ path }) => {\n  it('reads the value from the URL on mount', async ({ page }) => {\n    await navigateTo(page, path, '?test=init')\n    await expect(page.locator('#state')).toHaveText('init')\n    await expect(page.locator('#null-detector')).toHaveText('pass')\n  })\n\n  it('writes the value to the URL', async ({ page }) => {\n    await navigateTo(page, path)\n    await expect(page.locator('#state')).toBeEmpty()\n    await page.locator('button#set-pass').click()\n    await expect(page.locator('#state')).toHaveText('pass')\n    await expect(page).toHaveURL(url => url.search === '?test=pass')\n  })\n\n  it('updates the value in the URL', async ({ page }) => {\n    await navigateTo(page, path, '?test=init')\n    await expect(page.locator('#state')).toHaveText('init')\n    await page.locator('button#set-pass').click()\n    await expect(page.locator('#state')).toHaveText('pass')\n    await expect(page).toHaveURL(url => url.search === '?test=pass')\n    await expect(page.locator('#null-detector')).toHaveText('pass')\n  })\n\n  it('removes the value from the URL', async ({ page }) => {\n    await navigateTo(page, path, '?test=init')\n    await page.locator('button#clear').click()\n    await expect(page.locator('#state')).toBeEmpty()\n    await expect(page).toHaveURL(url => url.search === '')\n  })\n\n  it('removes a set value from the URL', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('button#set-pass').click()\n    await page.locator('button#clear').click()\n    await expect(page.locator('#state')).toBeEmpty()\n    await expect(page).toHaveURL(url => url.search === '')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/basic-io.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { NullDetector } from '../components/null-detector'\n\nexport function UseQueryStateBasicIO() {\n  const [state, setState] = useQueryState('test')\n  return (\n    <>\n      <button id=\"set-pass\" onClick={() => setState('pass')}>\n        Test\n      </button>\n      <button id=\"clear\" onClick={() => setState(null)}>\n        Clear\n      </button>\n      <pre id=\"state\">{state}</pre>\n      <NullDetector state={state} />\n    </>\n  )\n}\n\nexport function UseQueryStatesBasicIO() {\n  const [{ test }, setSearchParams] = useQueryStates({\n    test: parseAsString\n  })\n  return (\n    <>\n      <button id=\"set-pass\" onClick={() => setSearchParams({ test: 'pass' })}>\n        Test\n      </button>\n      <button id=\"clear\" onClick={() => setSearchParams(null)}>\n        Clear\n      </button>\n      <pre id=\"state\">{test}</pre>\n      <NullDetector state={test} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/conditional-rendering.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testConditionalRendering = defineTest(\n  'Conditional rendering',\n  ({ path }) => {\n    it('should have the correct initial state after mounting', async ({\n      page\n    }) => {\n      await navigateTo(page, path, '?test=pass')\n      await page.locator('button#mount').click()\n      await expect(page.locator('#state')).toHaveText('pass')\n      await expect(page.locator('#null-detector')).toHaveText('pass')\n    })\n\n    it('should keep the correct state after unmounting and remounting', async ({\n      page\n    }) => {\n      await navigateTo(page, path)\n      await page.locator('button#mount').click()\n      await page.locator('button#set').click()\n      await page.locator('button#unmount').click()\n      await page.locator('button#mount').click()\n      await expect(page.locator('#state')).toHaveText('pass')\n      await expect(page.locator('#null-detector')).toHaveText('pass')\n    })\n\n    it('should keep the correct state after unmounting and remounting with a different state', async ({\n      page\n    }) => {\n      await navigateTo(page, path, '?test=init')\n      await page.locator('button#mount').click()\n      await page.locator('button#set').click()\n      await page.locator('button#unmount').click()\n      await page.locator('button#mount').click()\n      await expect(page.locator('#state')).toHaveText('pass')\n      await expect(page.locator('#null-detector')).toHaveText('pass')\n    })\n  }\n)\n"
  },
  {
    "path": "packages/e2e/shared/specs/conditional-rendering.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { type FC, useState } from 'react'\nimport { NullDetector } from '../components/null-detector'\n\nfunction ConditionalRenderer({ Component }: { Component: FC }) {\n  const [mounted, setMounted] = useState(false)\n  return (\n    <>\n      <button id=\"mount\" onClick={() => setMounted(true)}>\n        Mount\n      </button>\n      <button id=\"unmount\" onClick={() => setMounted(false)}>\n        Unount\n      </button>\n      {mounted && <Component />}\n    </>\n  )\n}\n\n// --\n\nfunction TestComponentUseQueryState() {\n  const [state, setState] = useQueryState('test')\n  return (\n    <>\n      <button id=\"set\" onClick={() => setState('pass')}>\n        Set\n      </button>\n      <pre id=\"state\">{state}</pre>\n      <NullDetector state={state} />\n    </>\n  )\n}\n\nfunction TestComponentUseQueryStates() {\n  const [{ state }, setState] = useQueryStates(\n    {\n      state: parseAsString\n    },\n    { urlKeys: { state: 'test' } }\n  )\n  return (\n    <>\n      <button id=\"set\" onClick={() => setState({ state: 'pass' })}>\n        Set\n      </button>\n      <pre id=\"state\">{state}</pre>\n      <NullDetector state={state} />\n    </>\n  )\n}\n\n// --\n\nexport function ConditionalRenderingUseQueryState() {\n  return <ConditionalRenderer Component={TestComponentUseQueryState} />\n}\n\nexport function ConditionalRenderingUseQueryStates() {\n  return <ConditionalRenderer Component={TestComponentUseQueryStates} />\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/debounce-client.tsx",
    "content": "'use client'\n\nimport { debounce, useQueryStates } from 'nuqs'\nimport { Display } from '../components/display'\nimport { useLink } from '../components/link'\nimport { useRouter } from '../components/router'\nimport {\n  demoControls,\n  demoSearchParams,\n  demoSearchParamsUrlKeys\n} from './debounce.defs'\n\ntype DebounceProps = {\n  navigateHref: string\n}\n\nexport function DebounceClient({ navigateHref }: DebounceProps) {\n  const [{ debounceTime: timeMs }, setControls] = useQueryStates(demoControls)\n  const [{ search, pageIndex }, setSearchParams] = useQueryStates(\n    demoSearchParams,\n    {\n      shallow: false,\n      urlKeys: demoSearchParamsUrlKeys\n    }\n  )\n  const Link = useLink()\n  const router = useRouter()\n  return (\n    <>\n      <Display\n        environment=\"client\"\n        state={JSON.stringify({ search, pageIndex })}\n      />\n      <input\n        type=\"text\"\n        value={search}\n        onChange={e =>\n          setSearchParams(\n            { search: e.target.value },\n            {\n              // Instant update when clearing the input, otherwise debounce\n              limitUrlUpdates:\n                e.target.value === '' ? undefined : debounce(timeMs)\n            }\n          )\n        }\n        onKeyDown={e => {\n          if (e.key === 'Enter') {\n            // Send the search immediately when pressing Enter\n            setSearchParams({ search: e.currentTarget.value })\n          }\n        }}\n      />\n      <button\n        id=\"increment-page-index\"\n        onClick={() =>\n          setSearchParams(old => ({ pageIndex: old.pageIndex + 1 }))\n        }\n      >\n        Page {pageIndex}\n      </button>\n      <button id=\"reset\" onClick={() => setSearchParams(null)}>\n        Reset\n      </button>\n      <button\n        id=\"navigate-router-shallow-false\"\n        onClick={() => router.push(navigateHref, { shallow: false })}\n      >\n        Navigate with router\n      </button>\n      <Link href={navigateHref}>Navigate with Link</Link>\n      <h2>Controls</h2>\n      <div style={{ marginTop: '1rem' }}>\n        <label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>\n          <input\n            type=\"range\"\n            value={timeMs}\n            onChange={e =>\n              setControls({ debounceTime: e.target.valueAsNumber })\n            }\n            step={100}\n            min={100}\n            max={2000}\n          />\n          Search debounce time: {timeMs}ms\n        </label>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/debounce-server.tsx",
    "content": "import { Display } from '../components/display'\nimport type { DemoSearchParams } from './debounce.defs'\n\ntype DebounceServerDisplayProps = {\n  state: DemoSearchParams\n  children: React.ReactNode\n}\n\nexport function DebounceServer({\n  state,\n  children\n}: DebounceServerDisplayProps) {\n  return (\n    <>\n      <h2>Server</h2>\n      <Display environment=\"server\" state={JSON.stringify(state)} />\n      <h2>Client</h2>\n      {children}\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/debounce.defs.ts",
    "content": "import {\n  createLoader,\n  createSerializer,\n  parseAsInteger,\n  parseAsString,\n  type inferParserType,\n  type UrlKeys\n} from 'nuqs/server'\n\nexport const demoControls = {\n  debounceTime: parseAsInteger.withDefault(1000)\n}\n\nexport const getUrl = createSerializer(demoControls, {\n  clearOnDefault: false\n})\n\nexport const demoSearchParams = {\n  search: parseAsString.withDefault(''),\n  pageIndex: parseAsInteger.withDefault(0)\n}\nexport const demoSearchParamsUrlKeys: UrlKeys<typeof demoSearchParams> = {\n  search: 'q',\n  pageIndex: 'page'\n}\n\nexport type DemoSearchParams = inferParserType<typeof demoSearchParams>\n\nexport const loadDemoSearchParams = createLoader(demoSearchParams, {\n  urlKeys: demoSearchParamsUrlKeys\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/debounce.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\nimport { getUrl } from './debounce.defs'\n\ntype TestDebounceConfig = TestConfig & {\n  otherPath?: string\n}\n\nexport function testDebounce(config: TestDebounceConfig) {\n  const test = defineTest('Debounce', ({ path }) => {\n    it('should debounce the input', async ({ page }) => {\n      const DEBOUNCE_TIME = 200\n      await navigateTo(page, getUrl(path, { debounceTime: DEBOUNCE_TIME }))\n      await page.locator('input[type=\"text\"]').pressSequentially('pass')\n      await expect(page.locator('#client-state')).toHaveText(\n        '{\"search\":\"pass\",\"pageIndex\":0}'\n      )\n      await expect(page.locator('#server-state')).toHaveText(\n        '{\"search\":\"\",\"pageIndex\":0}'\n      )\n      await expect(page).toHaveURL(\n        url => url.search === `?debounceTime=${DEBOUNCE_TIME}`\n      )\n      await page.waitForTimeout(DEBOUNCE_TIME)\n      await expect(page).toHaveURL(\n        url => url.search === `?debounceTime=${DEBOUNCE_TIME}&q=pass`\n      )\n      await expect(page.locator('#server-state')).toHaveText(\n        '{\"search\":\"pass\",\"pageIndex\":0}'\n      )\n      await expect(page.locator('#client-state')).toHaveText(\n        '{\"search\":\"pass\",\"pageIndex\":0}'\n      )\n    })\n\n    it('should debounce the input while allowing the page index to increment', async ({\n      page\n    }) => {\n      const DEBOUNCE_TIME = 400\n      await navigateTo(page, getUrl(path, { debounceTime: DEBOUNCE_TIME }))\n      await page.locator('input[type=\"text\"]').pressSequentially('pass')\n      const incrementButton = page.locator('button#increment-page-index')\n      await incrementButton.click()\n      await incrementButton.click()\n      await incrementButton.click()\n      await expect(page.locator('#client-state')).toHaveText(\n        '{\"search\":\"pass\",\"pageIndex\":3}'\n      )\n      await expect(page).toHaveURL(\n        url => url.search === `?debounceTime=${DEBOUNCE_TIME}&page=3`\n      )\n      await expect(page.locator('#server-state')).toHaveText(\n        '{\"search\":\"\",\"pageIndex\":3}'\n      )\n      await page.waitForTimeout(DEBOUNCE_TIME)\n      await expect(page).toHaveURL(\n        url => url.search === `?debounceTime=${DEBOUNCE_TIME}&page=3&q=pass`\n      )\n      await expect(page.locator('#server-state')).toHaveText(\n        '{\"search\":\"pass\",\"pageIndex\":3}'\n      )\n      await expect(page.locator('#client-state')).toHaveText(\n        '{\"search\":\"pass\",\"pageIndex\":3}'\n      )\n    })\n\n    it('should cancel a debounce when the back button is clicked', async ({\n      page\n    }) => {\n      await navigateTo(page, config.otherPath ?? path + '/other')\n      await navigateTo(page, getUrl(path, { debounceTime: 200 }))\n      await page.locator('input[type=\"text\"]').pressSequentially('fail')\n      await page.goBack()\n      await page.waitForTimeout(300)\n      await expect(page).toHaveURL(url => url.search === '')\n    })\n  })\n  test(config)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/dynamic-segments.spec.ts",
    "content": "import { expect, test as it, type Page } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { getOptionsUrl } from '../lib/options'\nimport { navigateTo } from '../playwright/navigate'\n\ntype TestDynamicSegmentsOptions = TestConfig & {\n  expectedSegments: string[]\n}\n\nexport function testDynamicSegments({\n  expectedSegments,\n  ...options\n}: TestDynamicSegmentsOptions) {\n  async function expectSegments(page: Page, environment: 'client' | 'server') {\n    if (expectedSegments.length === 0) {\n      await expect(page.locator(`#${environment}-segments`)).toBeEmpty()\n    } else {\n      await expect(page.locator(`#${environment}-segments`)).toHaveText(\n        JSON.stringify(expectedSegments)\n      )\n    }\n  }\n  const factory = defineTest('Dynamic segments', ({ path }) => {\n    for (const shallow of [true, false]) {\n      for (const history of ['replace', 'push'] as const) {\n        it(`${path}: Updates with ({ shallow: ${shallow}, history: ${history} })`, async ({\n          page\n        }) => {\n          await navigateTo(page, getOptionsUrl(path, { shallow, history }))\n          await expect(page.locator('#client-state')).toBeEmpty()\n          await expect(page.locator('#server-state')).toBeEmpty()\n          await expectSegments(page, 'server')\n          await expectSegments(page, 'client')\n          await page.locator('button').click()\n          await expect(page.locator('#client-state')).toHaveText('pass')\n          await expectSegments(page, 'server')\n          await expectSegments(page, 'client')\n          if (shallow === false) {\n            await expect(page.locator('#server-state')).toHaveText('pass')\n          } else {\n            await expect(page.locator('#server-state')).toBeEmpty()\n          }\n          if (history !== 'push') {\n            return\n          }\n          await expect(page).toHaveURL(\n            url => url.searchParams.get('test') === 'pass'\n          )\n          await page.goBack()\n          await expect(page.locator('#client-state')).toBeEmpty()\n          await expect(page.locator('#server-state')).toBeEmpty()\n          await expectSegments(page, 'server')\n          await expectSegments(page, 'client')\n        })\n      }\n    }\n  })\n  return factory(options)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/dynamic-segments.tsx",
    "content": "'use client'\n\nimport { useQueryState, useQueryStates } from 'nuqs'\nimport type { ReactNode } from 'react'\nimport { Display, type DisplayProps } from '../components/display'\nimport { optionsSearchParams } from '../lib/options'\n\ntype UrlControlsProps = {\n  children?: ReactNode\n}\n\nexport function UrlControls({ children }: UrlControlsProps) {\n  const [{ shallow, history }] = useQueryStates(optionsSearchParams)\n  const [state, setState] = useQueryState('test', { shallow, history })\n  return (\n    <>\n      <button onClick={() => setState('pass')}>Test</button>\n      {children}\n      <Display environment=\"client\" state={state} />\n    </>\n  )\n}\n\n// --\n\ntype DisplaySegmentsProps = Pick<DisplayProps, 'environment'> & {\n  segments: string[] | undefined\n}\n\nexport function DisplaySegments({\n  segments,\n  environment\n}: DisplaySegmentsProps) {\n  return (\n    <Display\n      environment={environment}\n      target=\"segments\"\n      state={JSON.stringify(segments)}\n    />\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/flush-after-navigate.defs.ts",
    "content": "import {\n  createLoader,\n  createSerializer,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsString,\n  parseAsStringLiteral\n} from 'nuqs/server'\n\nexport const searchParams = {\n  debounce: parseAsInteger,\n  throttle: parseAsInteger,\n  linkState: parseAsString,\n  history: parseAsStringLiteral(['push', 'replace']).withDefault('replace'),\n  shallow: parseAsBoolean.withDefault(true),\n  linkPath: parseAsStringLiteral(['/end', '/start']).withDefault('/end')\n}\n\nexport const testSearchParams = {\n  test: parseAsString\n}\n\nexport const loadSearchParams = createLoader(searchParams)\nexport const getUrl = createSerializer(searchParams)\n"
  },
  {
    "path": "packages/e2e/shared/specs/flush-after-navigate.spec.ts",
    "content": "import { expect, type Page, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\nimport { getUrl } from './flush-after-navigate.defs'\n\nasync function expectPathname(page: Page, pathname: string) {\n  await expect(page).toHaveURL(url => url.pathname.endsWith(pathname))\n}\n\nexport function testFlushAfterNavigate(config: TestConfig) {\n  const shallows = [true, false]\n  const histories = ['push', 'replace'] as const\n  for (const shallow of shallows) {\n    for (const history of histories) {\n      const test = defineTest(\n        {\n          label: 'Flush after navigate',\n          variants: `shallow: ${shallow}, history: ${history}`\n        },\n        ({ path }) => {\n          const timeMs = 200\n          it('should not apply pending search params after navigation', async ({\n            page\n          }) => {\n            await navigateTo(\n              page,\n              getUrl(path + '/start', { shallow, history, debounce: timeMs })\n            )\n            async function runTest() {\n              await page.locator('#test').click()\n              await page.locator('a').click()\n              await expectPathname(page, path + '/end')\n              await expect(page.locator('#client-useQueryState')).toBeEmpty()\n              await expect(page.locator('#client-useQueryStates')).toBeEmpty()\n              await expect(page).toHaveURL(url => url.search === '')\n              await page.waitForTimeout(timeMs)\n              await expect(page.locator('#client-useQueryState')).toBeEmpty()\n              await expect(page.locator('#client-useQueryStates')).toBeEmpty()\n              await expect(page).toHaveURL(url => url.search === '')\n            }\n            await runTest()\n            await page.goBack()\n            await runTest()\n          })\n          it('should not apply pending search params on top of existing link state when navigating to another page', async ({\n            page\n          }) => {\n            await navigateTo(\n              page,\n              getUrl(path + '/start', {\n                shallow,\n                history,\n                debounce: timeMs,\n                linkState: 'nav'\n              })\n            )\n            async function runTest() {\n              await page.locator('#test').click()\n              await page.locator('a').click()\n              await expectPathname(page, path + '/end')\n              await expect(page.locator('#client-useQueryState')).toHaveText(\n                'nav'\n              )\n              await expect(page.locator('#client-useQueryStates')).toHaveText(\n                'nav'\n              )\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n              await page.waitForTimeout(timeMs)\n              await expect(page.locator('#client-useQueryState')).toHaveText(\n                'nav'\n              )\n              await expect(page.locator('#client-useQueryStates')).toHaveText(\n                'nav'\n              )\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n            }\n            await runTest()\n            await page.goBack()\n            await runTest()\n          })\n          it('should not apply pending search params on top of existing link state when navigating to the same page', async ({\n            page\n          }) => {\n            await navigateTo(\n              page,\n              getUrl(path + '/start', {\n                shallow,\n                history,\n                debounce: timeMs,\n                linkPath: '/start',\n                linkState: 'nav'\n              })\n            )\n            async function runTest() {\n              await page.locator('#test').click()\n              await page.locator('a').click()\n              await expectPathname(page, path + '/start')\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n              await expect(page.locator('#client-state')).toHaveText('nav')\n              await page.waitForTimeout(timeMs)\n              await expect(page.locator('#client-state')).toHaveText('nav')\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n            }\n            await runTest()\n            await page.goBack()\n            await runTest()\n          })\n          it('should not apply pending search params queued while throttled after navigation', async ({\n            page\n          }) => {\n            await navigateTo(\n              page,\n              getUrl(path + '/start', { shallow, history, throttle: timeMs })\n            )\n            async function runTest() {\n              await page.locator('#preflush').click() // Trigger an immediate flush to enable the throttling queue\n              await page.locator('#test').click() // Queue the change\n              await page.locator('a').click() // Navigate\n              await expectPathname(page, path + '/end')\n              await expect(page.locator('#client-useQueryState')).toBeEmpty()\n              await expect(page.locator('#client-useQueryStates')).toBeEmpty()\n              await expect(page).toHaveURL(url => url.search === '')\n              await page.waitForTimeout(timeMs)\n              await expect(page.locator('#client-useQueryState')).toBeEmpty()\n              await expect(page.locator('#client-useQueryStates')).toBeEmpty()\n              await expect(page).toHaveURL(url => url.search === '')\n            }\n            await runTest()\n            await page.goBack()\n            await runTest()\n          })\n          it('should not apply pending search params queued while throttled after navigation with link state', async ({\n            page\n          }) => {\n            await navigateTo(\n              page,\n              getUrl(path + '/start', {\n                shallow,\n                history,\n                throttle: timeMs,\n                linkState: 'nav'\n              })\n            )\n            async function runTest() {\n              await page.locator('#preflush').click() // Trigger an immediate flush to enable the throttling queue\n              await page.locator('#test').click() // Queue the change\n              await page.locator('a').click() // Navigate\n              await expectPathname(page, path + '/end')\n              await expect(page.locator('#client-useQueryState')).toHaveText(\n                'nav'\n              )\n              await expect(page.locator('#client-useQueryStates')).toHaveText(\n                'nav'\n              )\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n              await page.waitForTimeout(timeMs)\n              await expect(page.locator('#client-useQueryState')).toHaveText(\n                'nav'\n              )\n              await expect(page.locator('#client-useQueryStates')).toHaveText(\n                'nav'\n              )\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n            }\n            await runTest()\n            await page.goBack()\n            await runTest()\n          })\n          it('should not apply pending search params queued while throttled after navigation with link state to the same page', async ({\n            page\n          }) => {\n            await navigateTo(\n              page,\n              getUrl(path + '/start', {\n                shallow,\n                history,\n                throttle: timeMs,\n                linkPath: '/start',\n                linkState: 'nav'\n              })\n            )\n            async function runTest() {\n              await page.locator('#preflush').click() // Trigger an immediate flush to enable the throttling queue\n              await page.locator('#test').click() // Queue the change\n              await page.locator('a').click() // Navigate\n              await expectPathname(page, path + '/start')\n              await expect(page.locator('#client-state')).toHaveText('nav')\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n              await page.waitForTimeout(timeMs)\n              await expect(page.locator('#client-state')).toHaveText('nav')\n              await expect(page).toHaveURL(url => url.search === '?test=nav')\n            }\n            await runTest()\n            await page.goBack()\n            await runTest()\n          })\n        }\n      )\n      test(config)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/flush-after-navigate.tsx",
    "content": "'use client'\n\nimport { createSerializer, useQueryState, useQueryStates } from 'nuqs'\nimport { Display } from '../components/display'\nimport { useLink } from '../components/link'\nimport { searchParams, testSearchParams } from './flush-after-navigate.defs'\n\nconst getLink = createSerializer(testSearchParams)\n\nexport function FlushAfterNavigateUseQueryStateStart({\n  path\n}: {\n  path: string\n}) {\n  const [{ debounce, throttle, linkState, linkPath, history, shallow }] =\n    useQueryStates(searchParams)\n  const Link = useLink()\n  const [, preflush] = useQueryState('preflush')\n  const [state, setState] = useQueryState('test', {\n    history,\n    shallow,\n    limitUrlUpdates:\n      debounce !== null\n        ? { method: 'debounce', timeMs: debounce }\n        : throttle !== null\n          ? { method: 'throttle', timeMs: throttle }\n          : undefined\n  })\n  return (\n    <>\n      <button id=\"test\" onClick={() => setState('fail')}>\n        Test\n      </button>\n      <button id=\"preflush\" onClick={() => preflush(null)}>\n        Preflush\n      </button>\n      <Link href={getLink(`${path}${linkPath}`, { test: linkState })}>\n        Link\n      </Link>\n      <Display environment=\"client\" state={state} />\n    </>\n  )\n}\n\nexport function FlushAfterNavigateUseQueryStatesStart({\n  path\n}: {\n  path: string\n}) {\n  const [{ debounce, throttle, linkState, linkPath, history, shallow }] =\n    useQueryStates(searchParams)\n  const Link = useLink()\n  const [, preflush] = useQueryState('preflush')\n  const [{ test: state }, setState] = useQueryStates(testSearchParams, {\n    history,\n    shallow,\n    limitUrlUpdates:\n      debounce !== null\n        ? { method: 'debounce', timeMs: debounce }\n        : throttle !== null\n          ? { method: 'throttle', timeMs: throttle }\n          : undefined\n  })\n  return (\n    <>\n      <button id=\"test\" onClick={() => setState({ test: 'fail' })}>\n        Test\n      </button>\n      <button id=\"preflush\" onClick={() => preflush(null)}>\n        Preflush\n      </button>\n      <Link href={getLink(`${path}${linkPath}`, { test: linkState })}>\n        Link\n      </Link>\n      <Display environment=\"client\" state={state} />\n    </>\n  )\n}\n\nexport function FlushAfterNavigateEnd() {\n  const [stateUseQueryState] = useQueryState('test')\n  const [{ test: stateUseQueryStates }] = useQueryStates(testSearchParams)\n  return (\n    <>\n      <Display\n        environment=\"client\"\n        target=\"useQueryState\"\n        state={stateUseQueryState}\n      />\n      <Display\n        environment=\"client\"\n        target=\"useQueryStates\"\n        state={stateUseQueryStates}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/form.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testForm = defineTest('Form', ({ path }) => {\n  it('supports native HTML forms to update search params', async ({\n    page\n  }) => {\n    await navigateTo(page, path)\n    await page.locator('input').fill('pass')\n    await page.locator('input').press('Enter')\n    await expect(page.locator('#state')).toHaveText('pass')\n    await page.goBack()\n    await expect(page.locator('#state')).toHaveText('')\n  })\n\n  it('supports loading initial form state from the URL', async ({ page }) => {\n    await navigateTo(page, path, '?test=init')\n    await expect(page.locator('input')).toHaveValue('init')\n    await expect(page.locator('#state')).toHaveText('init')\n    await page.locator('input').fill('pass')\n    await page.locator('input').press('Enter')\n    await expect(page.locator('#state')).toHaveText('pass')\n    await page.goBack()\n    await expect(page.locator('#state')).toHaveText('init')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/form.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\n\ntype FormProps = {\n  defaultValue: string\n}\n\nconst testParser = parseAsString.withDefault('')\n\nfunction Form({ defaultValue }: FormProps) {\n  return (\n    <form>\n      <input type=\"text\" id=\"test\" name=\"test\" defaultValue={defaultValue} />\n      <button type=\"submit\">Submit</button>\n    </form>\n  )\n}\n\nexport function TestFormUseQueryState() {\n  const [state] = useQueryState('test', testParser)\n  return (\n    <>\n      <pre id=\"state\">{state}</pre>\n      <Form defaultValue={state} />\n    </>\n  )\n}\n\nexport function TestFormUseQueryStates() {\n  const [{ state }] = useQueryStates(\n    {\n      state: testParser\n    },\n    {\n      urlKeys: {\n        state: 'test'\n      }\n    }\n  )\n  return (\n    <>\n      <pre id=\"state\">{state}</pre>\n      <Form defaultValue={state} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/hash-preservation.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testHashPreservation = defineTest(\n  'Hash Preservation',\n  ({ path }) => {\n    it('preserves the hash on state updates', async ({ page }) => {\n      await navigateTo(page, path, '#hash')\n      await page.locator('#set').click()\n      await expect(page).toHaveURL(url => url.hash === '#hash')\n      await page.locator('#clear').click()\n      await expect(page).toHaveURL(url => url.hash === '#hash')\n    })\n  }\n)\n"
  },
  {
    "path": "packages/e2e/shared/specs/hash-preservation.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\n\nexport function HashPreservation() {\n  const [, setState] = useQueryState('test')\n  return (\n    <>\n      <button id=\"set\" onClick={() => setState('pass')}>\n        Set\n      </button>\n      <button id=\"clear\" onClick={() => setState(null)}>\n        Clear\n      </button>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/history-sync.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testHistorySync = defineTest('History Sync', ({ path }) => {\n  it('history.replaceState synchronously updates location.search', async ({\n    page\n  }) => {\n    await navigateTo(page, path)\n    await page.getByRole('button', { name: 'Test replaceState' }).click()\n    await expect(page.locator('#client-state')).toHaveText('?test=pass')\n  })\n\n  it('history.pushState synchronously updates location.search', async ({\n    page\n  }) => {\n    await navigateTo(page, path)\n    await page.getByRole('button', { name: 'Test pushState' }).click()\n    await expect(page.locator('#client-state')).toHaveText('?test=pass')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/history-sync.tsx",
    "content": "'use client'\n\nimport { useState } from 'react'\nimport { Display } from '../components/display'\n\nexport function HistorySync() {\n  const [state, setState] = useState('init')\n  return (\n    <>\n      <button\n        onClick={() => {\n          history.replaceState(null, '', '?test=pass')\n          setState(location.search || 'fail')\n        }}\n      >\n        Test replaceState\n      </button>\n      <button\n        onClick={() => {\n          history.pushState(null, '', '?test=pass')\n          setState(location.search || 'fail')\n        }}\n      >\n        Test pushState\n      </button>\n      <Display environment=\"client\" state={state} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/json.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testJson = defineTest('parseAsJson', ({ path }) => {\n  it('reads JSON from the URL', async ({ page }) => {\n    await navigateTo(page, path, '?test={\"name\":\"pass\",\"age\":123}')\n    await expect(page.locator('#name-input')).toHaveValue('pass')\n    await expect(page.locator('#client-name')).toHaveText('pass')\n    await expect(page.locator('#client-age')).toHaveText('123')\n  })\n\n  it('writes JSON to the URL', async ({ page }) => {\n    await navigateTo(page, path)\n    await expect(page.locator('#name-input')).toHaveValue('init')\n    await expect(page.locator('#client-name')).toHaveText('init')\n    await expect(page.locator('#client-age')).toHaveText('42')\n    await page.locator('button').click()\n    await expect(page).toHaveURL(\n      url => url.search === '?test={%22name%22:%22pass%22,%22age%22:43}'\n    )\n    await expect(page.locator('#name-input')).toHaveValue('pass')\n    await expect(page.locator('#client-name')).toHaveText('pass')\n    await expect(page.locator('#client-age')).toHaveText('43')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/json.tsx",
    "content": "'use client'\n\nimport {\n  createStandardSchemaV1,\n  type inferParserType,\n  parseAsInteger,\n  parseAsJson,\n  parseAsString,\n  useQueryState\n} from 'nuqs'\nimport { Display } from '../components/display'\n\n// Normally we'd use Zod for a schema, but we can leverage\n// the Standard Schema validator instead for a zero-dep validation.\nconst schema = {\n  name: parseAsString.withDefault('init'),\n  age: parseAsInteger.withDefault(42)\n}\ntype Schema = inferParserType<typeof schema>\nconst validate = createStandardSchemaV1(schema)\n\nconst defaultValue: Schema = {\n  name: schema.name.defaultValue,\n  age: schema.age.defaultValue\n}\n\nexport const parser = parseAsJson(validate).withDefault(defaultValue)\n\nexport function Json() {\n  const [{ name, age }, setState] = useQueryState('test', parser)\n  return (\n    <>\n      <input\n        id=\"name-input\"\n        type=\"text\"\n        value={name}\n        onChange={e => setState(old => ({ ...old, name: e.target.value }))}\n      />\n      <button\n        onClick={() => setState(old => ({ name: 'pass', age: old.age + 1 }))}\n      >\n        Test\n      </button>\n      <Display environment=\"client\" target=\"name\" state={name} />\n      <Display environment=\"client\" target=\"age\" state={age} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/key-isolation.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { assertLogCount, setupLogSpy } from '../playwright/log-spy'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testKeyIsolation = defineTest('Key isolation', ({ path }) => {\n  it('does not render b when updating a', async ({ page }) => {\n    using logSpy = setupLogSpy(page)\n    await navigateTo(page, path)\n    await page.locator('#trigger-a').click()\n    await expect(page.locator('#state-a')).toHaveText('pass')\n    await expect(page).toHaveURL(url => url.search === '?a=pass')\n    await assertLogCount(logSpy, 'render a', 3) // 1 at mount + 2 at update\n    await assertLogCount(logSpy, 'render b', 1) // only 1 at mount\n  })\n\n  it('does not render a when updating b', async ({ page }) => {\n    using logSpy = setupLogSpy(page)\n    await navigateTo(page, path)\n    await page.locator('#trigger-b').click()\n    await expect(page.locator('#state-b')).toHaveText('pass')\n    await expect(page).toHaveURL(url => url.search === '?b=pass')\n    await assertLogCount(logSpy, 'render b', 3) // 1 at mount + 2 at update\n    await assertLogCount(logSpy, 'render a', 1) // only 1 at mount\n  })\n\n  it('does not render a again when updating b after a', async ({ page }) => {\n    using logSpy = setupLogSpy(page)\n    await navigateTo(page, path)\n    await page.locator('#trigger-a').click()\n    await expect(page.locator('#state-a')).toHaveText('pass')\n    await page.locator('#trigger-b').click()\n    await expect(page.locator('#state-b')).toHaveText('pass')\n    await expect(page).toHaveURL(url => url.search === '?a=pass&b=pass')\n    await assertLogCount(logSpy, 'render a', 3) // 1 at mount + 2 at update\n    await assertLogCount(logSpy, 'render b', 3) // 1 at mount + 2 at update\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/key-isolation.tsx",
    "content": "import { parseAsString, useQueryState, useQueryStates } from 'nuqs'\n\ntype TestComponentProps = {\n  id: string\n}\n\nexport function KeyIsolationUseQueryState() {\n  return (\n    <>\n      <TestComponentUseQueryState id=\"a\" />\n      <TestComponentUseQueryState id=\"b\" />\n    </>\n  )\n}\n\nexport function KeyIsolationUseQueryStates() {\n  return (\n    <>\n      <TestComponentUseQueryStates id=\"a\" />\n      <TestComponentUseQueryStates id=\"b\" />\n    </>\n  )\n}\n\nfunction TestComponentUseQueryState({ id }: TestComponentProps) {\n  const [state, setState] = useQueryState(id)\n  console.log(`render ${id}`)\n  return (\n    <>\n      <button id={`trigger-${id}`} onClick={() => setState('pass')}>\n        Test {id}\n      </button>\n      <pre id={`state-${id}`}>{state}</pre>\n    </>\n  )\n}\n\nfunction TestComponentUseQueryStates({ id }: TestComponentProps) {\n  const [state, setState] = useQueryStates({\n    [id]: parseAsString\n  })\n  console.log(`render ${id}`)\n  return (\n    <>\n      <button id={`trigger-${id}`} onClick={() => setState({ [id]: 'pass' })}>\n        Test {id}\n      </button>\n      <pre id={`state-${id}`}>{state[id]}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/life-and-death.spec.ts",
    "content": "import { expect, test as it, type Page } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\n/**\n * This tests that components mounting from a URL state update that also consume\n * that state do mount with the correct (optimistic) one from the get-go.\n * It also tests that they can clear it (self-destruct) and that they don't\n * throw errors when doing so.\n * See https://github.com/47ng/nuqs/issues/702\n */\n\nexport const testLifeAndDeath = defineTest('Life & Death', ({ path }) => {\n  it('set from URL initial state, clear from useQueryState', async ({\n    page\n  }) => {\n    await navigateTo(page, path, '?test=pass')\n    await assertFilledState(page)\n    await page.locator('#clear-useQueryState').click()\n    await assertEmptyState(page)\n  })\n\n  it('set from URL initial state, clear from useQueryStates', async ({\n    page\n  }) => {\n    await navigateTo(page, path, '?test=pass')\n    await assertFilledState(page)\n    await page.locator('#clear-useQueryStates').click()\n    await assertEmptyState(page)\n  })\n\n  it('set from useQueryState, clear from useQueryState', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('#set-useQueryState').click()\n    await assertFilledState(page)\n    await page.locator('#clear-useQueryState').click()\n    await assertEmptyState(page)\n  })\n\n  it('set from useQueryState, clear from useQueryStates', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('#set-useQueryState').click()\n    await assertFilledState(page)\n    await page.locator('#clear-useQueryStates').click()\n    await assertEmptyState(page)\n  })\n\n  it('set from useQueryStates, clear from useQueryState', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('#set-useQueryStates').click()\n    await assertFilledState(page)\n    await page.locator('#clear-useQueryState').click()\n    await assertEmptyState(page)\n  })\n\n  it('set from useQueryStates, clear from useQueryStates', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('#set-useQueryStates').click()\n    await assertFilledState(page)\n    await page.locator('#clear-useQueryStates').click()\n    await assertEmptyState(page)\n  })\n})\n\nasync function assertFilledState(page: Page) {\n  await expect(page.locator('#client-useQueryState')).toHaveText('pass')\n  await expect(page.locator('#client-useQueryStates')).toHaveText('pass')\n  await expect(page.locator('#null-detector-useQueryState')).toHaveText('pass')\n  await expect(page.locator('#null-detector-useQueryStates')).toHaveText('pass')\n}\n\nasync function assertEmptyState(page: Page) {\n  await expect(page.locator('button')).toHaveCount(2)\n  await expect(page.locator('#client-useQueryState')).toHaveCount(0)\n  await expect(page.locator('#client-useQueryStates')).toHaveCount(0)\n  await expect(page.locator('#null-detector-useQueryState')).toHaveCount(0)\n  await expect(page.locator('#null-detector-useQueryStates')).toHaveCount(0)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/life-and-death.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { Display } from '../components/display'\nimport { NullDetector } from '../components/null-detector'\n\nexport function LifeAndDeath() {\n  return (\n    <>\n      <LifeAndDeathUseQueryState />\n      <LifeAndDeathUseQueryStates />\n    </>\n  )\n}\n\nconst useQueryStateTest = () => useQueryState('test')\nconst useQueryStatesTest = () => useQueryStates({ test: parseAsString })\n\nfunction LifeAndDeathUseQueryState() {\n  const [test, setTest] = useQueryStateTest()\n  return (\n    <div>\n      <button id=\"set-useQueryState\" onClick={() => setTest('pass')}>\n        Test useQueryState\n      </button>\n      {test === 'pass' ? <ChildComponentUseQueryState /> : null}\n    </div>\n  )\n}\n\nfunction ChildComponentUseQueryState() {\n  const [test, setTest] = useQueryStateTest()\n  return (\n    <>\n      <button id=\"clear-useQueryState\" onClick={() => setTest(null)}>\n        Clear\n      </button>\n      <Display environment=\"client\" target=\"useQueryState\" state={test} />\n      <NullDetector throwOnNull id=\"null-detector-useQueryState\" state={test} />\n    </>\n  )\n}\n\n// --\n\nfunction LifeAndDeathUseQueryStates() {\n  const [{ test }, setTest] = useQueryStatesTest()\n  return (\n    <div>\n      <button id=\"set-useQueryStates\" onClick={() => setTest({ test: 'pass' })}>\n        Test useQueryStates\n      </button>\n      {test === 'pass' ? <ChildComponentUseQueryStates /> : null}\n    </div>\n  )\n}\n\nfunction ChildComponentUseQueryStates() {\n  const [{ test }, setTest] = useQueryStatesTest()\n  return (\n    <>\n      <button id=\"clear-useQueryStates\" onClick={() => setTest({ test: null })}>\n        Clear\n      </button>\n      <Display environment=\"client\" target=\"useQueryStates\" state={test} />\n      <NullDetector\n        throwOnNull\n        id=\"null-detector-useQueryStates\"\n        state={test}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/linking.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testLinking = defineTest('Linking', ({ path }) => {\n  it('picks up state from Links pointing to the same page', async ({\n    page\n  }) => {\n    await navigateTo(page, path)\n    await expect(page.locator('#state')).toBeEmpty()\n    await page.locator('a').click()\n    await expect(page.locator('#state')).toHaveText('pass')\n  })\n\n  it('picks up state from Links from another page', async ({ page }) => {\n    await navigateTo(page, path + '/other')\n    await expect(page.locator('#state')).toBeEmpty()\n    await page.locator('a').click()\n    await expect(page.locator('#state').first()).toHaveText('pass')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/linking.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { useLink } from '../components/link'\n\ntype Props = {\n  path: string\n}\n\nexport function LinkingUseQueryState({ path }: Props) {\n  const Link = useLink()\n  const [state] = useQueryState('test')\n  return (\n    <>\n      <Link href={path + '?test=pass'}>Test</Link>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n\nexport function LinkingUseQueryStates({ path }: Props) {\n  const Link = useLink()\n  const [{ test: state }] = useQueryStates({\n    test: parseAsString\n  })\n  return (\n    <>\n      <Link href={path + '?test=pass'}>Test</Link>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/loader.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testLoader = defineTest('Loader', ({ path }) => {\n  it('loads state from the URL', async ({ page }) => {\n    await navigateTo(page, path + '?test=pass&int=42')\n    await expect(page.locator('#test')).toHaveText('pass')\n    await expect(page.locator('#int')).toHaveText('42')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/loader.tsx",
    "content": "import {\n  createLoader,\n  type inferParserType,\n  parseAsInteger,\n  parseAsString\n} from 'nuqs/server'\n\nconst searchParams = {\n  test: parseAsString,\n  int: parseAsInteger\n}\n\nexport type SearchParams = inferParserType<typeof searchParams>\nexport const loadSearchParams = createLoader(searchParams)\n\ntype LoaderRendererProps = {\n  serverValues: inferParserType<typeof searchParams>\n}\n\nexport function LoaderRenderer({ serverValues }: LoaderRendererProps) {\n  return (\n    <>\n      <pre id=\"test\">{serverValues.test}</pre>\n      <pre id=\"int\">{serverValues.int}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/native-array.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testNativeArray = defineTest('parseAsNativeArray', ({ path }) => {\n  it('reads native array from the URL', async ({ page }) => {\n    await navigateTo(page, path, '?test=1&test=2&test=3')\n    await expect(page.locator('#client-name')).toHaveText('1 - 2 - 3')\n    await page.locator('#add-button').click()\n    await expect(page.locator('#client-name')).toHaveText('1 - 2 - 3 - 4')\n  })\n\n  it('works with the browser back button', async ({ page }) => {\n    await navigateTo(page, path)\n    await expect(page.locator('#client-name')).toBeEmpty()\n    // Note: adding assertions after each action to ensure proper sequencing\n    // (otherwise Playwright pools all actions into one URL update)\n    await page.locator('#add-button').click()\n    await expect(page.locator('#client-name')).toHaveText('1')\n    await expect(page).toHaveURL(url => url.search === '?test=1')\n    await page.locator('#add-button').click()\n    await expect(page.locator('#client-name')).toHaveText('1 - 2')\n    await expect(page).toHaveURL(url => url.search === '?test=1&test=2')\n    await page.locator('#add-button').click()\n    await expect(page.locator('#client-name')).toHaveText('1 - 2 - 3')\n    await expect(page).toHaveURL(url => url.search === '?test=1&test=2&test=3')\n    await page.goBack()\n    await expect(page).toHaveURL(url => url.search === '?test=1&test=2')\n    await expect(page.locator('#client-name')).toHaveText('1 - 2')\n    await page.goForward()\n    await expect(page).toHaveURL(url => url.search === '?test=1&test=2&test=3')\n    await expect(page.locator('#client-name')).toHaveText('1 - 2 - 3')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/native-array.tsx",
    "content": "'use client'\n\nimport { parseAsInteger, parseAsNativeArrayOf, useQueryState } from 'nuqs'\nimport { Display } from '../components/display'\n\nexport const parser = parseAsNativeArrayOf(parseAsInteger).withOptions({\n  history: 'push'\n})\n\nexport function NativeArray() {\n  const [state, setState] = useQueryState('test', parser)\n  return (\n    <>\n      <button onClick={() => setState([])}>Reset</button>\n      <button\n        id=\"add-button\"\n        onClick={() => setState(prev => prev.concat(prev.length + 1))}\n      >\n        Add\n      </button>\n      <Display environment=\"client\" target=\"name\" state={state?.join(' - ')} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/popstate-queue-reset.defs.ts",
    "content": "import { createSerializer, parseAsInteger, parseAsString } from 'nuqs/server'\n\nexport const searchParams = {\n  a: parseAsInteger.withDefault(0),\n  b: parseAsInteger.withDefault(0),\n  c: parseAsInteger.withDefault(0)\n}\n\nexport const controlSearchParams = {\n  debounceTime: parseAsInteger.withDefault(500),\n  value: parseAsString.withDefault('')\n}\n\nexport const getUrl = createSerializer(controlSearchParams, {\n  clearOnDefault: false\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/popstate-queue-reset.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { expectSearch } from '../playwright/expect-url'\nimport { navigateTo } from '../playwright/navigate'\nimport { getUrl } from './popstate-queue-reset.defs'\n\ntype TestPopstateQueueResetConfig = TestConfig & {\n  otherPath?: string\n}\n\nexport function testPopstateQueueReset(config: TestPopstateQueueResetConfig) {\n  const test = defineTest('Popstate Queue Reset', ({ path }) => {\n    const otherPath = config.otherPath ?? path + '/other'\n\n    it('should cancel pending debounced updates when navigating back', async ({\n      page\n    }) => {\n      // Navigate to \"other\" page first, then to test page\n      await navigateTo(page, otherPath)\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate to test page with long debounce\n      await navigateTo(page, getUrl(path, { debounceTime: 500 }))\n\n      // Type to trigger debounced update\n      await page.locator('#debounced-input').fill('pending-value')\n\n      // Verify optimistic update shows the value\n      await expect(page.locator('#value-display')).toHaveText('pending-value')\n\n      // Navigate back before debounce completes\n      await page.goBack()\n\n      // Wait for debounce time to pass\n      await page.waitForTimeout(600)\n\n      // Verify we're on the other page and URL is clean\n      await expect(page.locator('#other-page')).toBeVisible()\n      await expect(page).toHaveURL(url => !url.search.includes('pending-value'))\n    })\n\n    it('should not apply old queued updates after back/forward navigation', async ({\n      page\n    }) => {\n      // Navigate to other page first\n      await navigateTo(page, otherPath)\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate to test page with debounce\n      await navigateTo(page, getUrl(path, { debounceTime: 400 }))\n\n      // Trigger debounced update\n      await page.locator('#debounced-input').fill('old-value')\n      await expect(page.locator('#value-display')).toHaveText('old-value')\n\n      // Navigate back before debounce completes\n      await page.goBack()\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate forward - this triggers popstate which should reset queues\n      await page.goForward()\n\n      // Clear and enter new value\n      await page.locator('#debounced-input').clear()\n      await page.locator('#debounced-input').fill('new-value')\n\n      // Wait for debounce to complete\n      await page.waitForTimeout(500)\n\n      // Should show ONLY new-value, not old-value\n      await expect(page).toHaveURL(url =>\n        url.search.includes('value=new-value')\n      )\n      await expect(page).toHaveURL(url => !url.search.includes('old-value'))\n    })\n\n    it('should properly sequence updates after back/forward navigation (mutex reset)', async ({\n      page\n    }) => {\n      // Navigate to other page first\n      await navigateTo(page, otherPath)\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate to test page\n      await navigateTo(page, path)\n\n      // Trigger staggered updates to set up some queue state/mutex\n      await page.locator('#staggered-updates').click()\n      await expect(page.locator('#client-state')).toHaveText('1,1,1')\n\n      // Wait for all updates to complete\n      await expectSearch(page, { a: '1', b: '1', c: '1' })\n\n      // Navigate back - this triggers popstate which should reset mutex to 0\n      await page.goBack()\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate forward - another popstate event\n      await page.goForward()\n\n      // Wait for page to be ready\n      await expect(page.locator('#client-state')).toBeVisible()\n\n      // Trigger fresh staggered updates - these should sequence correctly\n      // If mutex was incorrectly reset (e.g., to 1 instead of 0 on popstate),\n      // the rate limiting / queue reset behavior would be incorrect\n      await page.locator('#staggered-updates').click()\n      await expect(page.locator('#client-state')).toHaveText('2,2,2')\n\n      // Verify URL updates sequence correctly\n      await expectSearch(page, { a: '2' })\n      await expectSearch(page, { a: '2', b: '2' })\n      await expectSearch(page, { a: '2', b: '2', c: '2' })\n    })\n\n    it('should clear all pending queue updates on popstate', async ({\n      page\n    }) => {\n      // Navigate to other page first\n      await navigateTo(page, otherPath)\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate to test page\n      await navigateTo(page, path)\n\n      // Trigger staggered updates (a=immediate, b=250ms, c=500ms)\n      await page.locator('#staggered-updates').click()\n      await expect(page.locator('#client-state')).toHaveText('1,1,1')\n\n      // Wait for 'a' to be applied but not 'b' or 'c'\n      await expectSearch(page, { a: '1' })\n\n      // Navigate back before b and c are applied\n      await page.goBack()\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Wait for what would have been the debounce completion\n      await page.waitForTimeout(600)\n\n      // Verify the other page's URL is clean (b and c were not applied)\n      await expect(page).toHaveURL(url => !url.search.includes('b='))\n      await expect(page).toHaveURL(url => !url.search.includes('c='))\n    })\n\n    // This test uses client-side router navigation to ensure\n    // the popstate event fires within the same SPA context,\n    // which is required to test the queue reset functionality.\n    //\n    // CRITICAL: This test specifically targets the popstate handler's\n    // resetQueues() call. The key scenario is:\n    // 1. Trigger ONLY debounced updates (no immediate updates)\n    //    - This avoids the onHistoryStateUpdate -> resetQueues flow\n    // 2. Navigate via popstate (back/forward) before debounce fires\n    // 3. The pending debounced updates should be cancelled by resetQueues() in onPopState\n    //\n    // Without resetQueues() in onPopState, the debounced updates would fire\n    // and modify the URL of the destination page.\n    it('should abort pending debounced updates on client-side back navigation', async ({\n      page\n    }) => {\n      // Log console messages for debugging\n      page.on('console', msg => {\n        if (msg.text().includes('nuqs')) {\n          console.log('BROWSER:', msg.text())\n        }\n      })\n\n      // Start on the test page\n      await navigateTo(page, path)\n      await expect(page.locator('#client-state')).toBeVisible()\n\n      // Use client-side navigation to go to \"other\" page first\n      // This creates a history entry within the SPA context\n      await page.locator('#navigate-to-other').click()\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Navigate back to test page using browser back button (popstate)\n      await page.goBack()\n      await expect(page.locator('#client-state')).toBeVisible()\n\n      // Trigger ONLY debounced updates (b=300ms, c=500ms)\n      // No immediate update, so no history calls, so no onHistoryStateUpdate\n      await page.locator('#debounced-only').click()\n\n      // Verify optimistic state shows the updates\n      await expect(page.locator('#client-state')).toHaveText('0,1,1')\n\n      // Navigate forward to other page IMMEDIATELY (before any debounce fires)\n      // This is a popstate event, which should trigger resetQueues() in onPopState\n      await page.goForward()\n      await expect(page.locator('#other-page')).toBeVisible()\n\n      // Wait for what would have been the debounce completion\n      await page.waitForTimeout(600)\n\n      // If resetQueues() was called on popstate, b and c should be aborted\n      // and NOT appear in the URL.\n      // If resetQueues() was NOT called (mutation), b and c would be applied\n      // to the current URL (other page's URL).\n      await expect(page).toHaveURL(url => !url.search.includes('b='))\n      await expect(page).toHaveURL(url => !url.search.includes('c='))\n    })\n  })\n\n  test(config)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/popstate-queue-reset.tsx",
    "content": "'use client'\n\nimport { debounce, useQueryStates } from 'nuqs'\nimport { Display } from '../components/display'\nimport { controlSearchParams, searchParams } from './popstate-queue-reset.defs'\n\nexport function PopstateQueueResetClient({\n  onNavigateToOther\n}: {\n  onNavigateToOther?: () => void\n}) {\n  const [{ debounceTime, value }, setControls] =\n    useQueryStates(controlSearchParams)\n  const [{ a, b, c }, setSearchParams] = useQueryStates(searchParams)\n\n  // Debounced value update - used to test queue cancellation on popstate\n  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setControls(\n      { value: e.target.value || null },\n      { limitUrlUpdates: debounce(debounceTime) }\n    )\n  }\n\n  // Staggered updates to test queue reset and mutex state\n  const triggerStaggeredUpdates = () => {\n    setSearchParams(old => ({ a: old.a + 1 }))\n    setSearchParams(old => ({ b: old.b + 1 }), {\n      limitUrlUpdates: debounce(250)\n    })\n    setSearchParams(old => ({ c: old.c + 1 }), {\n      limitUrlUpdates: debounce(500)\n    })\n  }\n\n  // Individual updates to test sequencing after popstate\n  const triggerA = () => setSearchParams(old => ({ a: old.a + 1 }))\n  const triggerB = () =>\n    setSearchParams(old => ({ b: old.b + 1 }), {\n      limitUrlUpdates: debounce(100)\n    })\n  const triggerC = () =>\n    setSearchParams(old => ({ c: old.c + 1 }), {\n      limitUrlUpdates: debounce(200)\n    })\n\n  // Trigger ONLY debounced updates (no immediate update)\n  // This is used to test popstate reset without triggering the\n  // normal onHistoryStateUpdate -> resetQueues flow\n  const triggerDebouncedOnly = () => {\n    setSearchParams(old => ({ b: old.b + 1 }), {\n      limitUrlUpdates: debounce(300)\n    })\n    setSearchParams(old => ({ c: old.c + 1 }), {\n      limitUrlUpdates: debounce(500)\n    })\n  }\n\n  return (\n    <>\n      <Display environment=\"client\" state={[a, b, c].join(',')} />\n      <div id=\"value-display\">{value}</div>\n      <input\n        type=\"text\"\n        id=\"debounced-input\"\n        value={value}\n        onChange={handleInputChange}\n        placeholder=\"Type to trigger debounced update\"\n      />\n      <button id=\"staggered-updates\" onClick={triggerStaggeredUpdates}>\n        Trigger staggered updates\n      </button>\n      <button id=\"trigger-a\" onClick={triggerA}>\n        Trigger A\n      </button>\n      <button id=\"trigger-b\" onClick={triggerB}>\n        Trigger B\n      </button>\n      <button id=\"trigger-c\" onClick={triggerC}>\n        Trigger C\n      </button>\n      <button id=\"debounced-only\" onClick={triggerDebouncedOnly}>\n        Trigger Debounced Only\n      </button>\n      <button id=\"reset\" onClick={() => setSearchParams(null)}>\n        Reset\n      </button>\n      {onNavigateToOther && (\n        <button id=\"navigate-to-other\" onClick={onNavigateToOther}>\n          Navigate to Other\n        </button>\n      )}\n    </>\n  )\n}\n\nexport function PopstateQueueResetOther() {\n  return (\n    <div>\n      <h1 id=\"other-page\">Other Page</h1>\n      <p>This is a different page for back/forward navigation testing</p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/pretty-urls.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testPrettyUrls = defineTest('Pretty URLs', ({ path }) => {\n  it('should render unencoded characters', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('button').click()\n    await expect(page.locator('#state')).toHaveText('-._~!$()*,;=:@/?[]{}\\\\|^')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/pretty-urls.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\n\nexport function PrettyUrls() {\n  const [state, setState] = useQueryState('test')\n  return (\n    <>\n      <button onClick={() => setState('-._~!$()*,;=:@/?[]{}\\\\|^')}>Test</button>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/push.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testPush = defineTest('Push', ({ path }) => {\n  it('pushes a new state to the history and allows navigating states with Back/Forward', async ({\n    page\n  }) => {\n    await navigateTo(page, path, '?test=init')\n    await page.locator('button').click()\n    await expect(page).toHaveURL(url => url.search === '?test=pass')\n    await expect(page.locator('#state')).toHaveText('pass')\n    await page.goBack()\n    await expect(page.locator('#state')).toHaveText('init')\n    await expect(page).toHaveURL(url => url.search === '?test=init')\n    await page.goForward()\n    await expect(page).toHaveURL(url => url.search === '?test=pass')\n    await expect(page.locator('#state')).toHaveText('pass')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/push.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\n\nexport function PushUseQueryState() {\n  const [state, setState] = useQueryState('test', { history: 'push' })\n  return (\n    <>\n      <button onClick={() => setState('pass')}>Test</button>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n\nexport function PushUseQueryStates() {\n  const [{ test: state }, setSearchParams] = useQueryStates(\n    {\n      test: parseAsString\n    },\n    { history: 'push' }\n  )\n  return (\n    <>\n      <button onClick={() => setSearchParams({ test: 'pass' })}>Test</button>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/rate-limits.tsx",
    "content": "'use client'\n\nimport { parseAsInteger, useQueryState } from 'nuqs'\nimport { useCallback, useReducer } from 'react'\n\nconst UPDATE_RATE_MS = 20\nconst RUN_TIME_MS = 60_000\n\ntype UpdateSuccess = {\n  outcome: 'success'\n  time: number\n  searchParams: URLSearchParams\n}\ntype UpdateFailure = {\n  outcome: 'failure'\n  time: number\n  error: unknown\n}\n\ntype UpdateOutcome = UpdateSuccess | UpdateFailure\n\ntype TestHookResult = {\n  start: () => void\n  stop: () => void\n  reset: () => void\n  startedAt: number | null\n  currentCount: number\n  successfulUpdates: UpdateSuccess[]\n  failedUpdates: UpdateFailure[]\n}\n\ntype ReducerState = {\n  successfulUpdates: UpdateSuccess[]\n  failedUpdates: UpdateFailure[]\n  controller: AbortController | null\n  startedAt: number | null\n}\n\ntype ReducerAction =\n  | { type: 'start'; payload: AbortController }\n  | { type: 'stop' }\n  | { type: 'reset' }\n  | { type: 'update'; payload: UpdateOutcome }\n\nfunction reducer(state: ReducerState, action: ReducerAction): ReducerState {\n  performance.mark(`[nuqs] dispatch ${JSON.stringify(action)}`)\n  switch (action.type) {\n    case 'start':\n      state.controller?.abort()\n      return {\n        ...state,\n        startedAt: performance.now(),\n        controller: action.payload\n      }\n    case 'stop':\n      state.controller?.abort()\n      return {\n        ...state,\n        controller: null,\n        startedAt: null\n      }\n    case 'reset':\n      return {\n        ...state,\n        successfulUpdates: [],\n        failedUpdates: []\n      }\n    case 'update':\n      const key =\n        action.payload.outcome === 'success'\n          ? 'successfulUpdates'\n          : 'failedUpdates'\n      if (\n        state[key].findIndex(\n          (u: UpdateOutcome) => u.time === action.payload.time\n        ) !== -1\n      ) {\n        return state\n      }\n      return {\n        ...state,\n        [key]: [...state[key], action.payload]\n      }\n    default:\n      throw new Error(`Unknown action type for ${action}`)\n  }\n}\n\nconst initialState: ReducerState = {\n  successfulUpdates: [],\n  failedUpdates: [],\n  controller: null,\n  startedAt: null\n}\n\nfunction useRateLimitTest(): TestHookResult {\n  const [currentCount, setCount] = useQueryState(\n    'count',\n    parseAsInteger.withDefault(0)\n  )\n  const [state, dispatch] = useReducer(reducer, initialState)\n\n  const start = useCallback(() => {\n    const controller = new AbortController()\n    dispatch({\n      type: 'start',\n      payload: controller\n    })\n    const signal = controller.signal\n    const timeout = setTimeout(() => dispatch({ type: 'stop' }), RUN_TIME_MS)\n    let i = 1\n    const interval = setInterval(() => {\n      try {\n        setCount(i++)\n          .then(searchParams => {\n            dispatch({\n              type: 'update',\n              payload: {\n                outcome: 'success',\n                time: performance.now(),\n                searchParams\n              }\n            })\n          })\n          .catch(error => {\n            dispatch({\n              type: 'update',\n              payload: {\n                outcome: 'failure',\n                time: performance.now(),\n                error\n              }\n            })\n          })\n      } catch (error) {\n        dispatch({\n          type: 'update',\n          payload: {\n            outcome: 'failure',\n            time: performance.now(),\n            error\n          }\n        })\n      }\n    }, UPDATE_RATE_MS)\n    signal.addEventListener('abort', () => {\n      clearTimeout(timeout)\n      clearInterval(interval)\n    })\n  }, [])\n  const stop = useCallback(() => dispatch({ type: 'stop' }), [])\n  const reset = useCallback(() => {\n    setCount(null)\n    dispatch({ type: 'reset' })\n  }, [])\n\n  return {\n    start,\n    stop,\n    reset,\n    currentCount,\n    startedAt: state.startedAt,\n    failedUpdates: state.failedUpdates,\n    successfulUpdates: state.successfulUpdates\n  }\n}\n\nexport function RateLimits() {\n  const {\n    currentCount,\n    startedAt,\n    successfulUpdates,\n    failedUpdates,\n    reset,\n    start,\n    stop\n  } = useRateLimitTest()\n\n  return (\n    <>\n      <button onClick={start} disabled={startedAt !== null}>\n        Start\n      </button>\n      <button onClick={stop} disabled={startedAt === null}>\n        Stop\n      </button>\n      <button onClick={reset} disabled={startedAt !== null}>\n        Reset\n      </button>\n      <p>Count: {currentCount}</p>\n      <p>Total: {successfulUpdates.length + failedUpdates.length}</p>\n      <p>Success: {successfulUpdates.length}</p>\n      <p>Errors: {failedUpdates.length}</p>\n      {failedUpdates.length > 0 && (\n        <ul>\n          {failedUpdates.map(({ time, error }, i) => (\n            <li key={i}>\n              +{time - (startedAt ?? 0)}: {String(error)}\n            </li>\n          ))}\n        </ul>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/react-router/fog-of-war.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../../define-test'\nimport { navigateTo } from '../../playwright/navigate'\n\nexport const testFogOfWar = defineTest('Fog of War', ({ path }) => {\n  it('should navigate to the result page', async ({ page }) => {\n    await navigateTo(page, path)\n    await page.locator('#set').click()\n    await page.locator('#navigate').click()\n    await expect(page).toHaveURL(url => url.pathname.endsWith(`${path}/result`))\n    await expect(page.locator('#result')).toHaveText('pass')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/react-router/fog-of-war.tsx",
    "content": "import { useQueryState } from 'nuqs'\nimport { useLink } from '../../components/link'\n\nexport function FogOfWarStartPage({ resultHref }: { resultHref: string }) {\n  const [, setState] = useQueryState('test')\n  // const [, setState] = useState('init')\n  const Link = useLink()\n  return (\n    <>\n      <button id=\"set\" onClick={() => setState('pass')}>\n        Set\n      </button>\n      <Link id=\"navigate\" href={resultHref}>\n        Navigate\n      </Link>\n    </>\n  )\n}\n\nexport function FogOfWarResultPage() {\n  return <div id=\"result\">pass</div>\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../../define-test'\nimport { navigateTo } from '../../playwright/navigate'\n\nexport const testRepro839LocationStatePersistence = defineTest(\n  'Repro for issue #839 - Location state persistence',\n  ({ path }) => {\n    it('persists location.state on shallow URL updates', async ({ page }) => {\n      await navigateTo(page, path)\n      await page.locator('#setup').click()\n      await page.locator('#shallow').click()\n      await expect(page.locator('#state')).toHaveText('{\"test\":\"pass\"}')\n    })\n\n    it('persists location.state on deep URL updates', async ({ page }) => {\n      await navigateTo(page, path)\n      await page.locator('#setup').click()\n      await page.locator('#deep').click()\n      await expect(page.locator('#state')).toHaveText('{\"test\":\"pass\"}')\n    })\n  }\n)\n"
  },
  {
    "path": "packages/e2e/shared/specs/react-router/repro-839-location-state-persistence.tsx",
    "content": "import { useQueryState } from 'nuqs'\n\ntype Repro839Props = {\n  useNavigate: () => (url: string, options: { state: unknown }) => void\n  useLocation: () => { state: unknown }\n}\n\nexport function Repro839({ useNavigate, useLocation }: Repro839Props) {\n  const navigate = useNavigate()\n  const location = useLocation()\n  const [, setShallow] = useQueryState('shallow', {\n    shallow: true\n  })\n  const [, setDeep] = useQueryState('deep', {\n    shallow: false\n  })\n  return (\n    <>\n      <button\n        id=\"setup\"\n        onClick={() => navigate('.', { state: { test: 'pass' } })}\n      >\n        Setup\n      </button>\n      <button id=\"shallow\" onClick={() => setShallow('pass')}>\n        Test shallow\n      </button>\n      <button id=\"deep\" onClick={() => setDeep('pass')}>\n        Test deep\n      </button>\n      <pre id=\"state\">{JSON.stringify(location.state)}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/referential-stability.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testReferentialStability = defineTest(\n  'Referential stability',\n  ({ path }) => {\n    it('keeps referential stability of the setter function across updates', async ({\n      page\n    }) => {\n      await navigateTo(page, path)\n      await expect(page.locator('#state')).toHaveText('pass')\n      await page.locator('button').click()\n      await expect(page.locator('#state')).toHaveText('pass')\n    })\n  }\n)\n"
  },
  {
    "path": "packages/e2e/shared/specs/referential-stability.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { useRef } from 'react'\n\nexport function ReferentialStabilityUseQueryState() {\n  const [, setState] = useQueryState('test')\n  const setterRef = useRef(setState)\n  const hasChanged = setterRef.current !== setState\n  return (\n    <>\n      <button onClick={() => setState('test')}>Test</button>\n      <div id=\"state\">{hasChanged ? 'fail' : 'pass'}</div>\n    </>\n  )\n}\n\nexport function ReferentialStabilityUseQueryStates() {\n  const [, setState] = useQueryStates({\n    test: parseAsString\n  })\n  const setterRef = useRef(setState)\n  const hasChanged = setterRef.current !== setState\n  return (\n    <>\n      <button onClick={() => setState({ test: 'test' })}>Test</button>\n      <div id=\"state\">{hasChanged ? 'fail' : 'pass'}</div>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/render-count.params.ts",
    "content": "import {\n  createLoader,\n  type inferParserType,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsStringLiteral\n} from 'nuqs/server'\n\nconst params = {\n  hook: parseAsStringLiteral([\n    'useQueryState',\n    'useQueryStates'\n  ]).withDefault('useQueryState'),\n  shallow: parseAsBoolean.withDefault(true),\n  history: parseAsStringLiteral(['push', 'replace']).withDefault(\n    'replace'\n  ),\n  startTransition: parseAsBoolean.withDefault(false)\n}\n\nconst searchParams = {\n  delay: parseAsInteger.withDefault(0)\n}\n\nexport type Params = inferParserType<typeof params>\nexport type SearchParams = inferParserType<typeof searchParams>\n\nexport const loadParams = createLoader(params)\nexport const loadSearchParams = createLoader(searchParams)\n"
  },
  {
    "path": "packages/e2e/shared/specs/render-count.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { assertLogCount, setupLogSpy } from '../playwright/log-spy'\nimport { navigateTo } from '../playwright/navigate'\n\ntype TestRenderCountConfig = TestConfig & {\n  props: {\n    shallow: boolean\n    history: 'push' | 'replace'\n    startTransition: boolean\n    delay?: number\n  }\n  expected: {\n    mount: number\n    update: number\n  }\n}\n\nexport function testRenderCount({\n  props,\n  expected,\n  ...config\n}: TestRenderCountConfig) {\n  const test = defineTest(\n    {\n      label: 'Render count',\n      variants:\n        `shallow: ${props.shallow}, history: ${props.history}, startTransition: ${props.startTransition}` +\n        (props.delay ? `, delay: ${props.delay}ms` : '')\n    },\n    ({ path }) => {\n      it(`should render ${times(expected.mount)} on mount`, async ({\n        page\n      }) => {\n        using logSpy = setupLogSpy(page)\n        await navigateTo(page, path)\n        await assertLogCount(logSpy, 'render', expected.mount)\n      })\n\n      it(`should then render ${times(expected.update)} on updates`, async ({\n        page\n      }) => {\n        using logSpy = setupLogSpy(page)\n        await navigateTo(page, path)\n        await page.locator('button').click()\n        if (props.delay) {\n          await page.waitForTimeout(props.delay)\n        }\n        await expect(page.locator('#state')).toHaveText('pass')\n        await expect(page).toHaveURL(url => url.search.includes('test=pass'))\n        await assertLogCount(logSpy, 'render', expected.mount + expected.update)\n      })\n    }\n  )\n  return test(config)\n}\n\nfunction times(n: number) {\n  if (n === 1) {\n    return 'once'\n  }\n  return `${n} times`\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/render-count.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { startTransition as reactStartTransition } from 'react'\n\ntype RenderCountProps = {\n  hook: 'useQueryState' | 'useQueryStates'\n  shallow: boolean\n  history: 'push' | 'replace'\n  startTransition: boolean\n}\n\nexport function RenderCount({\n  hook,\n  shallow,\n  history,\n  startTransition: enableStartTransition\n}: RenderCountProps) {\n  console.log('render')\n  const startTransition = enableStartTransition\n    ? reactStartTransition\n    : undefined\n  let runTest = () => {}\n  let state = null\n  if (hook === 'useQueryState') {\n    const [testState, setState] = useQueryState('test', {\n      shallow,\n      history,\n      startTransition\n    })\n    runTest = () => setState('pass')\n    state = testState\n  }\n  if (hook === 'useQueryStates') {\n    const [{ test }, setState] = useQueryStates({\n      test: parseAsString.withOptions({ shallow, history, startTransition })\n    })\n    runTest = () => setState({ test: 'pass' })\n    state = test\n  }\n  return (\n    <>\n      <button onClick={runTest}>Test</button>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-1099.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { getOptionsUrl } from '../lib/options'\nimport { navigateTo } from '../playwright/navigate'\n\ntype TestRepro1099Options = TestConfig & {\n  shallowOptions?: boolean[]\n  historyOptions?: ('replace' | 'push')[]\n}\n\nexport function testRepro1099({\n  shallowOptions = [true, false],\n  historyOptions = ['replace', 'push'],\n  ...options\n}: TestRepro1099Options) {\n  const factory = defineTest('repro-1099', ({ path }) => {\n    for (const shallow of shallowOptions) {\n      for (const history of historyOptions) {\n        it(`should not emit null during updates with { shallow: ${shallow}, history: '${history}' }`, async ({\n          page\n        }) => {\n          await navigateTo(page, getOptionsUrl(path, { history, shallow }))\n          await expect(page.locator('#null-detector')).toHaveText('pass')\n          await page.locator('button').click()\n          await expect(page).toHaveURL(url => url.search.endsWith('test=pass'))\n          await expect(page.locator('#null-detector')).toHaveText('pass')\n        })\n      }\n    }\n  })\n  return factory(options)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-1099.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { useEffect, useState } from 'react'\nimport { NullDetector } from '../components/null-detector'\nimport { useOptions } from '../lib/options'\n\nexport function Repro1099UseQueryState() {\n  const { shallow, history } = useOptions()\n  const [state, setState] = useQueryState('test', { shallow, history })\n  const [isNullDetectorEnabled, setIsNullDetectorEnabled] = useState(false)\n  useStateUpdateInEffect(state)\n  return (\n    <>\n      <button\n        onClick={() => {\n          setIsNullDetectorEnabled(true)\n          setState('pass')\n        }}\n      >\n        Test\n      </button>\n      <NullDetector state={state} enabled={isNullDetectorEnabled} />\n    </>\n  )\n}\n\nexport function Repro1099UseQueryStates() {\n  const { shallow, history } = useOptions()\n  const [{ state }, setSearchParams] = useQueryStates(\n    {\n      state: parseAsString\n    },\n    {\n      shallow,\n      history,\n      urlKeys: {\n        state: 'test'\n      }\n    }\n  )\n  const [isNullDetectorEnabled, setIsNullDetectorEnabled] = useState(false)\n  useStateUpdateInEffect(state)\n  return (\n    <>\n      <button\n        onClick={() => {\n          setIsNullDetectorEnabled(true)\n          setSearchParams({ state: 'pass' })\n        }}\n      >\n        Test\n      </button>\n      <NullDetector state={state} enabled={isNullDetectorEnabled} />\n    </>\n  )\n}\n\nfunction useStateUpdateInEffect(trigger: unknown) {\n  const [, setCount] = useState(0)\n  useEffect(() => {\n    if (!trigger) {\n      return\n    }\n    console.log('[repro-1099] trigger other state update')\n    setCount(x => x + 1)\n  }, [trigger])\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-1293.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { assertLogCount, setupLogSpy } from '../playwright/log-spy'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testRepro1293 = defineTest('repro-1293', ({ path }) => {\n  it('should not re-render the source page with target search params when navigating to another page', async ({\n    page\n  }) => {\n    using logSpy = setupLogSpy(page)\n    // Navigate to Page A\n    await navigateTo(page, path + '/a')\n\n    // Navigate to Page B\n    logSpy.logs.length = 0 // Clear logs\n    await page.getByRole('link', { name: 'Go to Page B' }).click()\n    await expect(page).toHaveURL(\n      url =>\n        url.pathname.endsWith(`${path}/b`) && // support basePath\n        url.searchParams.get('count') === '1'\n    )\n    await assertLogCount(\n      logSpy,\n      'a: 1',\n      0,\n      'should not re-render Page A when navigating to Page B'\n    )\n\n    // Navigate back to Page A\n    logSpy.logs.length = 0 // Clear logs\n    await page.getByRole('button', { name: 'Go back' }).click()\n    await expect(page).toHaveURL(\n      url =>\n        url.pathname.endsWith(`${path}/a`) && // support basePath\n        url.searchParams.get('count') === null\n    )\n    await assertLogCount(\n      logSpy,\n      'b: 0',\n      0,\n      'should not re-render Page B when navigating back to Page A'\n    )\n  })\n\n  it('should not pre-render the target page with source search params when navigating to another page after a nuqs state update', async ({\n    page\n  }) => {\n    using logSpy = setupLogSpy(page)\n    // Navigate to Page A\n    await navigateTo(page, path + '/a')\n\n    // Increment count to 1\n    logSpy.logs.length = 0 // Clear logs\n    await page.getByRole('button', { name: 'Increment' }).click()\n    await expect(page).toHaveURL(\n      url =>\n        url.pathname.endsWith(`${path}/a`) && // support basePath\n        url.searchParams.get('count') === '1'\n    )\n\n    // Navigate to Page B\n    logSpy.logs.length = 0 // Clear logs\n    await page.getByRole('link', { name: 'Go to Page B' }).click()\n    await expect(page).toHaveURL(\n      url =>\n        url.pathname.endsWith(`${path}/b`) && // support basePath\n        url.searchParams.get('count') === '2'\n    )\n    await assertLogCount(\n      logSpy,\n      'a: 2',\n      0,\n      'should not re-render Page A when navigating to Page B after a nuqs state update'\n    )\n    await assertLogCount(\n      logSpy,\n      'b: 1',\n      0,\n      'should not pre-render Page B with pre-navigation search params'\n    )\n\n    // Increment on page B\n    logSpy.logs.length = 0 // Clear logs\n    await page.getByRole('button', { name: 'Increment' }).click()\n    await expect(page).toHaveURL(\n      url =>\n        url.pathname.endsWith(`${path}/b`) && // support basePath\n        url.searchParams.get('count') === '3'\n    )\n\n    // Go back to Page A\n    logSpy.logs.length = 0 // Clear logs\n    await page.getByRole('button', { name: 'Go back' }).click()\n    await expect(page).toHaveURL(\n      url =>\n        url.pathname.endsWith(`${path}/a`) && // support basePath\n        url.searchParams.get('count') === '1'\n    )\n    await assertLogCount(\n      logSpy,\n      'a: 3',\n      0,\n      'should not pre-render Page A when navigating back after a nuqs state update'\n    )\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-1293.tsx",
    "content": "'use client'\n\nimport { createSerializer, parseAsInteger, useQueryStates } from 'nuqs'\nimport { useLink } from '../components/link'\n\ntype Props = {\n  linkHref: string\n}\n\nconst searchParams = {\n  count: parseAsInteger.withDefault(0)\n}\nconst serialize = createSerializer(searchParams)\n\nexport function Repro1293PageA({ linkHref }: Props) {\n  const [{ count }, setSearchParams] = useQueryStates(searchParams)\n  console.log(`a: ${count}`)\n  const Link = useLink()\n  const href = serialize(linkHref, { count: count + 1 })\n  return (\n    <>\n      <button onClick={() => setSearchParams({ count: count + 1 })}>\n        Increment\n      </button>\n\n      <Link href={href}>Go to Page B</Link>\n    </>\n  )\n}\n\nexport function Repro1293PageB() {\n  const [{ count }, setSearchParams] = useQueryStates(searchParams)\n  console.log(`b: ${count}`)\n  return (\n    <>\n      <button onClick={() => setSearchParams({ count: count + 1 })}>\n        Increment\n      </button>\n      <button onClick={() => history.back()}>Go back</button>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-1365.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { assertLogCount, setupLogSpy } from '../playwright/log-spy'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testRepro1365 = defineTest('repro-1365', ({ path }) => {\n  it('should not cause extra effect fires when updating state in useEffect', async ({\n    page\n  }) => {\n    using logSpy = setupLogSpy(page)\n    await navigateTo(page, path)\n\n    // Wait for mount effect to settle:\n    // initial b=0, effect fires once on mount -> b=1\n    await assertLogCount(logSpy, 'effect', 1)\n    await expect(page.locator('#b')).toHaveText('1')\n\n    // Clear logs after mount settles\n    logSpy.logs.length = 0\n\n    // Click toggle: a changes false -> true\n    await page.getByRole('button', { name: 'toggle' }).click()\n\n    // Wait for b to update in the URL (proves the effect has run and flushed)\n    await expect(page).toHaveURL(\n      url =>\n        url.searchParams.get('a') === 'true' &&\n        url.searchParams.get('b') === '2'\n    )\n\n    // The effect should have fired exactly once for the a change.\n    // With the bug (flash to default), the effect fires extra times\n    // and b ends up higher than 2.\n    await expect(page.locator('#b')).toHaveText('2')\n    await assertLogCount(\n      logSpy,\n      'effect',\n      1,\n      'effect should fire exactly once when a changes'\n    )\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-1365.tsx",
    "content": "'use client'\n\nimport { parseAsBoolean, parseAsInteger, useQueryState } from 'nuqs'\nimport { useEffect, useRef } from 'react'\n\nexport function Repro1365() {\n  const [a, setA] = useQueryState('a', parseAsBoolean.withDefault(false))\n  const [b, setB] = useQueryState('b', parseAsInteger.withDefault(0))\n  const effectCount = useRef(0)\n\n  useEffect(() => {\n    effectCount.current++\n    console.log('effect')\n    void setB(prev => prev + 1)\n  }, [a])\n\n  return (\n    <>\n      <pre id=\"a\">{String(a)}</pre>\n      <pre id=\"b\">{String(b)}</pre>\n      <pre id=\"effect-count\">{effectCount.current}</pre>\n      <button onClick={() => void setA(prev => !prev)}>toggle</button>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-359.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testRepro359 = defineTest('repro-359', ({ path }) => {\n  it('should follow cross-link updates & conditional mounting', async ({\n    page\n  }) => {\n    await navigateTo(page, path)\n\n    await expect(page).toHaveURL(url => url.search === '')\n    await expect(page.locator('#nuqs-param')).toHaveText('null')\n    await expect(page.locator('#nuqs-component')).toHaveText('')\n    await expect(page.locator('#nuqss-param')).toHaveText('null')\n    await expect(page.locator('#nuqss-component')).toHaveText('')\n\n    await page.getByText('Component 1 (nuqs)').click()\n    await expect(page).toHaveURL(url => url.search === '?param=comp1&component=comp1')\n    await expect(page.locator('#comp1')).toHaveText('comp1')\n    await expect(page.locator('#comp2')).not.toBeAttached()\n    await expect(page.locator('#nuqs-param')).toHaveText('comp1')\n    await expect(page.locator('#nuqs-component')).toHaveText('comp1')\n    await expect(page.locator('#nuqss-param')).toHaveText('comp1')\n    await expect(page.locator('#nuqss-component')).toHaveText('comp1')\n\n    await page.getByText('Component 2 (nuqs)').click()\n    await expect(page).toHaveURL(url => url.search === '?param=comp2&component=comp2')\n    await expect(page.locator('#comp1')).not.toBeAttached()\n    await expect(page.locator('#comp2')).toHaveText('comp2')\n    await expect(page.locator('#nuqs-param')).toHaveText('comp2')\n    await expect(page.locator('#nuqs-component')).toHaveText('comp2')\n    await expect(page.locator('#nuqss-param')).toHaveText('comp2')\n    await expect(page.locator('#nuqss-component')).toHaveText('comp2')\n\n    await page.getByText('Component 1 (nuq+)').click()\n    await expect(page).toHaveURL(url => url.search === '?param=comp1&component=comp1')\n    await expect(page.locator('#comp1')).toHaveText('comp1')\n    await expect(page.locator('#comp2')).not.toBeAttached()\n    await expect(page.locator('#nuqs-param')).toHaveText('comp1')\n    await expect(page.locator('#nuqs-component')).toHaveText('comp1')\n    await expect(page.locator('#nuqss-param')).toHaveText('comp1')\n    await expect(page.locator('#nuqss-component')).toHaveText('comp1')\n\n    await page.getByText('Component 2 (nuq+)').click()\n    await expect(page).toHaveURL(url => url.search === '?param=comp2&component=comp2')\n    await expect(page.locator('#comp1')).not.toBeAttached()\n    await expect(page.locator('#comp2')).toHaveText('comp2')\n    await expect(page.locator('#nuqs-param')).toHaveText('comp2')\n    await expect(page.locator('#nuqs-component')).toHaveText('comp2')\n    await expect(page.locator('#nuqss-param')).toHaveText('comp2')\n    await expect(page.locator('#nuqss-component')).toHaveText('comp2')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-359.tsx",
    "content": "// https://github.com/47ng/nuqs/issues/359\n\n'use client'\n\nimport {\n  createSerializer,\n  parseAsString,\n  parseAsStringLiteral,\n  useQueryState,\n  useQueryStates\n} from 'nuqs'\nimport { useLink } from '../components/link'\n\nconst paramParser = parseAsString.withDefault('null')\nconst components = ['comp1', 'comp2'] as const\nconst componentParser = parseAsStringLiteral(components)\nconst searchParams = {\n  param: paramParser,\n  component: componentParser\n}\nconst href = createSerializer(searchParams)\n\nconst Component = (props: React.ComponentProps<'span'>) => {\n  const [param] = useQueryState('param', paramParser)\n  return <span {...props}>{param}</span>\n}\n\nexport function Repro359() {\n  const Link = useLink()\n  const [param, setParam] = useQueryState('param', paramParser)\n  const [component, seComponent] = useQueryState('component', componentParser)\n  const [multiple, setMultiple] = useQueryStates(searchParams)\n  return (\n    <>\n      <div>\n        {component === 'comp1' && <Component id=\"comp1\" />}\n        {component === 'comp2' && <Component id=\"comp2\" />}\n      </div>\n      <div>\n        <span id=\"nuqs-param\">{param}</span>\n        <span id=\"nuqs-component\">{component}</span>\n        <span id=\"nuqss-param\">{multiple.param}</span>\n        <span id=\"nuqss-component\">{multiple.component}</span>\n      </div>\n      <div>\n        <button\n          onClick={() => {\n            setParam('comp1')\n            seComponent('comp1')\n          }}\n        >\n          Component 1 (nuqs)\n        </button>\n        <button\n          onClick={() => {\n            setParam('comp2')\n            seComponent('comp2')\n          }}\n        >\n          Component 2 (nuqs)\n        </button>\n        <br />\n        <button\n          onClick={() => {\n            setMultiple({\n              param: 'comp1',\n              component: 'comp1'\n            })\n          }}\n        >\n          Component 1 (nuq+)\n        </button>\n        <button\n          onClick={() => {\n            setMultiple({\n              param: 'comp2',\n              component: 'comp2'\n            })\n          }}\n        >\n          Component 2 (nuq+)\n        </button>\n      </div>\n      <nav>\n        <Link href={href({ component: 'comp1', param: 'comp1' })}>Comp 1</Link>\n        <Link href={href({ component: 'comp2', param: 'comp2' })}>Comp 2</Link>\n      </nav>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-982.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testRepro982 = defineTest('repro-982', ({ path }) => {\n  it('keeps the first search param after an update when multiple ones occur', async ({\n    page\n  }) => {\n    await navigateTo(page, path, '?test=pass&test=fail')\n    await expect(page.locator('#client-state')).toHaveText('pass')\n    await page.locator('button').click()\n    await expect(page).toHaveURL(url => url.search === '?test=pass&test=fail&other=x')\n    await expect(page.locator('#client-state')).toHaveText('pass')\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/repro-982.tsx",
    "content": "'use client'\n\nimport { useQueryState } from 'nuqs'\nimport { Display } from '../components/display'\n\nexport function Repro982() {\n  const [test] = useQueryState('test')\n  const [, setOther] = useQueryState('other')\n  return (\n    <>\n      <button onClick={() => setOther('x')}>Test</button>\n      <Display environment=\"client\" state={test} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/routing.defs.ts",
    "content": "import {\n  createSerializer,\n  parseAsBoolean,\n  parseAsString,\n  parseAsStringLiteral\n} from 'nuqs'\n\nexport const routingSearchParams = {\n  state: parseAsString,\n  shallow: parseAsBoolean.withDefault(true),\n  method: parseAsStringLiteral(['push', 'replace']).withDefault('replace')\n}\nexport const routingUrlKeys = {\n  state: 'test',\n  method: 'router'\n}\n\nexport const getRoutingUrl = createSerializer(routingSearchParams, {\n  urlKeys: routingUrlKeys\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/routing.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\nimport { getRoutingUrl } from './routing.defs'\n\ntype TestRoutingOptions = TestConfig & {\n  shallowOptions?: boolean[]\n  methodOptions?: ('replace' | 'push')[]\n}\n\nexport function testRouting({\n  shallowOptions = [true, false],\n  methodOptions = ['replace', 'push'],\n  ...options\n}: TestRoutingOptions) {\n  const factory = defineTest('Routing', ({ path }) => {\n    for (const shallow of shallowOptions) {\n      for (const method of methodOptions) {\n        it(`picks up state from a router call pointing to the same page - router.${method}({ shallow: ${shallow} })`, async ({\n          page\n        }) => {\n          await navigateTo(page, getRoutingUrl(path, { shallow, method }))\n          await expect(page.locator('#state').first()).toBeEmpty()\n          await page.locator('button').click()\n          await expect(page.locator('#state').first()).toHaveText('pass')\n          if (method === 'push') {\n            await page.goBack()\n            await expect(page.locator('#state').first()).toBeEmpty()\n          }\n        })\n\n        it(`picks up state from a router issued from another page - router.${method}({ shallow: ${shallow} })`, async ({\n          page\n        }) => {\n          await navigateTo(\n            page,\n            getRoutingUrl(path + '/other', { shallow, method })\n          )\n          await expect(page.locator('#state').first()).toBeEmpty()\n          await page.locator('button').click()\n          await expect(page.locator('#state').first()).toHaveText('pass')\n          if (method === 'push') {\n            await page.goBack()\n            await expect(page.locator('#state').first()).toBeEmpty()\n          }\n        })\n      }\n    }\n  })\n  return factory(options)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/routing.tsx",
    "content": "'use client'\n\nimport {\n  parseAsBoolean,\n  parseAsStringLiteral,\n  useQueryState,\n  useQueryStates\n} from 'nuqs'\nimport { useRouter } from '../components/router'\nimport {\n  getRoutingUrl,\n  routingSearchParams,\n  routingUrlKeys\n} from './routing.defs'\n\ntype Props = {\n  path: string\n}\n\nexport function RoutingUseQueryState({ path }: Props) {\n  const router = useRouter()\n  const [state] = useQueryState('test')\n  const [shallow] = useQueryState('shallow', parseAsBoolean.withDefault(true))\n  const [method] = useQueryState(\n    'router',\n    parseAsStringLiteral(['push', 'replace']).withDefault('replace')\n  )\n  const test = () => {\n    const url = getRoutingUrl(path, { state: 'pass' })\n    const routerMethod = router[method].bind(router)\n    routerMethod(url, { shallow })\n  }\n  return (\n    <>\n      <button onClick={test}>Test</button>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n\nexport function RoutingUseQueryStates({ path }: Props) {\n  const router = useRouter()\n  const [{ state, shallow, method }] = useQueryStates(routingSearchParams, {\n    urlKeys: routingUrlKeys\n  })\n  const test = () => {\n    const url = getRoutingUrl(path, { state: 'pass' })\n    const routerMethod = router[method].bind(router)\n    routerMethod(url, { shallow })\n  }\n  return (\n    <>\n      <button onClick={test}>Test</button>\n      <pre id=\"state\">{state}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/scroll.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest } from '../define-test'\nimport { navigateTo } from '../playwright/navigate'\n\nexport const testScroll = defineTest('scroll', ({ path }) => {\n  it('does not scroll to the top of the page by default (scroll: false)', async ({\n    page\n  }) => {\n    await navigateTo(page, path, '?scroll=false')\n    await expect(page.locator('#not-at-the-top').first()).toBeVisible()\n    await page.locator('button').click()\n    await expect(page.locator('#not-at-the-top').first()).toBeVisible()\n  })\n\n  it('scrolls to the top of the page when setting scroll: true', async ({\n    page\n  }) => {\n    await navigateTo(page, path, '?scroll=true')\n    await expect(page.locator('#not-at-the-top').first()).toBeVisible()\n    await page.locator('button').click()\n    await expect(page.locator('#at-the-top').first()).toBeVisible()\n  })\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/scroll.tsx",
    "content": "'use client'\n\nimport { parseAsBoolean, useQueryState } from 'nuqs'\nimport { useEffect, useState } from 'react'\n\nexport function Scroll() {\n  return (\n    <>\n      <ScrollDetector />\n      <div style={{ height: '200vh' }} />\n      <ScrollAction />\n    </>\n  )\n}\n\nfunction ScrollDetector() {\n  const [atTheTop, setAtTheTop] = useState(false)\n\n  useEffect(() => {\n    const controller = new AbortController()\n    window.addEventListener('scroll', () => setAtTheTop(window.scrollY === 0), {\n      signal: controller.signal\n    })\n    return () => controller.abort()\n  }, [])\n\n  return (\n    <span\n      id={atTheTop ? 'at-the-top' : 'not-at-the-top'}\n      style={{ position: 'fixed', top: 8 }}\n    >\n      {atTheTop ? null : 'not '}at the top\n    </span>\n  )\n}\n\nfunction ScrollAction() {\n  const [scroll] = useQueryState('scroll', parseAsBoolean.withDefault(false))\n  const [, setState] = useQueryState('test', {\n    scroll\n  })\n\n  useEffect(() => {\n    document.getElementById('scroll-to-me')?.scrollIntoView()\n  }, [])\n\n  return (\n    <button\n      id=\"scroll-to-me\"\n      onClick={() => setState('pass')}\n      style={{ marginInline: 'auto', display: 'block' }}\n    >\n      Test\n    </button>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/shallow.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { getOptionsUrl } from '../lib/options'\nimport { navigateTo } from '../playwright/navigate'\n\ntype TestShallowOptions = TestConfig & {\n  supportsSSR?: boolean\n  shallowOptions?: boolean[]\n  historyOptions?: ('replace' | 'push')[]\n}\n\nexport function testShallow({\n  supportsSSR = true,\n  shallowOptions = [true, false],\n  historyOptions = ['replace', 'push'],\n  ...options\n}: TestShallowOptions) {\n  const factory = defineTest('Shallow', ({ path }) => {\n    for (const shallow of shallowOptions) {\n      for (const history of historyOptions) {\n        it(`Updates with ({ shallow: ${shallow}, history: ${history} })`, async ({\n          page\n        }) => {\n          await navigateTo(page, getOptionsUrl(path, { shallow, history }))\n          await expect(page.locator('#client-state')).toBeEmpty()\n          if (supportsSSR) {\n            await expect(page.locator('#server-state')).toBeEmpty()\n          }\n          await page.locator('button').click()\n          await expect(page.locator('#client-state')).toHaveText('pass')\n          if (supportsSSR) {\n            if (shallow === false) {\n              await expect(page.locator('#server-state')).toHaveText('pass')\n            } else {\n              await expect(page.locator('#server-state')).toBeEmpty()\n            }\n          }\n          if (history !== 'push') {\n            return\n          }\n          await expect(page).toHaveURL(\n            url => url.searchParams.get('test') === 'pass'\n          )\n          await page.goBack()\n          await expect(page.locator('#client-state')).toBeEmpty()\n          if (supportsSSR) {\n            await expect(page.locator('#server-state')).toBeEmpty()\n          }\n        })\n      }\n    }\n  })\n  if (supportsSSR) {\n    options.description = 'SSR'\n  }\n  return factory(options)\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/shallow.tsx",
    "content": "'use client'\n\nimport { parseAsString, useQueryState, useQueryStates } from 'nuqs'\nimport { Display } from '../components/display'\nimport { optionsSearchParams } from '../lib/options'\n\nexport function ShallowUseQueryState() {\n  const [{ shallow, history }] = useQueryStates(optionsSearchParams)\n  const [state, setState] = useQueryState('test', { shallow, history })\n  return (\n    <>\n      <button onClick={() => setState('pass')}>Test</button>\n      <Display environment=\"client\" state={state} />\n    </>\n  )\n}\n\nexport function ShallowUseQueryStates() {\n  const [{ shallow, history }] = useQueryStates(optionsSearchParams)\n  const [{ state }, setSearchParams] = useQueryStates(\n    {\n      state: parseAsString.withOptions({ shallow, history })\n    },\n    {\n      urlKeys: {\n        state: 'test'\n      }\n    }\n  )\n  return (\n    <>\n      <button onClick={() => setSearchParams({ state: 'pass' })}>Test</button>\n      <Display environment=\"client\" state={state} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/stitching.defs.ts",
    "content": "import {\n  createSerializer,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsStringLiteral\n} from 'nuqs/server'\n\nconst parser = parseAsInteger.withDefault(0)\n\nexport const searchParams = {\n  a: parser,\n  b: parser,\n  c: parser\n}\n\nexport const optionsSearchParams = {\n  hook: parseAsStringLiteral([\n    'useQueryState',\n    'useQueryStates'\n  ]).withDefault('useQueryState'),\n  shallow: parseAsBoolean.withDefault(true),\n  history: parseAsStringLiteral(['push', 'replace']).withDefault(\n    'replace'\n  )\n}\n\nexport const getUrl = createSerializer(optionsSearchParams, {\n  clearOnDefault: false\n})\n"
  },
  {
    "path": "packages/e2e/shared/specs/stitching.spec.ts",
    "content": "import { expect, test as it } from '@playwright/test'\nimport { defineTest, type TestConfig } from '../define-test'\nimport { expectSearch } from '../playwright/expect-url'\nimport { navigateTo } from '../playwright/navigate'\nimport { getUrl } from './stitching.defs'\n\ntype Config = TestConfig & {\n  enableShallowFalse?: boolean\n}\n\nexport function testStitching({\n  enableShallowFalse = true,\n  ...config\n}: Config) {\n  const hooks = ['useQueryState', 'useQueryStates'] as const\n  const shallows = enableShallowFalse ? [true, false] : [true]\n  const histories = ['replace', 'push'] as const\n  for (const hook of hooks) {\n    for (const shallow of shallows) {\n      for (const history of histories) {\n        const test = defineTest(\n          {\n            label: 'Stitching',\n            variants: `shallow: ${shallow}, history: ${history}`\n          },\n          ({ path }) => {\n            it('should update the state optimistically and sequence the URL updates', async ({\n              page\n            }) => {\n              await navigateTo(page, getUrl(path, { hook, shallow, history }))\n              await page.locator('#same-tick').click()\n              await expect(page.locator('#client-state')).toHaveText('1,1,1')\n              await expectSearch(page, { a: '1' })\n              await expectSearch(page, { a: '1', b: '1' })\n              await expectSearch(page, { a: '1', b: '1', c: '1' })\n              await page.locator('#same-tick').click()\n              await expect(page.locator('#client-state')).toHaveText('2,2,2')\n              await expectSearch(page, { a: '2', b: '1', c: '1' })\n              await expectSearch(page, { a: '2', b: '2', c: '1' })\n              await expectSearch(page, { a: '2', b: '2', c: '2' })\n              await page.locator('#same-tick').click()\n              await expect(page.locator('#client-state')).toHaveText('3,3,3')\n              await expectSearch(page, { a: '3', b: '2', c: '2' })\n              // Don't wait till completion, queue an update before the debounced resolve\n              await page.locator('#same-tick').click()\n              await expect(page.locator('#client-state')).toHaveText('4,4,4')\n              await expectSearch(page, { a: '4', b: '2', c: '2' })\n              await expectSearch(page, { a: '4', b: '4', c: '2' })\n              await expectSearch(page, { a: '4', b: '4', c: '4' })\n            })\n\n            it('should sequence updates when staggered', async ({ page }) => {\n              await navigateTo(page, getUrl(path, { hook, shallow, history }))\n              await page.locator('#staggered').click()\n              await expect(page.locator('#client-state')).toHaveText('1,1,1')\n              await expectSearch(page, { a: '1' })\n              await expectSearch(page, { a: '1', b: '1' })\n              await expectSearch(page, { a: '1', b: '1', c: '1' })\n              await page.locator('#staggered').click()\n              await expect(page.locator('#client-state')).toHaveText('2,2,2')\n              await expectSearch(page, { a: '2', b: '1', c: '1' })\n              await expectSearch(page, { a: '2', b: '2', c: '1' })\n              await expectSearch(page, { a: '2', b: '2', c: '2' })\n              await page.locator('#staggered').click()\n              await expect(page.locator('#client-state')).toHaveText('3,3,3')\n              await expectSearch(page, { a: '3', b: '2', c: '2' })\n              // Don't wait till completion, queue an update before the debounced resolve\n              await page.locator('#staggered').click()\n              await expect(page.locator('#client-state')).toHaveText('4,4,4')\n              await expectSearch(page, { a: '4', b: '2', c: '2' })\n              await expectSearch(page, { a: '4', b: '4', c: '2' })\n              await expectSearch(page, { a: '4', b: '4', c: '4' })\n            })\n          }\n        )\n        test({ ...config, hook })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/shared/specs/stitching.tsx",
    "content": "'use client'\n\nimport { debounce, useQueryState, useQueryStates } from 'nuqs'\nimport { Display } from '../components/display'\nimport { optionsSearchParams, searchParams } from './stitching.defs'\n\nexport function Stitching() {\n  const [{ hook }] = useQueryStates(optionsSearchParams)\n  if (hook === 'useQueryState') {\n    return <StitchingUseQueryState />\n  }\n  if (hook === 'useQueryStates') {\n    return <StitchingUseQueryStates />\n  }\n  return <>Invalid hook</>\n}\n\nfunction StitchingUseQueryState() {\n  const [{ history, shallow }] = useQueryStates(optionsSearchParams)\n  const [a, setA] = useQueryState(\n    'a',\n    searchParams.a.withOptions({ history, shallow })\n  )\n  const [b, setB] = useQueryState(\n    'b',\n    searchParams.b.withOptions({ history, shallow })\n  )\n  const [c, setC] = useQueryState(\n    'c',\n    searchParams.c.withOptions({ history, shallow })\n  )\n\n  const testOnSameTick = () => {\n    setA(x => x + 1)\n    setB(x => x + 1, { limitUrlUpdates: debounce(250) })\n    setC(x => x + 1, { limitUrlUpdates: debounce(500) })\n  }\n  const testStaggered = () => {\n    setC(x => x + 1, { limitUrlUpdates: debounce(500) })\n    setTimeout(() => {\n      setB(x => x + 1, { limitUrlUpdates: debounce(250) })\n      setTimeout(() => {\n        setA(x => x + 1)\n      }, 0)\n    }, 0)\n  }\n\n  return (\n    <>\n      <button id=\"same-tick\" onClick={testOnSameTick}>\n        Test on same tick\n      </button>\n      <button id=\"staggered\" onClick={testStaggered}>\n        Test staggered\n      </button>\n      <Display environment=\"client\" state={[a, b, c].join(',')} />\n    </>\n  )\n}\n\nfunction StitchingUseQueryStates() {\n  const [{ history, shallow }] = useQueryStates(optionsSearchParams)\n  const [{ a, b, c }, setSearchParams] = useQueryStates(searchParams, {\n    history,\n    shallow\n  })\n\n  const testOnSameTick = () => {\n    setSearchParams(old => ({ a: old.a + 1 }))\n    setSearchParams(old => ({ b: old.b + 1 }), {\n      limitUrlUpdates: debounce(250)\n    })\n    setSearchParams(old => ({ c: old.c + 1 }), {\n      limitUrlUpdates: debounce(500)\n    })\n  }\n  const testStaggered = () => {\n    setSearchParams(old => ({ c: old.c + 1 }), {\n      limitUrlUpdates: debounce(500)\n    })\n    setTimeout(() => {\n      setSearchParams(old => ({ b: old.b + 1 }), {\n        limitUrlUpdates: debounce(250)\n      })\n      setTimeout(() => {\n        setSearchParams(old => ({ a: old.a + 1 }))\n      }, 0)\n    }, 0)\n  }\n\n  return (\n    <>\n      <button id=\"same-tick\" onClick={testOnSameTick}>\n        Test on same tick\n      </button>\n      <button id=\"staggered\" onClick={testStaggered}>\n        Test staggered\n      </button>\n      <Display environment=\"client\" state={[a, b, c].join(',')} />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/shared/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    // Type checking\n    \"strict\": true,\n    \"alwaysStrict\": false, // Don't emit \"use strict\" to avoid conflicts with \"use client\"\n    // Modules\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    // Language & Environment\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    // Emit\n    \"noEmit\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"jsx\": \"preserve\",\n    // Interop\n    \"allowJs\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    // Misc\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/.cta.json",
    "content": "{\n  \"framework\": \"react\",\n  \"projectName\": \"tanstack-router\",\n  \"mode\": \"file-router\",\n  \"typescript\": true,\n  \"tailwind\": false,\n  \"packageManager\": \"pnpm\",\n  \"toolchain\": \"none\",\n  \"variableValues\": {},\n  \"git\": true,\n  \"version\": 1,\n  \"existingAddOns\": []\n}"
  },
  {
    "path": "packages/e2e/tanstack-router/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\nsrc/routeTree.gen.ts\n.tanstack/\n\n# Playwright\n.playwright/\n"
  },
  {
    "path": "packages/e2e/tanstack-router/README.md",
    "content": "Welcome to your new TanStack app! \n\n# Getting Started\n\nTo run this application:\n\n```bash\npnpm install\npnpm start  \n```\n\n# Building For Production\n\nTo build this application for production:\n\n```bash\npnpm build\n```\n\n## Testing\n\nThis project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:\n\n```bash\npnpm test\n```\n\n## Styling\n\nThis project uses CSS for styling.\n\n\n\n\n## Routing\nThis project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.\n\n### Adding A Route\n\nTo add a new route to your application just add another a new file in the `./src/routes` directory.\n\nTanStack will automatically generate the content of the route file for you.\n\nNow that you have two routes you can use a `Link` component to navigate between them.\n\n### Adding Links\n\nTo use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.\n\n```tsx\nimport { Link } from \"@tanstack/react-router\";\n```\n\nThen anywhere in your JSX you can use it like so:\n\n```tsx\n<Link to=\"/about\">About</Link>\n```\n\nThis will create a link that will navigate to the `/about` route.\n\nMore information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).\n\n### Using A Layout\n\nIn the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `<Outlet />` component.\n\nHere is an example layout that includes a header:\n\n```tsx\nimport { Outlet, createRootRoute } from '@tanstack/react-router'\nimport { TanStackRouterDevtools } from '@tanstack/react-router-devtools'\n\nimport { Link } from \"@tanstack/react-router\";\n\nexport const Route = createRootRoute({\n  component: () => (\n    <>\n      <header>\n        <nav>\n          <Link to=\"/\">Home</Link>\n          <Link to=\"/about\">About</Link>\n        </nav>\n      </header>\n      <Outlet />\n      <TanStackRouterDevtools />\n    </>\n  ),\n})\n```\n\nThe `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.\n\nMore information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).\n\n\n## Data Fetching\n\nThere are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.\n\nFor example:\n\n```tsx\nconst peopleRoute = createRoute({\n  getParentRoute: () => rootRoute,\n  path: \"/people\",\n  loader: async () => {\n    const response = await fetch(\"https://swapi.dev/api/people\");\n    return response.json() as Promise<{\n      results: {\n        name: string;\n      }[];\n    }>;\n  },\n  component: () => {\n    const data = peopleRoute.useLoaderData();\n    return (\n      <ul>\n        {data.results.map((person) => (\n          <li key={person.name}>{person.name}</li>\n        ))}\n      </ul>\n    );\n  },\n});\n```\n\nLoaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).\n\n### React-Query\n\nReact-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.\n\nFirst add your dependencies:\n\n```bash\npnpm add @tanstack/react-query @tanstack/react-query-devtools\n```\n\nNext we'll need to create a query client and provider. We recommend putting those in `main.tsx`.\n\n```tsx\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\n\n// ...\n\nconst queryClient = new QueryClient();\n\n// ...\n\nif (!rootElement.innerHTML) {\n  const root = ReactDOM.createRoot(rootElement);\n\n  root.render(\n    <QueryClientProvider client={queryClient}>\n      <RouterProvider router={router} />\n    </QueryClientProvider>\n  );\n}\n```\n\nYou can also add TanStack Query Devtools to the root route (optional).\n\n```tsx\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n\nconst rootRoute = createRootRoute({\n  component: () => (\n    <>\n      <Outlet />\n      <ReactQueryDevtools buttonPosition=\"top-right\" />\n      <TanStackRouterDevtools />\n    </>\n  ),\n});\n```\n\nNow you can use `useQuery` to fetch your data.\n\n```tsx\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport \"./App.css\";\n\nfunction App() {\n  const { data } = useQuery({\n    queryKey: [\"people\"],\n    queryFn: () =>\n      fetch(\"https://swapi.dev/api/people\")\n        .then((res) => res.json())\n        .then((data) => data.results as { name: string }[]),\n    initialData: [],\n  });\n\n  return (\n    <div>\n      <ul>\n        {data.map((person) => (\n          <li key={person.name}>{person.name}</li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\nexport default App;\n```\n\nYou can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview).\n\n## State Management\n\nAnother common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project.\n\nFirst you need to add TanStack Store as a dependency:\n\n```bash\npnpm add @tanstack/store\n```\n\nNow let's create a simple counter in the `src/App.tsx` file as a demonstration.\n\n```tsx\nimport { useStore } from \"@tanstack/react-store\";\nimport { Store } from \"@tanstack/store\";\nimport \"./App.css\";\n\nconst countStore = new Store(0);\n\nfunction App() {\n  const count = useStore(countStore);\n  return (\n    <div>\n      <button onClick={() => countStore.setState((n) => n + 1)}>\n        Increment - {count}\n      </button>\n    </div>\n  );\n}\n\nexport default App;\n```\n\nOne of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates.\n\nLet's check this out by doubling the count using derived state.\n\n```tsx\nimport { useStore } from \"@tanstack/react-store\";\nimport { Store, Derived } from \"@tanstack/store\";\nimport \"./App.css\";\n\nconst countStore = new Store(0);\n\nconst doubledStore = new Derived({\n  fn: () => countStore.state * 2,\n  deps: [countStore],\n});\ndoubledStore.mount();\n\nfunction App() {\n  const count = useStore(countStore);\n  const doubledCount = useStore(doubledStore);\n\n  return (\n    <div>\n      <button onClick={() => countStore.setState((n) => n + 1)}>\n        Increment - {count}\n      </button>\n      <div>Doubled - {doubledCount}</div>\n    </div>\n  );\n}\n\nexport default App;\n```\n\nWe use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating.\n\nOnce we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook.\n\nYou can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).\n\n# Demo files\n\nFiles prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.\n\n# Learn More\n\nYou can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).\n"
  },
  {
    "path": "packages/e2e/tanstack-router/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <title>e2e-tanstack-router</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/e2e/tanstack-router/package.json",
    "content": "{\n  \"name\": \"e2e-tanstack-router\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --port 3004\",\n    \"build\": \"vite build\",\n    \"start\": \"vite preview --port 3004\",\n    \"pretest\": \"playwright install chromium\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:types\": \"tsc\",\n    \"test:playwright\": \"playwright test --project=chromium\",\n    \"kill-server\": \"lsof -ti:3004 | xargs kill -9 || true\"\n  },\n  \"dependencies\": {\n    \"@tanstack/react-router\": \"1.158.0\",\n    \"@tanstack/react-router-devtools\": \"1.158.0\",\n    \"@tanstack/router-plugin\": \"1.158.0\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"catalog:e2e\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@vitejs/plugin-react\": \"^5.1.3\",\n    \"cross-env\": \"^10.1.0\",\n    \"e2e-shared\": \"workspace:*\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\"\n  }\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/playwright.config.ts",
    "content": "import { configurePlaywright } from 'e2e-shared/playwright.config.ts'\n\nexport default configurePlaywright({\n  startCommand: 'pnpm run start',\n  port: 3004\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/basic-io.spec.ts",
    "content": "import { testBasicIO } from 'e2e-shared/specs/basic-io.spec.ts'\n\ntestBasicIO({\n  hook: 'useQueryState',\n  path: '/basic-io/useQueryState'\n})\n\ntestBasicIO({\n  hook: 'useQueryStates',\n  path: '/basic-io/useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/conditional-rendering.spec.ts",
    "content": "import { testConditionalRendering } from 'e2e-shared/specs/conditional-rendering.spec.ts'\n\ntestConditionalRendering({\n  hook: 'useQueryState',\n  path: '/conditional-rendering/useQueryState'\n})\n\ntestConditionalRendering({\n  hook: 'useQueryStates',\n  path: '/conditional-rendering/useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/form.spec.ts",
    "content": "import { testForm } from 'e2e-shared/specs/form.spec.ts'\n\ntestForm({\n  hook: 'useQueryState',\n  path: '/form/useQueryState'\n})\n\ntestForm({\n  hook: 'useQueryStates',\n  path: '/form/useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/hash-preservation.spec.ts",
    "content": "import { testHashPreservation } from 'e2e-shared/specs/hash-preservation.spec.ts'\n\ntestHashPreservation({\n  path: '/hash-preservation'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/json.spec.ts",
    "content": "import { testJson } from 'e2e-shared/specs/json.spec.ts'\n\ntestJson({\n  path: '/json'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/key-isolation.spec.ts",
    "content": "import { testKeyIsolation } from 'e2e-shared/specs/key-isolation.spec.ts'\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestKeyIsolation({\n  path: '/key-isolation/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/linking.spec.ts",
    "content": "import { testLinking } from 'e2e-shared/specs/linking.spec.ts'\n\ntestLinking({\n  hook: 'useQueryState',\n  path: '/linking/useQueryState'\n})\n\ntestLinking({\n  hook: 'useQueryStates',\n  path: '/linking/useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/native-array.spec.ts",
    "content": "import { testNativeArray } from 'e2e-shared/specs/native-array.spec.ts'\n\ntestNativeArray({\n  path: '/native-array'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/pretty-urls.spec.ts",
    "content": "import { testPrettyUrls } from 'e2e-shared/specs/pretty-urls.spec.ts'\n\ntestPrettyUrls({\n  path: '/pretty-urls'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/push.spec.ts",
    "content": "import { testPush } from 'e2e-shared/specs/push.spec.ts'\n\ntestPush({\n  path: '/push/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestPush({\n  path: '/push/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/referential-stability.spec.ts",
    "content": "import { testReferentialStability } from 'e2e-shared/specs/referential-stability.spec.ts'\n\ntestReferentialStability({\n  hook: 'useQueryState',\n  path: '/referential-stability/useQueryState'\n})\n\ntestReferentialStability({\n  hook: 'useQueryStates',\n  path: '/referential-stability/useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/repro-1099.spec.ts",
    "content": "import { testRepro1099 } from 'e2e-shared/specs/repro-1099.spec.ts'\n\ntestRepro1099({\n  path: '/repro-1099/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRepro1099({\n  path: '/repro-1099/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/repro-1365.spec.ts",
    "content": "import { testRepro1365 } from 'e2e-shared/specs/repro-1365.spec.ts'\n\ntestRepro1365({\n  path: '/repro-1365'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/routing.spec.ts",
    "content": "import { testRouting } from 'e2e-shared/specs/routing.spec.ts'\n\ntestRouting({\n  path: '/routing/useQueryState',\n  hook: 'useQueryState'\n})\n\ntestRouting({\n  path: '/routing/useQueryStates',\n  hook: 'useQueryStates'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/scroll.spec.ts",
    "content": "import { testScroll } from 'e2e-shared/specs/scroll.spec.ts'\n\ntestScroll({\n  path: '/scroll'\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared/shallow.spec.ts",
    "content": "import { testShallow } from 'e2e-shared/specs/shallow.spec.ts'\n\ntestShallow({\n  path: '/shallow/useQueryState',\n  hook: 'useQueryState',\n  supportsSSR: false\n})\n\ntestShallow({\n  path: '/shallow/useQueryStates',\n  hook: 'useQueryStates',\n  supportsSSR: false\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/shared.spec.ts",
    "content": "import { runSharedTests } from 'e2e-shared/shared.spec.ts'\n\nrunSharedTests()\n"
  },
  {
    "path": "packages/e2e/tanstack-router/specs/trailing-slash.spec.ts",
    "content": "import { expect, test } from '@playwright/test'\nimport { navigateTo } from 'e2e-shared/playwright/navigate.ts'\n\ntest('does not append a trailing slash', async ({ page }) => {\n  await navigateTo(page, '/trailing-slash')\n  await expect(page).toHaveURL(url => url.pathname === '/trailing-slash')\n  await page.getByText('Set declared').click()\n  await expect(page).toHaveURL(url => url.pathname === '/trailing-slash')\n  await page.getByText('Clear').click()\n  await expect(page).toHaveURL(url => url.pathname === '/trailing-slash')\n  await page.getByText('Set undeclared').click()\n  await expect(page).toHaveURL(url => url.pathname === '/trailing-slash')\n  await page.getByText('Clear').click()\n  await expect(page).toHaveURL(url => url.pathname === '/trailing-slash')\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/layout.tsx",
    "content": "import { Link as TanStackLink, useNavigate } from '@tanstack/react-router'\nimport { HydrationMarker } from 'e2e-shared/components/hydration-marker'\nimport { LinkProvider, type LinkProps } from 'e2e-shared/components/link'\nimport { RouterProvider, type Router } from 'e2e-shared/components/router'\nimport type { ReactNode } from 'react'\n\nfunction Link({ href, ...props }: LinkProps) {\n  return <TanStackLink to={href} {...props} />\n}\n\nfunction useRouter(): Router {\n  const navigate = useNavigate()\n  return {\n    replace(url) {\n      navigate({\n        to: url,\n        replace: true\n      })\n    },\n    push(url) {\n      navigate({\n        to: url,\n        replace: false\n      })\n    }\n  }\n}\n\nexport function RootLayout({ children }: { children: ReactNode }) {\n  return (\n    <>\n      <HydrationMarker />\n      <LinkProvider Link={Link}>\n        <RouterProvider useRouter={useRouter}>{children}</RouterProvider>\n      </LinkProvider>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/main.tsx",
    "content": "import { RouterProvider, createRouter } from '@tanstack/react-router'\nimport { StrictMode } from 'react'\nimport ReactDOM from 'react-dom/client'\n\n// Import the generated route tree\nimport { routeTree } from './routeTree.gen'\n\n// Create a new router instance\nconst router = createRouter({\n  routeTree,\n  context: {},\n  defaultPreload: 'intent',\n  scrollRestoration: true,\n  defaultStructuralSharing: true,\n  defaultPreloadStaleTime: 0\n})\n\n// Register the router instance for type safety\ndeclare module '@tanstack/react-router' {\n  interface Register {\n    router: typeof router\n  }\n}\n\n// Render the app\nconst rootElement = document.getElementById('app')\nif (rootElement && !rootElement.innerHTML) {\n  const root = ReactDOM.createRoot(rootElement)\n  root.render(\n    <StrictMode>\n      <RouterProvider router={router} />\n    </StrictMode>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/__root.tsx",
    "content": "import { RootLayout } from '@/layout'\nimport { Outlet, createRootRoute } from '@tanstack/react-router'\n// import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'\nimport { NuqsAdapter } from 'nuqs/adapters/tanstack-router'\n\nexport const Route = createRootRoute({\n  component: () => (\n    <>\n      <NuqsAdapter>\n        <RootLayout>\n          <Outlet />\n        </RootLayout>\n      </NuqsAdapter>\n      {/* <TanStackRouterDevtools /> */}\n    </>\n  )\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/basic-io.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { UseQueryStateBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport const Route = createFileRoute('/basic-io/useQueryState')({\n  component: UseQueryStateBasicIO\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/basic-io.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { UseQueryStatesBasicIO } from 'e2e-shared/specs/basic-io'\n\nexport const Route = createFileRoute('/basic-io/useQueryStates')({\n  component: UseQueryStatesBasicIO\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/conditional-rendering.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { ConditionalRenderingUseQueryState } from 'e2e-shared/specs/conditional-rendering'\n\nexport const Route = createFileRoute('/conditional-rendering/useQueryState')({\n  component: ConditionalRenderingUseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/conditional-rendering.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { ConditionalRenderingUseQueryStates } from 'e2e-shared/specs/conditional-rendering'\n\nexport const Route = createFileRoute('/conditional-rendering/useQueryStates')({\n  component: ConditionalRenderingUseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/form.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { TestFormUseQueryState } from 'e2e-shared/specs/form'\n\nexport const Route = createFileRoute('/form/useQueryState')({\n  component: TestFormUseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/form.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { TestFormUseQueryStates } from 'e2e-shared/specs/form'\n\nexport const Route = createFileRoute('/form/useQueryStates')({\n  component: TestFormUseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/hash-preservation.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { HashPreservation } from 'e2e-shared/specs/hash-preservation'\n\nexport const Route = createFileRoute('/hash-preservation')({\n  component: HashPreservation\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/history-sync.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { HistorySync } from 'e2e-shared/specs/history-sync'\n\nexport const Route = createFileRoute('/history-sync')({\n  component: HistorySync\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/json.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { Json, parser } from 'e2e-shared/specs/json'\nimport { createStandardSchemaV1 } from 'nuqs'\n\nconst validateSearch = createStandardSchemaV1(\n  { test: parser },\n  { partialOutput: true }\n)\n\nexport const Route = createFileRoute('/json')({\n  component: Json,\n  validateSearch\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/key-isolation.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { KeyIsolationUseQueryState } from 'e2e-shared/specs/key-isolation'\n\nexport const Route = createFileRoute('/key-isolation/useQueryState')({\n  component: KeyIsolationUseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/key-isolation.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { KeyIsolationUseQueryStates } from 'e2e-shared/specs/key-isolation'\n\nexport const Route = createFileRoute('/key-isolation/useQueryStates')({\n  component: KeyIsolationUseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/life-and-death.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { LifeAndDeath } from 'e2e-shared/specs/life-and-death'\n\nexport const Route = createFileRoute('/life-and-death')({\n  component: LifeAndDeath\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/linking.useQueryState.other.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport const Route = createFileRoute('/linking/useQueryState/other')({\n  component: Page\n})\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/linking.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { LinkingUseQueryState } from 'e2e-shared/specs/linking'\n\nexport const Route = createFileRoute('/linking/useQueryState')({\n  component: Page\n})\n\nexport default function Page() {\n  return <LinkingUseQueryState path=\"/linking/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/linking.useQueryStates.other.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport const Route = createFileRoute('/linking/useQueryStates/other')({\n  component: Page\n})\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/linking.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { LinkingUseQueryStates } from 'e2e-shared/specs/linking'\n\nexport const Route = createFileRoute('/linking/useQueryStates')({\n  component: Page\n})\n\nexport default function Page() {\n  return <LinkingUseQueryStates path=\"/linking/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/native-array.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { NativeArray, parser } from 'e2e-shared/specs/native-array'\nimport { createStandardSchemaV1 } from 'nuqs'\n\nconst validateSearch = createStandardSchemaV1(\n  { test: parser },\n  { partialOutput: true }\n)\n\nexport const Route = createFileRoute('/native-array')({\n  component: NativeArray,\n  validateSearch\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/pretty-urls.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { PrettyUrls } from 'e2e-shared/specs/pretty-urls'\n\nexport const Route = createFileRoute('/pretty-urls')({\n  component: PrettyUrls\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/push.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { PushUseQueryState } from 'e2e-shared/specs/push'\n\nexport const Route = createFileRoute('/push/useQueryState')({\n  component: PushUseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/push.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { PushUseQueryStates } from 'e2e-shared/specs/push'\n\nexport const Route = createFileRoute('/push/useQueryStates')({\n  component: PushUseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/referential-stability.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { ReferentialStabilityUseQueryState } from 'e2e-shared/specs/referential-stability'\n\nexport const Route = createFileRoute('/referential-stability/useQueryState')({\n  component: ReferentialStabilityUseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/referential-stability.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { ReferentialStabilityUseQueryStates } from 'e2e-shared/specs/referential-stability'\n\nexport const Route = createFileRoute('/referential-stability/useQueryStates')({\n  component: ReferentialStabilityUseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/repro-1099.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { Repro1099UseQueryState } from 'e2e-shared/specs/repro-1099'\n\nexport const Route = createFileRoute('/repro-1099/useQueryState')({\n  component: Repro1099UseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/repro-1099.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { Repro1099UseQueryStates } from 'e2e-shared/specs/repro-1099'\n\nexport const Route = createFileRoute('/repro-1099/useQueryStates')({\n  component: Repro1099UseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/repro-1365.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { Repro1365 } from 'e2e-shared/specs/repro-1365'\n\nexport const Route = createFileRoute('/repro-1365')({\n  component: Repro1365\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/routing.useQueryState.other.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport const Route = createFileRoute('/routing/useQueryState/other')({\n  component: Page\n})\n\nfunction Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/routing.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { RoutingUseQueryState } from 'e2e-shared/specs/routing'\n\nexport const Route = createFileRoute('/routing/useQueryState')({\n  component: Page\n})\n\nfunction Page() {\n  return <RoutingUseQueryState path=\"/routing/useQueryState\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/routing.useQueryStates.other.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport const Route = createFileRoute('/routing/useQueryStates/other')({\n  component: Page\n})\n\nfunction Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/routing.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { RoutingUseQueryStates } from 'e2e-shared/specs/routing'\n\nexport const Route = createFileRoute('/routing/useQueryStates')({\n  component: Page\n})\n\nfunction Page() {\n  return <RoutingUseQueryStates path=\"/routing/useQueryStates\" />\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/scroll.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { Scroll } from 'e2e-shared/specs/scroll'\n\nexport const Route = createFileRoute('/scroll')({\n  component: Scroll\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/shallow.useQueryState.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { ShallowUseQueryState } from 'e2e-shared/specs/shallow'\n\nexport const Route = createFileRoute('/shallow/useQueryState')({\n  component: ShallowUseQueryState\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/shallow.useQueryStates.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'\n\nexport const Route = createFileRoute('/shallow/useQueryStates')({\n  component: ShallowUseQueryStates\n})\n"
  },
  {
    "path": "packages/e2e/tanstack-router/src/routes/trailing-slash.tsx",
    "content": "import { createFileRoute } from '@tanstack/react-router'\nimport { createStandardSchemaV1, parseAsString, useQueryStates } from 'nuqs'\n\nconst declared = {\n  declared: parseAsString\n}\nconst undeclared = {\n  undeclared: parseAsString\n}\n\nconst searchParams = {\n  ...declared,\n  ...undeclared\n}\n\nexport const Route = createFileRoute('/trailing-slash')({\n  validateSearch: createStandardSchemaV1(declared, {\n    partialOutput: true\n  }),\n  component: TrailingSlashTest\n})\n\nfunction TrailingSlashTest() {\n  const [{ declared, undeclared }, setSearchParams] =\n    useQueryStates(searchParams)\n  return (\n    <div>\n      <button\n        onClick={() => {\n          setSearchParams({\n            declared: 'pass'\n          })\n        }}\n      >\n        Set declared\n      </button>\n      <button\n        onClick={() => {\n          setSearchParams({\n            undeclared: 'pass'\n          })\n        }}\n      >\n        Set undeclared\n      </button>\n      <button onClick={() => setSearchParams(null)}>Clear</button>\n      <pre>{JSON.stringify({ declared, undeclared }, null, 2)}</pre>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/tsconfig.json",
    "content": "{\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"jsx\": \"react-jsx\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"vite/client\", \"node\"],\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n\n    /* Linting */\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"dist/**\", \"src/routeTree.gen.ts\"],\n      \"dependsOn\": [\"^build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    },\n    \"test\": {\n      \"outputs\": [\".playwright/**\"],\n      \"dependsOn\": [\"build\"],\n      \"env\": [\"REACT_COMPILER\", \"E2E_NO_CACHE_ON_RERUN\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/e2e/tanstack-router/vite.config.js",
    "content": "import { tanstackRouter } from '@tanstack/router-plugin/vite'\nimport viteReact from '@vitejs/plugin-react'\nimport { resolve } from 'node:path'\nimport { defineConfig } from 'vite'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [\n    tanstackRouter({ autoCodeSplitting: true, target: 'react' }),\n    viteReact()\n  ],\n  build: {\n    sourcemap: true\n  },\n  resolve: {\n    alias: {\n      '@': resolve(__dirname, './src')\n    }\n  },\n  server: {\n    port: 3004\n  }\n})\n"
  },
  {
    "path": "packages/examples/next-app/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "packages/examples/next-app/README.md",
    "content": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).\n\n<span style=\"display: flex; align-items: center; gap: 1rem; line-height: 1\">Deploy it on Vercel: [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2F47ng%2Fnuqs%2Ftree%2Fnext%2Fpackages%2Fexamples%2Fnext-app&project-name=nuqs-example-next-app&repository-name=nuqs-example-next-app)</span>\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:4001](http://localhost:4001) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.\n\nThis project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2F47ng%2Fnuqs%2Ftree%2Fnext%2Fpackages%2Fexamples%2Fnext-app&project-name=nuqs-example-next-app&repository-name=nuqs-example-next-app) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.\n"
  },
  {
    "path": "packages/examples/next-app/next.config.ts",
    "content": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  /* config options here */\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "packages/examples/next-app/package.json",
    "content": "{\n  \"name\": \"examples-next-app\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack --port 4001\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"next\": \"catalog:next\",\n    \"nuqs\": \"latest\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.1.18\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"tailwindcss\": \"^4.1.18\",\n    \"typescript\": \"^5.9.3\"\n  }\n}\n"
  },
  {
    "path": "packages/examples/next-app/postcss.config.mjs",
    "content": "const config = {\n  plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/examples/next-app/src/app/_components/filter.tsx",
    "content": "'use client'\n\nimport { useQueryStates } from 'nuqs'\nimport { searchParams } from '../searchParams'\n\nexport function Filter() {\n  const [filter, setFilter] = useQueryStates(searchParams, {\n    shallow: false\n  })\n  return (\n    <div className=\"flex flex-col gap-4\">\n      <input\n        className=\"border border-gray-300 dark:border-gray-600 rounded px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500\"\n        type=\"text\"\n        value={filter.search}\n        onChange={e => setFilter({ search: e.target.value, page: 1 })}\n        placeholder=\"Search users\"\n      />\n\n      <div className=\"flex gap-2\">\n        <button\n          onClick={() =>\n            setFilter(prev => ({\n              order: prev.order === 'asc' ? 'desc' : 'asc',\n              page: 1\n            }))\n          }\n          className=\"rounded px-4 flex-1 py-2 text-sm border border-gray-300 dark:border-gray-600 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 transition-colors\"\n        >\n          Order:{' '}\n          {filter.order === 'asc'\n            ? 'Ascending'\n            : filter.order === 'desc'\n              ? 'Descending'\n              : 'None'}\n        </button>\n\n        <button\n          onClick={() => setFilter(null)}\n          className=\"rounded px-4 py-2 text-sm border border-red-400 bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-800 dark:text-red-100 dark:hover:bg-red-700 transition-colors\"\n        >\n          Reset\n        </button>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/examples/next-app/src/app/_components/pagination.tsx",
    "content": "'use client'\nimport Link from 'next/link'\nimport { useSearchParams } from 'next/navigation'\nimport { useQueryStates } from 'nuqs'\nimport { searchParams, serialize } from '../searchParams'\n\nexport function Pagination({ total }: { total: number }) {\n  const [{ page, limit }] = useQueryStates(searchParams)\n  const totalPages = Math.ceil(total / limit)\n  const currentSearchParams = useSearchParams()\n  return (\n    <nav className=\"mt-2 flex items-center gap-2 mx-auto\">\n      {Array.from({ length: totalPages }, (_, i) => {\n        const p = i + 1\n        const isActive = p === page\n        return (\n          <Link\n            key={p}\n            href={serialize(currentSearchParams, { page: p })}\n            className={`rounded border px-3 py-1 text-sm transition-colors ${\n              isActive\n                ? 'bg-gray-800 text-white dark:bg-gray-200 dark:text-black'\n                : 'hover:bg-gray-100 dark:hover:bg-gray-800'\n            }`}\n          >\n            {p}\n          </Link>\n        )\n      })}\n    </nav>\n  )\n}\n"
  },
  {
    "path": "packages/examples/next-app/src/app/_components/users-list.tsx",
    "content": "import { getUsers } from '@/data'\nimport { searchParamsCache } from '../searchParams'\nimport { Pagination } from './pagination'\n\nexport async function UsersList() {\n  const params = searchParamsCache.all()\n  const users = await getUsers(params)\n\n  return (\n    <div className=\"w-full flex flex-col gap-4\">\n      <ul className=\"w-full max-w-sm divide-y divide-gray-200 dark:divide-gray-700 text-center sm:text-left\">\n        {users.data.map(user => (\n          <li key={user.id} className=\"py-2\">\n            {user.name}\n          </li>\n        ))}\n      </ul>\n      <Pagination total={users.total} />\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/examples/next-app/src/app/globals.css",
    "content": "@import \"tailwindcss\";\n\n:root {\n  --background: #ffffff;\n  --foreground: #171717;\n}\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --font-sans: var(--font-geist-sans);\n  --font-mono: var(--font-geist-mono);\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --background: #0a0a0a;\n    --foreground: #ededed;\n  }\n}\n\nbody {\n  background: var(--background);\n  color: var(--foreground);\n  font-family: Arial, Helvetica, sans-serif;\n}\n"
  },
  {
    "path": "packages/examples/next-app/src/app/layout.tsx",
    "content": "import type { Metadata } from 'next'\nimport { Geist, Geist_Mono } from 'next/font/google'\nimport { NuqsAdapter } from 'nuqs/adapters/next/app'\nimport './globals.css'\n\nconst geistSans = Geist({\n  variable: '--font-geist-sans',\n  subsets: ['latin']\n})\n\nconst geistMono = Geist_Mono({\n  variable: '--font-geist-mono',\n  subsets: ['latin']\n})\n\nexport const metadata: Metadata = {\n  title: 'Create Next App',\n  description: 'Generated by create next app'\n}\n\nexport default function RootLayout({\n  children\n}: Readonly<{\n  children: React.ReactNode\n}>) {\n  return (\n    <html lang=\"en\">\n      <body\n        className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n      >\n        <NuqsAdapter>{children}</NuqsAdapter>\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "packages/examples/next-app/src/app/page.tsx",
    "content": "import type { SearchParams } from 'next/dist/server/request/search-params'\nimport Image from 'next/image'\nimport { Suspense } from 'react'\nimport { Filter } from './_components/filter'\nimport { UsersList } from './_components/users-list'\nimport { searchParamsCache } from './searchParams'\n\ntype PageProps = {\n  searchParams: Promise<SearchParams>\n}\n\nexport default async function Home({ searchParams }: PageProps) {\n  await searchParamsCache.parse(searchParams)\n  return (\n    <div className=\"grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\">\n      <main className=\"flex flex-col gap-[32px] row-start-2 items-center sm:items-start\">\n        <Image\n          className=\"dark:invert\"\n          src=\"/next.svg\"\n          alt=\"Next.js logo\"\n          width={180}\n          height={38}\n          priority\n        />\n        <Suspense>\n          <Filter />\n        </Suspense>\n        <Suspense>\n          <UsersList />\n        </Suspense>\n        <a\n          className=\"w-full rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5\"\n          href=\"https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n        >\n          <Image\n            className=\"dark:invert\"\n            src=\"/vercel.svg\"\n            alt=\"Vercel logomark\"\n            width={20}\n            height={20}\n          />\n          Deploy now\n        </a>\n      </main>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/examples/next-app/src/app/searchParams.tsx",
    "content": "import {\n  createSearchParamsCache,\n  createSerializer,\n  parseAsInteger,\n  parseAsString,\n  parseAsStringEnum\n} from 'nuqs/server'\n\nexport const searchParams = {\n  page: parseAsInteger.withDefault(1),\n  limit: parseAsInteger.withDefault(5),\n  order: parseAsStringEnum(['asc', 'desc']),\n  search: parseAsString.withDefault('')\n}\n\nexport const searchParamsCache = createSearchParamsCache(searchParams)\n\nexport const serialize = createSerializer(searchParams)\n"
  },
  {
    "path": "packages/examples/next-app/src/data.ts",
    "content": "export const users = [\n  {\n    id: 1,\n    name: 'John Doe'\n  },\n  {\n    id: 2,\n    name: 'Jane Doe'\n  },\n  {\n    id: 3,\n    name: 'Ziad El-Sayed'\n  },\n  {\n    id: 4,\n    name: 'Alice Johnson'\n  },\n  {\n    id: 5,\n    name: 'Bob Brown'\n  },\n  {\n    id: 6,\n    name: 'Charlie Davis'\n  },\n  {\n    id: 7,\n    name: 'Diana Evans'\n  },\n  {\n    id: 8,\n    name: 'Ethan Harris'\n  },\n  {\n    id: 9,\n    name: 'Fiona Clark'\n  },\n  {\n    id: 10,\n    name: 'George Miller'\n  },\n  {\n    id: 11,\n    name: 'Hannah Wilson'\n  },\n  {\n    id: 12,\n    name: 'Ian Taylor'\n  },\n  {\n    id: 13,\n    name: 'Karen Anderson'\n  },\n  {\n    id: 14,\n    name: 'Liam Thomas'\n  },\n  {\n    id: 15,\n    name: 'Mia Rodriguez'\n  },\n  {\n    id: 16,\n    name: 'Noah Martinez'\n  },\n  {\n    id: 17,\n    name: 'Olivia Lee'\n  },\n  {\n    id: 18,\n    name: 'Peter Walker'\n  },\n  {\n    id: 19,\n    name: 'Quinn Young'\n  },\n  {\n    id: 20,\n    name: 'Riley King'\n  }\n]\n\nexport interface GetUsersOptions {\n  page: number\n  limit: number\n  order: 'asc' | 'desc' | null\n  search: string\n}\n\nexport async function getUsers({\n  page,\n  limit,\n  order,\n  search\n}: GetUsersOptions) {\n  let records = users\n\n  if (search.trim()) {\n    const term = search.toLowerCase()\n    records = records.filter(item => item.name.toLowerCase().includes(term))\n  }\n\n  if (order) {\n    records = [...records].sort((a, b) => {\n      const valueA = a.name\n      const valueB = b.name\n      if (valueA < valueB) return order === 'asc' ? -1 : 1\n      if (valueA > valueB) return order === 'asc' ? 1 : -1\n      return 0\n    })\n  }\n\n  const total = records.length\n  const start = (page - 1) * limit\n  const paginated = records.slice(start, start + limit)\n\n  return {\n    total,\n    data: paginated\n  }\n}\n"
  },
  {
    "path": "packages/examples/next-app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\n        \"./src/*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/examples/package.json",
    "content": "{\n  \"name\": \"examples\",\n  \"version\": \"0.0.0\",\n  \"private\": true\n}\n"
  },
  {
    "path": "packages/examples/trpc/.gitignore",
    "content": ".DS_Store\n/node_modules/\n\n# React Router\n/.react-router/\n/build/\n"
  },
  {
    "path": "packages/examples/trpc/README.md",
    "content": "# tRPC + React Router\n\nThis example showcases how to use nuqs search params definitions as\nvalidators for tRPC procedure inputs, so you can feed them URL state\nand maintain end-to-end type safety.\n\nSee:\n\n- [search-params.ts](./app/search-params.ts) for search params definitions\n- [trpc.ts](./app/server/trpc.ts) for tRPC router and procedure definitions\n- [inverted-coordinates.tsx](./app/components/inverted-coordinates.tsx) for usage\n"
  },
  {
    "path": "packages/examples/trpc/app/components/inverted-coordinates.tsx",
    "content": "import { useQuery } from '@tanstack/react-query'\nimport { useCoordinates } from '~/search-params'\nimport { useTRPC } from '~/utils/trpc'\n\nexport function InvertedCoordinates() {\n  const { latitude, longitude } = useCoordinates()\n  const tprc = useTRPC()\n  const { data } = useQuery(\n    tprc.invert.queryOptions({\n      latitude,\n      longitude\n    })\n  )\n  return (\n    <>\n      <p>Inverted coordinates: (last updated at: {data?.time ?? null})</p>\n      <pre>{JSON.stringify(data?.inverted, null, 2)}</pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/examples/trpc/app/components/random-coordinates.tsx",
    "content": "import { useCoordinates } from '~/search-params'\n\nexport function RandomCoordinates() {\n  const { latitude, longitude, setCoordinates } = useCoordinates()\n  return (\n    <div>\n      <button\n        onClick={() =>\n          setCoordinates({\n            latitude: Math.random() * 90,\n            longitude: Math.random() * 180\n          })\n        }\n      >\n        Set random coordinates\n      </button>\n      <pre>{JSON.stringify({ latitude, longitude }, null, 2)}</pre>\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/examples/trpc/app/root.tsx",
    "content": "import { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { ReactQueryDevtools } from '@tanstack/react-query-devtools'\nimport { createTRPCClient, httpBatchLink } from '@trpc/client'\nimport { NuqsAdapter } from 'nuqs/adapters/react-router/v7'\nimport { useState } from 'react'\nimport {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration\n} from 'react-router'\nimport type { AppRouter } from '~/server/trpc'\nimport { TRPCProvider } from '~/utils/trpc'\nimport type { Route } from './+types/root'\n\nfunction makeQueryClient() {\n  return new QueryClient({\n    defaultOptions: {\n      queries: {\n        // With SSR, we usually want to set some default staleTime\n        // above 0 to avoid refetching immediately on the client\n        staleTime: 60 * 1000\n      }\n    }\n  })\n}\n\nlet browserQueryClient: QueryClient | undefined = undefined\n\nfunction getQueryClient() {\n  if (typeof window === 'undefined') {\n    // Server: always make a new query client\n    return makeQueryClient()\n  } else {\n    // Browser: make a new query client if we don't already have one\n    // This is very important, so we don't re-make a new client if React\n    // suspends during the initial render. This may not be needed if we\n    // have a suspense boundary BELOW the creation of the query client\n    if (!browserQueryClient) browserQueryClient = makeQueryClient()\n    return browserQueryClient\n  }\n}\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  )\n}\n\nexport default function App() {\n  const queryClient = getQueryClient()\n  const [trpcClient] = useState(() =>\n    createTRPCClient<AppRouter>({\n      links: [\n        httpBatchLink({\n          url: 'http://localhost:4000/api/trpc'\n        })\n      ]\n    })\n  )\n  return (\n    <QueryClientProvider client={queryClient}>\n      <TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>\n        <NuqsAdapter>\n          <Outlet />\n        </NuqsAdapter>\n        <ReactQueryDevtools />\n      </TRPCProvider>\n    </QueryClientProvider>\n  )\n}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = 'Oops!'\n  let details = 'An unexpected error occurred.'\n  let stack: string | undefined\n\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? '404' : 'Error'\n    details =\n      error.status === 404\n        ? 'The requested page could not be found.'\n        : error.statusText || details\n  } else if (import.meta.env.DEV && error && error instanceof Error) {\n    details = error.message\n    stack = error.stack\n  }\n\n  return (\n    <main className=\"pt-16 p-4 container mx-auto\">\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre className=\"w-full p-4 overflow-x-auto\">\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  )\n}\n"
  },
  {
    "path": "packages/examples/trpc/app/routes/api/trpc.ts",
    "content": "import { fetchRequestHandler } from '@trpc/server/adapters/fetch'\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'\nimport { appRouter } from '~/server/trpc'\n\nexport const loader = async (args: LoaderFunctionArgs) => {\n  return handleRequest(args)\n}\n\nexport const action = async (args: ActionFunctionArgs) => {\n  return handleRequest(args)\n}\n\nfunction handleRequest(args: LoaderFunctionArgs | ActionFunctionArgs) {\n  return fetchRequestHandler({\n    endpoint: '/api/trpc',\n    req: args.request,\n    router: appRouter\n  })\n}\n"
  },
  {
    "path": "packages/examples/trpc/app/routes/index.tsx",
    "content": "import { InvertedCoordinates } from '~/components/inverted-coordinates'\nimport { RandomCoordinates } from '~/components/random-coordinates'\n\nexport default function Page() {\n  return (\n    <>\n      <RandomCoordinates />\n      <hr />\n      <InvertedCoordinates />\n    </>\n  )\n}\n"
  },
  {
    "path": "packages/examples/trpc/app/routes.ts",
    "content": "import { type RouteConfig, route } from '@react-router/dev/routes'\n\nexport default [\n  // prettier-ignore\n  route('/api/trpc/*', 'routes/api/trpc.ts'),\n  route('/', './routes/index.tsx')\n] satisfies RouteConfig\n"
  },
  {
    "path": "packages/examples/trpc/app/search-params.ts",
    "content": "import {\n  createLoader,\n  createStandardSchemaV1,\n  parseAsFloat,\n  useQueryStates,\n  type UrlKeys\n} from 'nuqs'\n\nexport const coordinates = {\n  latitude: parseAsFloat.withDefault(45),\n  longitude: parseAsFloat.withDefault(5)\n}\nexport const coordinatesUrlKeys: UrlKeys<typeof coordinates> = {\n  latitude: 'lat',\n  longitude: 'lng'\n}\n\nexport const loadCoordinates = createLoader(coordinates, {\n  urlKeys: coordinatesUrlKeys\n})\nexport const validateCoordinates = createStandardSchemaV1(coordinates, {\n  urlKeys: coordinatesUrlKeys\n})\n\nexport function useCoordinates() {\n  const [{ latitude, longitude }, setCoordinates] = useQueryStates(\n    coordinates,\n    {\n      urlKeys: coordinatesUrlKeys\n    }\n  )\n  return {\n    latitude,\n    longitude,\n    setCoordinates\n  }\n}\n"
  },
  {
    "path": "packages/examples/trpc/app/server/trpc.ts",
    "content": "import { initTRPC } from '@trpc/server'\nimport { validateCoordinates } from '~/search-params.ts'\n\n/**\n * Initialization of tRPC backend\n * Should be done only once per backend!\n */\nconst t = initTRPC.create()\n\n/**\n * Export reusable router and procedure helpers\n * that can be used throughout the router\n */\nexport const router = t.router\nexport const publicProcedure = t.procedure\n\nexport const appRouter = router({\n  invert: publicProcedure.input(validateCoordinates).query(async options => {\n    const { latitude, longitude } = options.input\n    const inverted = {\n      latitude: latitude * -1,\n      longitude: longitude * -1\n    }\n    return {\n      time: new Date().toISOString(),\n      inverted\n    }\n  })\n})\n\n// Export type router type signature,\n// NOT the router itself.\nexport type AppRouter = typeof appRouter\n"
  },
  {
    "path": "packages/examples/trpc/app/utils/trpc.ts",
    "content": "import { createTRPCContext } from '@trpc/tanstack-react-query'\nimport type { AppRouter } from '~/server/trpc'\n\nexport const { TRPCProvider, useTRPC, useTRPCClient } =\n  createTRPCContext<AppRouter>()\n"
  },
  {
    "path": "packages/examples/trpc/package.json",
    "content": "{\n  \"name\": \"examples-trpc\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev --port 4000\",\n    \"start\": \"cross-env NODE_ENV=production ./server.mjs\",\n    \"test\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@react-router/node\": \"^7.13.0\",\n    \"@react-router/serve\": \"^7.13.0\",\n    \"@tanstack/react-query\": \"^5.90.20\",\n    \"@tanstack/react-query-devtools\": \"^5.91.3\",\n    \"@trpc/client\": \"^11.9.0\",\n    \"@trpc/server\": \"^11.9.0\",\n    \"@trpc/tanstack-react-query\": \"^11.9.0\",\n    \"isbot\": \"^5.1.34\",\n    \"nuqs\": \"workspace:*\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"react-router\": \"^7.13.0\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.13.0\",\n    \"@react-router/express\": \"^7.13.0\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"compression\": \"^1.8.1\",\n    \"cross-env\": \"^10.1.0\",\n    \"express\": \"^4.22.1\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"catalog:vite\",\n    \"vite-tsconfig-paths\": \"^6.0.5\"\n  }\n}\n"
  },
  {
    "path": "packages/examples/trpc/react-router.config.ts",
    "content": "import type { Config } from '@react-router/dev/config'\n\nexport default {\n  // Config options...\n  // Server-side render by default, to enable SPA mode set this to `false`\n  ssr: true\n} satisfies Config\n"
  },
  {
    "path": "packages/examples/trpc/server.mjs",
    "content": "#!/usr/bin/env node\n// @ts-check\n\nimport { createRequestHandler } from '@react-router/express'\nimport compression from 'compression'\nimport express from 'express'\nimport os from 'node:os'\nimport path from 'node:path'\nimport url from 'node:url'\n\nprocess.env.NODE_ENV = process.env.NODE_ENV ?? 'production'\n\nrun()\n\nasync function run() {\n  const port = 4000\n  const buildPath = path.resolve('build/server/index.js')\n\n  /** @type {import('react-router').ServerBuild } */\n  const build = await import(url.pathToFileURL(buildPath).href)\n\n  const onListen = () => {\n    const address =\n      process.env.HOST ||\n      Object.values(os.networkInterfaces())\n        .flat()\n        .find(ip => String(ip?.family).includes('4') && !ip?.internal)?.address\n\n    if (!address) {\n      console.log(`[react-router-v7] http://localhost:${port}`)\n    } else {\n      console.log(\n        `[react-router-v7] http://localhost:${port} (http://${address}:${port})`\n      )\n    }\n  }\n\n  const app = express()\n  app.disable('x-powered-by')\n  app.use(compression())\n  app.use(\n    path.posix.join(build.publicPath, 'assets'),\n    express.static(path.join(build.assetsBuildDirectory, 'assets'), {\n      immutable: true,\n      maxAge: '1y'\n    })\n  )\n  app.use(build.publicPath, express.static(build.assetsBuildDirectory))\n  app.use(express.static('public', { maxAge: '1h' }))\n\n  app.all(\n    '*',\n    createRequestHandler({\n      build,\n      mode: process.env.NODE_ENV\n    })\n  )\n\n  const server = process.env.HOST\n    ? app.listen(port, process.env.HOST, onListen)\n    : app.listen(port, onListen)\n\n  ;['SIGTERM', 'SIGINT'].forEach(signal => {\n    process.once(signal, () => server?.close(console.error))\n  })\n}\n"
  },
  {
    "path": "packages/examples/trpc/tsconfig.json",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "packages/examples/trpc/vite.config.ts",
    "content": "import { reactRouter } from '@react-router/dev/vite'\nimport { defineConfig } from 'vite'\nimport tsconfigPaths from 'vite-tsconfig-paths'\n\nexport default defineConfig({\n  plugins: [reactRouter(), tsconfigPaths()]\n})\n"
  },
  {
    "path": "packages/nuqs/.gitignore",
    "content": "size.json\nsrc/__screenshots__\n"
  },
  {
    "path": "packages/nuqs/adapters/custom.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adapters/custom'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../dist/adapters/custom'\n"
  },
  {
    "path": "packages/nuqs/adapters/next/app.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adpaters/next/app'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../../dist/adapters/next/app'\n"
  },
  {
    "path": "packages/nuqs/adapters/next/pages.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adpaters/next/pages'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../../dist/adapters/next/pages'\n"
  },
  {
    "path": "packages/nuqs/adapters/next.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adpaters/next'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../dist/adapters/next'\n"
  },
  {
    "path": "packages/nuqs/adapters/react-router/v6.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adapters/react-router/v6'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../../dist/adapters/react-router/v6'\n"
  },
  {
    "path": "packages/nuqs/adapters/react-router/v7.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adapters/react-router/v7'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../../dist/adapters/react-router/v7'\n"
  },
  {
    "path": "packages/nuqs/adapters/react-router.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adapters/react-router'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n//\n// Note: this default react-router adapter is for react-router v6.\n// If you are using react-router v7, please import from `nuqs/adapters/react-router/v7`\n\nexport * from '../dist/adapters/react-router'\n"
  },
  {
    "path": "packages/nuqs/adapters/react.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adapters/react'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../dist/adapters/react'\n"
  },
  {
    "path": "packages/nuqs/adapters/remix.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adapters/remix'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../dist/adapters/remix'\n"
  },
  {
    "path": "packages/nuqs/adapters/tanstack-router.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adpaters/tanstack-router'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../dist/adapters/tanstack-router'\n"
  },
  {
    "path": "packages/nuqs/adapters/testing.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/adpaters/testing'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from '../dist/adapters/testing'\n"
  },
  {
    "path": "packages/nuqs/package.json",
    "content": "{\n  \"name\": \"nuqs\",\n  \"version\": \"0.0.0-semantically-released\",\n  \"description\": \"Type-safe search params state manager for React - Like useState, but stored in the URL query string\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"François Best\",\n    \"email\": \"contact@francoisbest.com\",\n    \"url\": \"https://francoisbest.com\"\n  },\n  \"funding\": \"https://github.com/sponsors/franky47\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/47ng/nuqs.git\",\n    \"directory\": \"packages/nuqs\"\n  },\n  \"homepage\": \"https://nuqs.dev\",\n  \"keywords\": [\n    \"url state\",\n    \"url\",\n    \"querystring\",\n    \"query string\",\n    \"search params\",\n    \"next-usequerystate\",\n    \"useQueryState\",\n    \"useQueryStates\",\n    \"nextjs\",\n    \"react\",\n    \"remix\",\n    \"react-router\",\n    \"tanstack-router\"\n  ],\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"provenance\": true\n  },\n  \"files\": [\n    \"dist/\",\n    \"server.d.ts\",\n    \"testing.d.ts\",\n    \"adapters/react.d.ts\",\n    \"adapters/next.d.ts\",\n    \"adapters/next/app.d.ts\",\n    \"adapters/next/pages.d.ts\",\n    \"adapters/remix.d.ts\",\n    \"adapters/react-router.d.ts\",\n    \"adapters/react-router/v6.d.ts\",\n    \"adapters/react-router/v7.d.ts\",\n    \"adapters/tanstack-router.d.ts\",\n    \"adapters/custom.d.ts\",\n    \"adapters/testing.d.ts\"\n  ],\n  \"type\": \"module\",\n  \"sideEffects\": false,\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \"./package.json\": \"./package.json\",\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"default\": \"./dist/index.js\"\n    },\n    \"./server\": {\n      \"types\": \"./dist/server.d.ts\",\n      \"import\": \"./dist/server.js\",\n      \"default\": \"./dist/server.js\"\n    },\n    \"./testing\": {\n      \"types\": \"./dist/testing.d.ts\",\n      \"import\": \"./dist/testing.js\",\n      \"default\": \"./dist/testing.js\"\n    },\n    \"./adapters/react\": {\n      \"types\": \"./dist/adapters/react.d.ts\",\n      \"import\": \"./dist/adapters/react.js\",\n      \"default\": \"./dist/adapters/react.js\"\n    },\n    \"./adapters/next\": {\n      \"types\": \"./dist/adapters/next.d.ts\",\n      \"import\": \"./dist/adapters/next.js\",\n      \"default\": \"./dist/adapters/next.js\"\n    },\n    \"./adapters/next/app\": {\n      \"types\": \"./dist/adapters/next/app.d.ts\",\n      \"import\": \"./dist/adapters/next/app.js\",\n      \"default\": \"./dist/adapters/next/app.js\"\n    },\n    \"./adapters/next/pages\": {\n      \"types\": \"./dist/adapters/next/pages.d.ts\",\n      \"import\": \"./dist/adapters/next/pages.js\",\n      \"default\": \"./dist/adapters/next/pages.js\"\n    },\n    \"./adapters/remix\": {\n      \"types\": \"./dist/adapters/remix.d.ts\",\n      \"import\": \"./dist/adapters/remix.js\",\n      \"default\": \"./dist/adapters/remix.js\"\n    },\n    \"./adapters/react-router\": {\n      \"types\": \"./dist/adapters/react-router.d.ts\",\n      \"import\": \"./dist/adapters/react-router.js\",\n      \"default\": \"./dist/adapters/react-router.js\"\n    },\n    \"./adapters/react-router/v6\": {\n      \"types\": \"./dist/adapters/react-router/v6.d.ts\",\n      \"import\": \"./dist/adapters/react-router/v6.js\",\n      \"default\": \"./dist/adapters/react-router/v6.js\"\n    },\n    \"./adapters/react-router/v7\": {\n      \"types\": \"./dist/adapters/react-router/v7.d.ts\",\n      \"import\": \"./dist/adapters/react-router/v7.js\",\n      \"default\": \"./dist/adapters/react-router/v7.js\"\n    },\n    \"./adapters/tanstack-router\": {\n      \"types\": \"./dist/adapters/tanstack-router.d.ts\",\n      \"import\": \"./dist/adapters/tanstack-router.js\",\n      \"default\": \"./dist/adapters/tanstack-router.js\"\n    },\n    \"./adapters/custom\": {\n      \"types\": \"./dist/adapters/custom.d.ts\",\n      \"import\": \"./dist/adapters/custom.js\",\n      \"default\": \"./dist/adapters/custom.js\"\n    },\n    \"./adapters/testing\": {\n      \"types\": \"./dist/adapters/testing.d.ts\",\n      \"import\": \"./dist/adapters/testing.js\",\n      \"default\": \"./dist/adapters/testing.js\"\n    }\n  },\n  \"scripts\": {\n    \"dev\": \"tsdown --watch\",\n    \"build\": \"tsdown\",\n    \"build:size-json\": \"size-limit --json > size.json\",\n    \"test\": \"pnpm run --stream '/^test:/'\",\n    \"test:unit\": \"vitest run --project unit\",\n    \"test:browser\": \"vitest run --project browser --browser.headless\",\n    \"test:types\": \"vitest run --project types\",\n    \"test:size\": \"size-limit\",\n    \"prepack\": \"./scripts/prepack.sh\"\n  },\n  \"dependencies\": {\n    \"@standard-schema/spec\": \"1.0.0\"\n  },\n  \"peerDependencies\": {\n    \"@remix-run/react\": \">=2\",\n    \"@tanstack/react-router\": \"^1\",\n    \"next\": \">=14.2.0\",\n    \"react\": \">=18.2.0 || ^19.0.0-0\",\n    \"react-router\": \"^5 || ^6 || ^7\",\n    \"react-router-dom\": \"^5 || ^6 || ^7\"\n  },\n  \"peerDependenciesMeta\": {\n    \"@remix-run/react\": {\n      \"optional\": true\n    },\n    \"@tanstack/react-router\": {\n      \"optional\": true\n    },\n    \"next\": {\n      \"optional\": true\n    },\n    \"react-router\": {\n      \"optional\": true\n    },\n    \"react-router-dom\": {\n      \"optional\": true\n    }\n  },\n  \"devDependencies\": {\n    \"@remix-run/react\": \"^2.17.4\",\n    \"@size-limit/preset-small-lib\": \"^12.0.0\",\n    \"@types/node\": \"^24.10.10\",\n    \"@types/react\": \"catalog:react19\",\n    \"@types/react-dom\": \"catalog:react19\",\n    \"@vitejs/plugin-react\": \"^5.1.3\",\n    \"@vitest/browser-playwright\": \"catalog:vitest\",\n    \"@vitest/coverage-v8\": \"catalog:vitest\",\n    \"arktype\": \"^2.1.29\",\n    \"fast-check\": \"^4.5.3\",\n    \"next\": \"catalog:next\",\n    \"playwright\": \"catalog:e2e\",\n    \"react\": \"catalog:react19\",\n    \"react-dom\": \"catalog:react19\",\n    \"react-router-dom\": \"6.30.3\",\n    \"size-limit\": \"^12.0.0\",\n    \"tsdown\": \"^0.20.1\",\n    \"typescript\": \"^5.9.3\",\n    \"valibot\": \"^1.2.0\",\n    \"vitest\": \"catalog:vitest\",\n    \"vitest-browser-react\": \"2.0.4\",\n    \"vitest-package-exports\": \"^1.1.2\",\n    \"zod\": \"^4.3.6\"\n  },\n  \"size-limit\": [\n    {\n      \"name\": \"Client\",\n      \"path\": \"dist/index.js\",\n      \"limit\": \"6 kB\"\n    },\n    {\n      \"name\": \"Client (minimal tree-shaken)\",\n      \"path\": \"dist/index.js\",\n      \"limit\": \"4.5 kB\",\n      \"import\": \"{ useQueryStates, parseAsInteger }\"\n    },\n    {\n      \"name\": \"Server\",\n      \"path\": \"dist/server.js\",\n      \"limit\": \"3.5 kB\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/nuqs/scripts/prepack.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\n# Place ourselves in the package directory\ncd \"$(dirname \"$0\")/..\"\n\n# Copy the README & License from the root of the repository\ncp -f ../../README.md ../../LICENSE ./\n\n# Read the version from package.json\nVERSION=$(jq -r '.version' < package.json)\n\necho \"Injecting version ${VERSION} into built files...\"\n\nif [[ \"$(uname)\" == \"Darwin\" ]]; then\n  # macOS requires an empty string as the backup extension\n  find dist -name \"*.js\" -exec sed -i '' \"s/0.0.0-inject-version-here/${VERSION}/g\" {} +\nelse\n  # Ubuntu (CI/CD) doesn't\n  find dist -name \"*.js\" -exec sed -i \"s/0.0.0-inject-version-here/${VERSION}/g\" {} +\nfi\n"
  },
  {
    "path": "packages/nuqs/server.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/server'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from './dist/server'\n"
  },
  {
    "path": "packages/nuqs/src/adapters/custom.ts",
    "content": "export { renderQueryString } from '../lib/url-encoding'\nexport {\n  createAdapterProvider as unstable_createAdapterProvider,\n  type AdapterContext as unstable_AdapterContext\n} from './lib/context'\nexport type {\n  AdapterInterface as unstable_AdapterInterface,\n  AdapterOptions as unstable_AdapterOptions,\n  UpdateUrlFunction as unstable_UpdateUrlFunction,\n  UseAdapterHook as unstable_UseAdapterHook\n} from './lib/defs'\n"
  },
  {
    "path": "packages/nuqs/src/adapters/lib/context.ts",
    "content": "import {\n  createContext,\n  createElement,\n  useContext,\n  type Context,\n  type ProviderProps,\n  type ReactElement,\n  type ReactNode\n} from 'react'\nimport type { Options } from '../../defs'\nimport { debugEnabled } from '../../lib/debug'\nimport { error } from '../../lib/errors'\nimport type { AdapterInterface, UseAdapterHook } from './defs'\n\nexport type AdapterProps = {\n  defaultOptions?: Partial<\n    Pick<Options, 'shallow' | 'clearOnDefault' | 'scroll' | 'limitUrlUpdates'>\n  >\n  processUrlSearchParams?: (search: URLSearchParams) => URLSearchParams\n}\n\nexport type AdapterContext = AdapterProps & {\n  useAdapter: UseAdapterHook\n}\n\nexport const context: Context<AdapterContext> = createContext<AdapterContext>({\n  useAdapter() {\n    throw new Error(error(404))\n  }\n})\ncontext.displayName = 'NuqsAdapterContext'\n\ndeclare global {\n  interface Window {\n    __NuqsAdapterContext?: typeof context\n  }\n}\n\nif (debugEnabled && typeof window !== 'undefined') {\n  if (window.__NuqsAdapterContext && window.__NuqsAdapterContext !== context) {\n    console.error(error(303))\n  }\n  window.__NuqsAdapterContext = context\n}\n\nexport type AdapterProvider = (\n  props: AdapterProps & {\n    children: ReactNode\n  }\n) => ReactElement<ProviderProps<AdapterContext>>\n\n/**\n * Create a custom adapter (context provider) for nuqs to work with your framework / router.\n *\n * Adapters are based on React Context,\n *\n * @param useAdapter\n * @returns\n */\nexport function createAdapterProvider(\n  useAdapter: UseAdapterHook\n): AdapterProvider {\n  return ({ children, defaultOptions, processUrlSearchParams, ...props }) =>\n    createElement(\n      context.Provider,\n      {\n        ...props,\n        value: { useAdapter, defaultOptions, processUrlSearchParams }\n      },\n      children\n    )\n}\n\nexport function useAdapter(watchKeys: string[]): AdapterInterface {\n  const value = useContext(context)\n  if (!('useAdapter' in value)) {\n    throw new Error(error(404))\n  }\n  return value.useAdapter(watchKeys)\n}\n\nexport const useAdapterDefaultOptions = (): AdapterProps['defaultOptions'] =>\n  useContext(context).defaultOptions\n\nexport const useAdapterProcessUrlSearchParams =\n  (): AdapterProps['processUrlSearchParams'] =>\n    useContext(context).processUrlSearchParams\n"
  },
  {
    "path": "packages/nuqs/src/adapters/lib/defs.ts",
    "content": "import type { Options } from '../../defs'\n\nexport type AdapterOptions = Pick<Options, 'history' | 'scroll' | 'shallow'>\n\nexport type UpdateUrlFunction = (\n  search: URLSearchParams,\n  options: Required<AdapterOptions>\n) => void\n\nexport type UseAdapterHook = (watchKeys: string[]) => AdapterInterface\n\nexport type AdapterInterface = {\n  searchParams: URLSearchParams\n  updateUrl: UpdateUrlFunction\n  getSearchParamsSnapshot?: () => URLSearchParams\n  rateLimitFactor?: number\n  autoResetQueueOnUpdate?: boolean\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/lib/key-isolation.ts",
    "content": "import { debug } from '../../lib/debug'\nimport { compareQuery } from '../../lib/compare'\n\nexport function applyChange(\n  newValue: URLSearchParams,\n  keys: string[],\n  copy: boolean\n): (oldValue: URLSearchParams) => URLSearchParams {\n  return (oldValue: URLSearchParams) => {\n    const hasChanged =\n      keys.length === 0\n        ? true\n        : keys.some(\n            key => !compareQuery(oldValue.getAll(key), newValue.getAll(key))\n          )\n    if (!hasChanged) {\n      debug(\n        '[nuqs `%s`] no change, returning previous',\n        keys.join(','),\n        oldValue\n      )\n      return oldValue\n    }\n    const filtered = filterSearchParams(newValue, keys, copy)\n    debug(\n      `[nuqs \\`%s\\`] subbed search params change\n  from %O\n  to   %O`,\n      keys.join(','),\n      oldValue,\n      filtered\n    )\n    return filtered\n  }\n}\n\nexport function filterSearchParams(\n  search: URLSearchParams,\n  keys: string[],\n  copy: boolean\n): URLSearchParams {\n  if (keys.length === 0) {\n    return search\n  }\n  const filtered = copy ? new URLSearchParams(search) : search\n  for (const key of search.keys()) {\n    if (!keys.includes(key)) {\n      filtered.delete(key)\n    }\n  }\n  return filtered\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/lib/patch-history.browser.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { getSearchParams } from './patch-history'\n\ndescribe('patch-history/getSearchParams', () => {\n  it('extracts search params from a URL object', () => {\n    const received = getSearchParams(new URL('http://example.com/?foo=bar'))\n    const expected = new URLSearchParams('?foo=bar')\n    expect(received).toEqual(expected)\n  })\n  it('extracts search params from a fully-qualified URL string', () => {\n    const received = getSearchParams('http://example.com/?foo=bar')\n    const expected = new URLSearchParams('?foo=bar')\n    expect(received).toEqual(expected)\n  })\n  it('extracts search params from a pathname', () => {\n    const received = getSearchParams('/?foo=bar')\n    const expected = new URLSearchParams('?foo=bar')\n    expect(received).toEqual(expected)\n  })\n  it('extracts search params from a query string', () => {\n    const received = getSearchParams('?foo=bar')\n    const expected = new URLSearchParams('?foo=bar')\n    expect(received).toEqual(expected)\n  })\n  it('falls back to an empty search params object for invalid inputs', () => {\n    const received = getSearchParams('invalid')\n    const expected = new URLSearchParams()\n    expect(received).toEqual(expected)\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/adapters/lib/patch-history.ts",
    "content": "import { debug } from '../../lib/debug'\nimport type { Emitter } from '../../lib/emitter'\nimport { error } from '../../lib/errors'\nimport { resetQueues, spinQueueResetMutex } from '../../lib/queues/reset'\n\nexport type SearchParamsSyncEmitterEvents = { update: URLSearchParams }\n\nexport const historyUpdateMarker = '__nuqs__'\n\ndeclare global {\n  interface History {\n    nuqs?: {\n      version: string\n      adapters: string[]\n    }\n  }\n}\n\nexport function getSearchParams(url: string | URL): URLSearchParams {\n  if (url instanceof URL) {\n    return url.searchParams\n  }\n  if (url.startsWith('?')) {\n    return new URLSearchParams(url)\n  }\n  try {\n    return new URL(url, location.origin).searchParams\n  } catch {\n    return new URLSearchParams(url)\n  }\n}\n\nexport function shouldPatchHistory(adapter: string): boolean {\n  if (typeof history === 'undefined') {\n    return false\n  }\n  if (\n    history.nuqs?.version &&\n    history.nuqs.version !== '0.0.0-inject-version-here'\n  ) {\n    console.error(\n      error(409),\n      history.nuqs.version,\n      `0.0.0-inject-version-here`,\n      adapter\n    )\n    return false\n  }\n  if (history.nuqs?.adapters?.includes(adapter)) {\n    return false\n  }\n  return true\n}\n\nexport function markHistoryAsPatched(adapter: string): void {\n  history.nuqs = history.nuqs ?? {\n    // This will be replaced by the prepack script\n    version: '0.0.0-inject-version-here',\n    adapters: []\n  }\n  history.nuqs.adapters.push(adapter)\n}\n\nexport function patchHistory(\n  emitter: Emitter<SearchParamsSyncEmitterEvents>,\n  adapter: string\n): void {\n  if (!shouldPatchHistory(adapter)) {\n    return\n  }\n  let lastSearchSeen = typeof location === 'object' ? location.search : ''\n\n  emitter.on('update', search => {\n    const searchString = search.toString()\n    lastSearchSeen = searchString.length ? '?' + searchString : ''\n  })\n\n  window.addEventListener('popstate', () => {\n    lastSearchSeen = location.search\n    resetQueues()\n  })\n\n  debug(\n    '[nuqs %s] Patching history (%s adapter)',\n    '0.0.0-inject-version-here',\n    adapter\n  )\n  function sync(url: URL | string) {\n    spinQueueResetMutex()\n    try {\n      const newSearch = new URL(url, location.origin).search\n      if (newSearch === lastSearchSeen) {\n        return\n      }\n    } catch {}\n    try {\n      emitter.emit('update', getSearchParams(url))\n    } catch (e) {\n      console.error(e)\n    }\n  }\n  const originalPushState = history.pushState\n  const originalReplaceState = history.replaceState\n  history.pushState = function nuqs_pushState(state, marker, url) {\n    originalPushState.call(history, state, '', url)\n    if (url && marker !== historyUpdateMarker) {\n      sync(url)\n    }\n  }\n  history.replaceState = function nuqs_replaceState(state, marker, url) {\n    originalReplaceState.call(history, state, '', url)\n    if (url && marker !== historyUpdateMarker) {\n      sync(url)\n    }\n  }\n  markHistoryAsPatched(adapter)\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/lib/react-router.ts",
    "content": "import { startTransition, useCallback, useEffect, useState } from 'react'\nimport { debug } from '../../lib/debug'\nimport { createEmitter } from '../../lib/emitter'\nimport { setQueueResetMutex } from '../../lib/queues/reset'\nimport { renderQueryString } from '../../lib/url-encoding'\nimport { createAdapterProvider, type AdapterProvider } from './context'\nimport type { AdapterInterface, AdapterOptions } from './defs'\nimport { applyChange, filterSearchParams } from './key-isolation'\nimport {\n  patchHistory as applyHistoryPatch,\n  historyUpdateMarker,\n  type SearchParamsSyncEmitterEvents\n} from './patch-history'\n\n// Abstract away the types for the useNavigate hook from react-router-based frameworks\ntype NavigateUrl = {\n  hash?: string\n  search?: string\n}\ntype NavigateOptions = {\n  replace?: boolean\n  preventScrollReset?: boolean\n  state?: unknown\n}\ntype NavigateFn = (url: NavigateUrl, options: NavigateOptions) => void\ntype UseNavigate = () => NavigateFn\ntype UseSearchParams = (initial: URLSearchParams) => [URLSearchParams, {}]\n\n// --\n\ntype CreateReactRouterBasedAdapterArgs = {\n  adapter: string\n  useNavigate: UseNavigate\n  useSearchParams: UseSearchParams\n}\n\nexport function createReactRouterBasedAdapter({\n  adapter,\n  useNavigate,\n  useSearchParams\n}: CreateReactRouterBasedAdapterArgs): {\n  NuqsAdapter: AdapterProvider\n  useOptimisticSearchParams: () => URLSearchParams\n} {\n  const emitter = createEmitter<SearchParamsSyncEmitterEvents>()\n  function useNuqsReactRouterBasedAdapter(\n    watchKeys: string[]\n  ): AdapterInterface {\n    const navigate = useNavigate()\n    const searchParams = useOptimisticSearchParams(watchKeys)\n    const updateUrl = useCallback(\n      (search: URLSearchParams, options: AdapterOptions) => {\n        startTransition(() => {\n          emitter.emit('update', search)\n        })\n        const url = new URL(location.href)\n        url.search = renderQueryString(search)\n        debug(`[nuqs ${adapter}] Updating url: %s`, url)\n        // First, update the URL locally without triggering a network request,\n        // this allows keeping a reactive URL if the network is slow.\n        const updateMethod =\n          options.history === 'push' ? history.pushState : history.replaceState\n        setQueueResetMutex(options.shallow ? 1 : 2)\n        updateMethod.call(\n          history,\n          history.state, // Maintain the history state\n          historyUpdateMarker,\n          url\n        )\n        if (options.shallow === false) {\n          navigate(\n            {\n              // Somehow passing the full URL object here strips the search params\n              // when accessing the request.url in loaders.\n              hash: url.hash,\n              search: url.search\n            },\n            {\n              replace: true,\n              preventScrollReset: true,\n              state: history.state?.usr\n            }\n          )\n        }\n        if (options.scroll) {\n          window.scrollTo(0, 0)\n        }\n      },\n      [navigate]\n    )\n    return {\n      searchParams,\n      updateUrl,\n      autoResetQueueOnUpdate: false\n    }\n  }\n  function useOptimisticSearchParams(\n    watchKeys: string[] = []\n  ): URLSearchParams {\n    const [serverSearchParams] = useSearchParams(\n      // Note: this will only be taken into account the first time the hook is called,\n      // and cached for subsequent calls, causing problems when mounting components\n      // after shallow updates have occurred.\n      typeof location === 'undefined'\n        ? new URLSearchParams()\n        : new URLSearchParams(location.search)\n    )\n    const [searchParams, setSearchParams] = useState(() => {\n      return typeof location === 'undefined'\n        ? // We use this on the server to SSR with the correct search params.\n          filterSearchParams(serverSearchParams, watchKeys, true)\n        : // Since useSearchParams isn't reactive to shallow changes,\n          // it doesn't pick up changes in the URL on mount, so we need to initialise\n          // the reactive state with the current URL instead.\n          filterSearchParams(\n            new URLSearchParams(location.search),\n            watchKeys,\n            false // No need for a copy here\n          )\n    })\n    useEffect(() => {\n      function onPopState() {\n        startTransition(() => {\n          setSearchParams(\n            applyChange(new URLSearchParams(location.search), watchKeys, false)\n          )\n        })\n      }\n      function onEmitterUpdate(search: URLSearchParams) {\n        startTransition(() => {\n          setSearchParams(applyChange(search, watchKeys, true))\n        })\n      }\n      emitter.on('update', onEmitterUpdate)\n      window.addEventListener('popstate', onPopState)\n      return () => {\n        emitter.off('update', onEmitterUpdate)\n        window.removeEventListener('popstate', onPopState)\n      }\n    }, [watchKeys.join('&')])\n    return searchParams\n  }\n  /**\n   * Sync shallow updates of the URL with the useOptimisticSearchParams hook.\n   *\n   * By default, the useOptimisticSearchParams hook will only react to internal nuqs updates.\n   * If third party code updates the History API directly, use this function to\n   * enable useOptimisticSearchParams to react to those changes.\n   *\n   * Note: this is actually required in React Router frameworks to follow Link navigations.\n   */\n  applyHistoryPatch(emitter, adapter)\n\n  return {\n    NuqsAdapter: createAdapterProvider(useNuqsReactRouterBasedAdapter),\n    useOptimisticSearchParams\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/next/app.ts",
    "content": "import {\n  createElement,\n  Suspense,\n  type ReactElement,\n  type ReactNode\n} from 'react'\nimport { createAdapterProvider, type AdapterProps } from '../lib/context'\nimport { NavigationSpy, useNuqsNextAppRouterAdapter } from './impl.app'\n\nconst Provider = createAdapterProvider(useNuqsNextAppRouterAdapter)\n\nexport function NuqsAdapter({\n  children,\n  ...adapterProps\n}: AdapterProps & {\n  children: ReactNode\n}): ReactElement {\n  return createElement(Provider, {\n    ...adapterProps,\n    children: [\n      createElement(Suspense, {\n        key: 'nuqs-adapter-suspense-navspy',\n        children: createElement(NavigationSpy)\n      }),\n      children\n    ]\n  })\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/next/impl.app.ts",
    "content": "import { usePathname, useRouter, useSearchParams } from 'next/navigation.js'\nimport {\n  startTransition,\n  useCallback,\n  useEffect,\n  useOptimistic,\n  useRef\n} from 'react'\nimport { debug } from '../../lib/debug'\nimport {\n  resetQueues,\n  setQueueResetMutex,\n  spinQueueResetMutex\n} from '../../lib/queues/reset'\nimport { globalThrottleQueue } from '../../lib/queues/throttle'\nimport { renderQueryString } from '../../lib/url-encoding'\nimport type { AdapterInterface, UpdateUrlFunction } from '../lib/defs'\nimport {\n  historyUpdateMarker,\n  markHistoryAsPatched,\n  shouldPatchHistory\n} from '../lib/patch-history'\n\n// See: https://github.com/47ng/nuqs/issues/603#issuecomment-2317057128\n// and https://github.com/47ng/nuqs/discussions/960#discussioncomment-12699171\nconst NUM_HISTORY_CALLS_PER_UPDATE = 3\n\nfunction onPopState() {\n  setQueueResetMutex(0)\n  resetQueues()\n}\n\nfunction onHistoryStateUpdate() {\n  // Doing this after the end of the current render work because of the error:\n  // \"useInsertionEffect cannot schedule updates\"\n  // (resetting the queue causes the useSyncExternalStore of queued queries\n  // to be marked for rendering)\n  // The useInsertionEffect in question is the one in the Next.js app router core\n  //  dealing with history API calls.\n  spinQueueResetMutex(() => {\n    queueMicrotask(resetQueues)\n  })\n}\n\nfunction patchHistory() {\n  if (!shouldPatchHistory('next/app')) {\n    return\n  }\n  const originalReplaceState = history.replaceState\n  const originalPushState = history.pushState\n  // replaceState: nuqs's own calls carry the marker (stripped before\n  // reaching the browser). Next.js cascade calls (e.g. useInsertionEffect\n  // patching history state after our update) also use replaceState but\n  // WITHOUT the marker, and there is no reliable way to distinguish them\n  // from external replaceState calls. We skip onHistoryStateUpdate for\n  // all replaceState to avoid cascades prematurely resetting the queue.\n  // Trade-off: external history.replaceState() on the same pathname won't\n  // cancel pending nuqs work. Cross-page navigations are still covered\n  // by the pathname-based reset in NavigationSpy below, and pushState-\n  // based navigations (Link clicks, router.push) are covered by the\n  // pushState handler.\n  history.replaceState = function nuqs_replaceState(state, marker, url) {\n    return originalReplaceState.call(\n      history,\n      state,\n      marker === historyUpdateMarker ? '' : marker,\n      url\n    )\n  }\n  // pushState: nuqs's own calls carry the marker (stripped below).\n  // External navigation (link clicks, router.push) uses pushState without\n  // the marker — this triggers queue reset via onHistoryStateUpdate.\n  history.pushState = function nuqs_pushState(state, marker, url) {\n    if (marker !== historyUpdateMarker) {\n      onHistoryStateUpdate()\n    }\n    return originalPushState.call(\n      history,\n      state,\n      marker === historyUpdateMarker ? '' : marker,\n      url\n    )\n  }\n  markHistoryAsPatched('next/app')\n}\n\n// Detect user navigation (clicking links, router calls)\n// and reset the queues when that happens.\nexport function NavigationSpy() {\n  const pathname = usePathname()\n  const prevPathname = useRef(pathname)\n  // Intentionally in the render phase (not an effect): the queue must be\n  // cleared before the new page's components render so they don't read\n  // stale values via getQueuedQuery. This is safe because:\n  // - In StrictMode the second render sees prevPathname === pathname (no-op)\n  // - globalThrottleQueue.reset() is idempotent\n  // - No React state updates are triggered (no useSyncExternalStore emissions)\n  if (prevPathname.current !== pathname) {\n    prevPathname.current = pathname\n    globalThrottleQueue.reset()\n  }\n  useEffect(() => {\n    patchHistory()\n    window.addEventListener('popstate', onPopState)\n    return () => window.removeEventListener('popstate', onPopState)\n  }, [])\n  return null\n}\n\nexport function useNuqsNextAppRouterAdapter(): AdapterInterface {\n  const router = useRouter()\n  const searchParams = useSearchParams()\n  const [optimisticSearchParams, setOptimisticSearchParams] =\n    useOptimistic<URLSearchParams>(searchParams)\n  const updateUrl: UpdateUrlFunction = useCallback((search, options) => {\n    startTransition(() => {\n      if (!options.shallow) {\n        setOptimisticSearchParams(search)\n      }\n      const url = renderURL(search)\n      debug('[nuqs next/app] Updating url: %s', url)\n      // First, update the URL locally without triggering a network request,\n      // this allows keeping a reactive URL if the network is slow.\n      const updateMethod =\n        options.history === 'push' ? history.pushState : history.replaceState\n      // Since replaceState calls are not monitored (see patchHistory above),\n      // the mutex is not needed to absorb cascade calls — they go undetected.\n      // Set to 0 so that the next external pushState immediately resets.\n      setQueueResetMutex(0)\n      updateMethod.call(\n        history,\n        // In next@14.1.0, useSearchParams becomes reactive to shallow updates,\n        // but only if passing `null` as the history state.\n        null,\n        historyUpdateMarker,\n        url\n      )\n      if (options.scroll) {\n        window.scrollTo(0, 0)\n      }\n      if (!options.shallow) {\n        // Call the Next.js router to perform a network request\n        // and re-render server components.\n        router.replace(url, {\n          scroll: false\n        })\n      }\n    })\n  }, [])\n  return {\n    searchParams: optimisticSearchParams,\n    updateUrl,\n    rateLimitFactor: NUM_HISTORY_CALLS_PER_UPDATE,\n    autoResetQueueOnUpdate: false\n  }\n}\n\nfunction renderURL(search: URLSearchParams) {\n  const { origin, pathname, hash } = location\n  return origin + pathname + renderQueryString(search) + hash\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/next/impl.pages.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport {\n  extractDynamicUrlParams,\n  getAsPathPathname,\n  urlSearchParamsToObject\n} from './impl.pages'\n\ndescribe('Next Pages Adapter: getAsPathPathname', () => {\n  it('returns a pure pathname', () => {\n    expect(getAsPathPathname('')).toBe('')\n    expect(getAsPathPathname('/')).toBe('/')\n    expect(getAsPathPathname('/a/b/c')).toBe('/a/b/c')\n  })\n  it('strips search params', () => {\n    expect(getAsPathPathname('/a/b/c?foo=bar')).toBe('/a/b/c')\n    expect(getAsPathPathname('/a/b/c?foo=bar&baz=qux')).toBe('/a/b/c')\n  })\n  it('strips hash', () => {\n    expect(getAsPathPathname('/a/b/c#foo')).toBe('/a/b/c')\n    expect(getAsPathPathname('/a/b/c#foo/bar')).toBe('/a/b/c')\n  })\n  it('strips both search params and hash', () => {\n    expect(getAsPathPathname('/a/b/c?foo=bar#baz')).toBe('/a/b/c')\n    expect(getAsPathPathname('/a/b/c?foo=bar&baz=qux#quux')).toBe('/a/b/c')\n  })\n})\n\ndescribe('Next Pages Adapter: extractDynamicUrlParams', () => {\n  it('returns an empty object when no dynamic params are present', () => {\n    const result = extractDynamicUrlParams('/path/without/params', {\n      ignored: 'gone' // ignored\n    })\n    expect(result).toEqual({})\n  })\n\n  it('returns an object with dynamic params', () => {\n    const result = extractDynamicUrlParams('/path/[foo]/[bar]', {\n      foo: 'bar',\n      bar: 'baz',\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ foo: 'bar', bar: 'baz' })\n  })\n\n  it('maps missing dynamic params to undefined', () => {\n    const result = extractDynamicUrlParams('/path/[foo]/[bar]', {\n      bar: 'baz' // foo is missing\n    })\n    expect(result).toEqual({ foo: undefined, bar: 'baz' })\n  })\n\n  // --\n\n  it('returns an array for catch-all params', () => {\n    const result = extractDynamicUrlParams('/path/[...params]', {\n      params: ['foo', 'bar'],\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ params: ['foo', 'bar'] })\n  })\n\n  // --\n\n  it('returns an empty array for optional catch-all params without values', () => {\n    const result = extractDynamicUrlParams('/path/[[...params]]', {\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ params: [] })\n  })\n\n  it('returns an array with a single item for optional catch-all params with a single value', () => {\n    const result = extractDynamicUrlParams('/path/[[...params]]', {\n      params: ['foo'],\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ params: ['foo'] })\n  })\n\n  it('returns an array with multiple items for optional catch-all params with multiple values', () => {\n    const result = extractDynamicUrlParams('/path/[[...params]]', {\n      params: ['foo', 'bar'],\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ params: ['foo', 'bar'] })\n  })\n\n  // --\n\n  it('supports a combination of dynamic and catch-all params', () => {\n    const result = extractDynamicUrlParams('/path/[foo]/[bar]/[...params]', {\n      foo: 'a',\n      bar: 'b',\n      params: ['baz'],\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ foo: 'a', bar: 'b', params: ['baz'] })\n  })\n\n  it('supports a combination of dynamic and optional catch-all params', () => {\n    const result = extractDynamicUrlParams('/path/[foo]/[bar]/[[...params]]', {\n      foo: 'a',\n      bar: 'b',\n      params: ['baz'],\n      ignored: 'gone'\n    })\n    expect(result).toEqual({ foo: 'a', bar: 'b', params: ['baz'] })\n  })\n})\n\ndescribe('Next Pages Adapter: urlSearchParamsToObject', () => {\n  it('returns an empty object when no search params are present', () => {\n    const result = urlSearchParamsToObject(new URLSearchParams())\n    expect(result).toEqual({})\n  })\n  it('returns an object with a single search param', () => {\n    const result = urlSearchParamsToObject(new URLSearchParams('?foo=bar'))\n    expect(result).toEqual({ foo: 'bar' })\n  })\n  it('returns an object with multiple search params', () => {\n    const result = urlSearchParamsToObject(\n      new URLSearchParams('?foo=bar&baz=qux')\n    )\n    expect(result).toEqual({ foo: 'bar', baz: 'qux' })\n  })\n  it('returns an object with multiple values for a single search param', () => {\n    const result = urlSearchParamsToObject(\n      new URLSearchParams('?foo=bar&foo=baz')\n    )\n    expect(result).toEqual({ foo: ['bar', 'baz'] })\n  })\n  it('returns an object with multiple values for multiple search params', () => {\n    const result = urlSearchParamsToObject(\n      new URLSearchParams('?foo=bar&baz=qux&foo=baz')\n    )\n    expect(result).toEqual({ foo: ['bar', 'baz'], baz: 'qux' })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/adapters/next/impl.pages.ts",
    "content": "import { useRouter } from 'next/compat/router.js'\nimport type { NextRouter } from 'next/router'\nimport { useCallback, useEffect, useMemo } from 'react'\nimport { debug } from '../../lib/debug'\nimport { resetQueues } from '../../lib/queues/reset'\nimport { renderQueryString } from '../../lib/url-encoding'\nimport type { AdapterInterface, UpdateUrlFunction } from '../lib/defs'\n\ndeclare global {\n  interface Window {\n    next?: {\n      router?: NextRouter & {\n        state: {\n          asPath: string\n        }\n      }\n    }\n  }\n}\n\nexport function isPagesRouter(): boolean {\n  return typeof window.next?.router?.state?.asPath === 'string'\n}\n\nlet isNuqsUpdateMutex: boolean = false\n\nfunction onNavigation() {\n  if (isNuqsUpdateMutex) {\n    return\n  }\n  resetQueues()\n}\n\nexport function useNuqsNextPagesRouterAdapter(): AdapterInterface {\n  const router = useRouter()\n\n  useEffect(() => {\n    router?.events.on('routeChangeStart', onNavigation)\n    router?.events.on('beforeHistoryChange', onNavigation)\n    return () => {\n      router?.events.off('routeChangeStart', onNavigation)\n      router?.events.off('beforeHistoryChange', onNavigation)\n    }\n  }, [])\n\n  const searchParams = useMemo(() => {\n    const searchParams = new URLSearchParams()\n    if (router === null) {\n      return searchParams\n    }\n    for (const [key, value] of Object.entries(router.query)) {\n      if (typeof value === 'string') {\n        searchParams.set(key, value)\n      } else if (Array.isArray(value)) {\n        for (const v of value) {\n          searchParams.append(key, v)\n        }\n      }\n    }\n    return searchParams\n  }, [JSON.stringify(router?.query)])\n\n  const updateUrl: UpdateUrlFunction = useCallback((search, options) => {\n    // While the Next.js team doesn't recommend using internals like this,\n    // we need direct access to the pages router, as a bound/closured version from\n    // useRouter may be out of date by the time the updateUrl function is called,\n    // and would also cause updateUrl to not be referentially stable.\n    const nextRouter = window.next?.router!\n    const urlParams = extractDynamicUrlParams(\n      nextRouter.pathname,\n      nextRouter.query\n    )\n    const asPath =\n      getAsPathPathname(nextRouter.asPath) +\n      renderQueryString(search) +\n      location.hash\n    debug('[nuqs next/pages] Updating url: %s', asPath)\n    const method =\n      options.history === 'push' ? nextRouter.push : nextRouter.replace\n    isNuqsUpdateMutex = true\n    method\n      .call(\n        nextRouter,\n        // This is what makes the URL work (mapping dynamic segments placeholders\n        // in pathname to their values in query, plus search params in query too).\n        {\n          pathname: nextRouter.pathname,\n          query: {\n            // Note: we put search params first so that one that conflicts\n            // with dynamic params will be overwritten.\n            ...urlSearchParamsToObject(search),\n            ...urlParams\n          }\n          // For some reason we don't need to pass the hash here,\n          // it's preserved when passed as part of the asPath.\n        },\n        // This is what makes the URL pretty (resolved dynamic segments\n        // and nuqs-formatted search params).\n        asPath,\n        // And these are the options that are passed to the router.\n        {\n          scroll: options.scroll,\n          shallow: options.shallow\n        }\n      )\n      .finally(() => {\n        isNuqsUpdateMutex = false\n      })\n  }, [])\n\n  return {\n    searchParams,\n    updateUrl,\n    autoResetQueueOnUpdate: false\n  }\n}\n\nexport function getAsPathPathname(asPath: string): string {\n  return asPath\n    .replace(/#.*$/, '') // Remove hash\n    .replace(/\\?.*$/, '') // Remove search\n}\n\nexport function urlSearchParamsToObject(\n  search: URLSearchParams\n): Record<string, string | string[]> {\n  const out: Record<string, string | string[]> = {}\n  for (const key of search.keys()) {\n    const values = search.getAll(key)\n    if (values.length === 1) {\n      out[key] = values[0]!\n    } else if (values.length > 1) {\n      out[key] = values\n    }\n  }\n  return out\n}\n\n/**\n * Next.js pages router merges dynamic URL params with search params in its\n * internal state.\n * However, we need to pass just the URL params to the href part of the router\n * update functions.\n * This function finds the dynamic URL params placeholders in the pathname\n * (eg: `/path/[foo]/[bar]`) and extracts the corresponding values from the\n * query state object, leaving out any other search params.\n */\nexport function extractDynamicUrlParams(\n  pathname: string,\n  values: Record<string, string | string[] | undefined>\n): Record<string, string | string[] | undefined> {\n  const paramNames = new Set<string>()\n  const dynamicRegex = /\\[([^\\]]+)\\]/g\n  const catchAllRegex = /\\[\\.{3}([^\\]]+)\\]$/\n  const optionalCatchAllRegex = /\\[\\[\\.{3}([^\\]]+)\\]\\]$/\n\n  let match\n  while ((match = dynamicRegex.exec(pathname)) !== null) {\n    const paramName = match[1]\n    if (paramName) {\n      paramNames.add(paramName)\n    }\n  }\n  const dynamicValues = Object.fromEntries(\n    Object.entries(values).filter(([key]) => paramNames.has(key))\n  )\n  const matchCatchAll = catchAllRegex.exec(pathname)\n  if (matchCatchAll && matchCatchAll[1]) {\n    const key = matchCatchAll[1]\n    dynamicValues[key] = values[key] ?? []\n  }\n  const matchOptionalCatchAll = optionalCatchAllRegex.exec(pathname)\n  if (matchOptionalCatchAll && matchOptionalCatchAll[1]) {\n    const key = matchOptionalCatchAll[1]\n    // Note: while Next.js returns undefined if there are no values for the\n    // optional catch-all, passing undefined back when setting the state\n    // results in the value being set to an empty string.\n    // Passing an empty array instead results in the value remaining undefined.\n    dynamicValues[key] = values[key] ?? []\n  }\n  return dynamicValues\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/next/pages.ts",
    "content": "import { createAdapterProvider, type AdapterProvider } from '../lib/context'\nimport { useNuqsNextPagesRouterAdapter } from './impl.pages'\n\nexport const NuqsAdapter: AdapterProvider = createAdapterProvider(\n  useNuqsNextPagesRouterAdapter\n)\n"
  },
  {
    "path": "packages/nuqs/src/adapters/next.ts",
    "content": "import { createAdapterProvider, type AdapterProvider } from './lib/context'\nimport type { AdapterInterface } from './lib/defs'\nimport { useNuqsNextAppRouterAdapter } from './next/impl.app'\nimport { isPagesRouter, useNuqsNextPagesRouterAdapter } from './next/impl.pages'\n\nfunction useNuqsNextAdapter(): AdapterInterface {\n  const pagesRouterImpl = useNuqsNextPagesRouterAdapter()\n  const appRouterImpl = useNuqsNextAppRouterAdapter()\n  return {\n    searchParams: appRouterImpl.searchParams,\n    updateUrl(search, options) {\n      if (isPagesRouter()) {\n        return pagesRouterImpl.updateUrl(search, options)\n      } else {\n        return appRouterImpl.updateUrl(search, options)\n      }\n    },\n    autoResetQueueOnUpdate: false\n  }\n}\n\nexport const NuqsAdapter: AdapterProvider =\n  createAdapterProvider(useNuqsNextAdapter)\n"
  },
  {
    "path": "packages/nuqs/src/adapters/react-router/v6.ts",
    "content": "import { useNavigate, useSearchParams } from 'react-router-dom'\nimport type { AdapterProvider } from '../lib/context'\nimport { createReactRouterBasedAdapter } from '../lib/react-router'\n\nconst adapter = createReactRouterBasedAdapter({\n  adapter: 'react-router-v6',\n  useNavigate,\n  useSearchParams\n})\n\nexport const NuqsAdapter: AdapterProvider = adapter.NuqsAdapter\nexport const useOptimisticSearchParams: () => URLSearchParams =\n  adapter.useOptimisticSearchParams\n"
  },
  {
    "path": "packages/nuqs/src/adapters/react-router/v7.ts",
    "content": "import { useNavigate, useSearchParams } from 'react-router'\nimport type { AdapterProvider } from '../lib/context'\nimport { createReactRouterBasedAdapter } from '../lib/react-router'\n\nconst adapter = createReactRouterBasedAdapter({\n  adapter: 'react-router-v7',\n  useNavigate,\n  useSearchParams\n})\n\nexport const NuqsAdapter: AdapterProvider = adapter.NuqsAdapter\nexport const useOptimisticSearchParams: () => URLSearchParams =\n  adapter.useOptimisticSearchParams\n"
  },
  {
    "path": "packages/nuqs/src/adapters/react-router.ts",
    "content": "export {\n  /**\n   * @deprecated This import will be removed in nuqs@3.0.0.\n   *\n   * Please pin your version of React Router in the import:\n   * - `nuqs/adapters/react-router/v6`\n   * - `nuqs/adapters/react-router/v7`.\n   *\n   * Note: this deprecated import (`nuqs/adapters/react-router`) is for React Router v6 only.\n   */\n  NuqsAdapter,\n  /**\n   * @deprecated This import will be removed in nuqs@3.0.0.\n   *\n   * Please pin your version of React Router in the import:\n   * - `nuqs/adapters/react-router/v6`\n   * - `nuqs/adapters/react-router/v7`.\n   *\n   * Note: this deprecated import (`nuqs/adapters/react-router`) is for React Router v6 only.\n   */\n  useOptimisticSearchParams\n} from './react-router/v6'\n"
  },
  {
    "path": "packages/nuqs/src/adapters/react.ts",
    "content": "import {\n  createContext,\n  createElement,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  type ReactElement,\n  type ReactNode\n} from 'react'\nimport { debug } from '../lib/debug'\nimport { createEmitter } from '../lib/emitter'\nimport { renderQueryString } from '../lib/url-encoding'\nimport { createAdapterProvider, type AdapterProps } from './lib/context'\nimport type { AdapterInterface, AdapterOptions } from './lib/defs'\nimport { applyChange, filterSearchParams } from './lib/key-isolation'\nimport {\n  historyUpdateMarker,\n  patchHistory,\n  type SearchParamsSyncEmitterEvents\n} from './lib/patch-history'\n\nconst emitter = createEmitter<SearchParamsSyncEmitterEvents>()\n\nfunction generateUpdateUrlFn(fullPageNavigationOnShallowFalseUpdates: boolean) {\n  return function updateUrl(search: URLSearchParams, options: AdapterOptions) {\n    const url = new URL(location.href)\n    url.search = renderQueryString(search)\n    debug('[nuqs react] Updating url: %s', url)\n    if (fullPageNavigationOnShallowFalseUpdates && options.shallow === false) {\n      const method =\n        options.history === 'push' ? location.assign : location.replace\n      method.call(location, url)\n    } else {\n      const method =\n        options.history === 'push' ? history.pushState : history.replaceState\n      method.call(history, history.state, historyUpdateMarker, url)\n    }\n    emitter.emit('update', search)\n    if (options.scroll === true) {\n      window.scrollTo({ top: 0 })\n    }\n  }\n}\n\nconst NuqsReactAdapterContext = createContext({\n  fullPageNavigationOnShallowFalseUpdates: false\n})\n\nfunction useNuqsReactAdapter(watchKeys: string[]): AdapterInterface {\n  const { fullPageNavigationOnShallowFalseUpdates } = useContext(\n    NuqsReactAdapterContext\n  )\n  const [searchParams, setSearchParams] = useState(() => {\n    if (typeof location === 'undefined') {\n      return new URLSearchParams()\n    }\n    return filterSearchParams(\n      new URLSearchParams(location.search),\n      watchKeys,\n      false\n    )\n  })\n  useEffect(() => {\n    // Popstate event is only fired when the user navigates\n    // via the browser's back/forward buttons.\n    const onPopState = () => {\n      setSearchParams(\n        applyChange(new URLSearchParams(location.search), watchKeys, false)\n      )\n    }\n    const onEmitterUpdate = (search: URLSearchParams) => {\n      setSearchParams(applyChange(search, watchKeys, true))\n    }\n    emitter.on('update', onEmitterUpdate)\n    window.addEventListener('popstate', onPopState)\n    return () => {\n      emitter.off('update', onEmitterUpdate)\n      window.removeEventListener('popstate', onPopState)\n    }\n  }, [watchKeys.join('&')])\n  const updateUrl = useMemo(\n    () => generateUpdateUrlFn(fullPageNavigationOnShallowFalseUpdates),\n    [fullPageNavigationOnShallowFalseUpdates]\n  )\n  return {\n    searchParams,\n    updateUrl\n  }\n}\n\nconst NuqsReactAdapter = createAdapterProvider(useNuqsReactAdapter)\n\nexport function NuqsAdapter({\n  children,\n  fullPageNavigationOnShallowFalseUpdates = false,\n  ...adapterProps\n}: AdapterProps & {\n  children: ReactNode\n  fullPageNavigationOnShallowFalseUpdates?: boolean\n}): ReactElement {\n  return createElement(\n    NuqsReactAdapterContext.Provider,\n    { value: { fullPageNavigationOnShallowFalseUpdates } },\n    createElement(NuqsReactAdapter, { ...adapterProps, children })\n  )\n}\n\n/**\n * Opt-in to syncing shallow updates of the URL with the useOptimisticSearchParams hook.\n *\n * By default, the useOptimisticSearchParams hook will only react to internal nuqs updates.\n * If third party code updates the History API directly, use this function to\n * enable useOptimisticSearchParams to react to those changes.\n */\nexport function enableHistorySync(): void {\n  patchHistory(emitter, 'react')\n}\n"
  },
  {
    "path": "packages/nuqs/src/adapters/remix.ts",
    "content": "import { useNavigate, useSearchParams } from '@remix-run/react'\nimport type { AdapterProvider } from './lib/context'\nimport { createReactRouterBasedAdapter } from './lib/react-router'\n\nconst adapter = createReactRouterBasedAdapter({\n  adapter: 'remix',\n  useNavigate,\n  useSearchParams\n})\n\nexport const NuqsAdapter: AdapterProvider = adapter.NuqsAdapter\nexport const useOptimisticSearchParams: () => URLSearchParams =\n  adapter.useOptimisticSearchParams\n"
  },
  {
    "path": "packages/nuqs/src/adapters/tanstack-router.ts",
    "content": "import { useLocation, useRouter, useRouterState } from '@tanstack/react-router'\nimport { startTransition, useCallback, useMemo } from 'react'\nimport { renderQueryString } from '../lib/url-encoding'\nimport { createAdapterProvider, type AdapterProvider } from './lib/context'\nimport type { AdapterInterface, UpdateUrlFunction } from './lib/defs'\n\nfunction useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {\n  const pathname = useLocation({ select: state => state.pathname })\n  // Use useRouterState instead of useLocation so that structuralSharing\n  // is forwarded, stabilizing object references when search values\n  // haven't changed. Prevents infinite re-renders with viewport preloading.\n  // See https://github.com/47ng/nuqs/issues/1363\n  const search = useRouterState({\n    select: state =>\n      Object.fromEntries(\n        Object.entries(state.location.search).filter(([key]) =>\n          watchKeys.includes(key)\n        )\n      ) as Record<string, string | string[]>,\n    structuralSharing: true\n  })\n  const { navigate } = useRouter()\n  const searchParams = useMemo(\n    () =>\n      // search is a Record<string, string | number | object | Array<string | number>>,\n      // so we need to flatten it into a list of key/value pairs,\n      // replicating keys that have multiple values before passing it\n      // to URLSearchParams, otherwise { foo: ['bar', 'baz'] }\n      // ends up as { foo → 'bar,baz' } instead of { foo → 'bar', foo → 'baz' }\n      new URLSearchParams(\n        Object.entries(search).flatMap(([key, value]) => {\n          if (Array.isArray(value)) {\n            return value.map(v => [key, v])\n          } else if (typeof value === 'object' && value !== null) {\n            // TSR JSON.parses objects in the search params,\n            // but parseAsJson expects a JSON string,\n            // so we need to re-stringify it first.\n            return [[key, JSON.stringify(value)]]\n          } else {\n            return [[key, value]]\n          }\n        })\n      ),\n    [search, watchKeys.join(',')]\n  )\n\n  const updateUrl: UpdateUrlFunction = useCallback(\n    (search, options) => {\n      // Wrapping in a startTransition seems to be necessary\n      // to support scroll restoration\n      startTransition(() => {\n        navigate({\n          // I know the docs say to use `search` here, but it would require\n          // userland code to stitch the nuqs definitions to the route declarations\n          // in order for TSR to serialize them, which kind of breaks the\n          // \"works out of the box\" promise, and it also wouldn't support\n          // the custom URL encoding.\n          // TBC if it causes issues with consuming those search params\n          // in other parts of the app.\n          //\n          // Note: we need to specify pathname + search here to avoid TSR appending\n          // a trailing slash to the pathname, see https://github.com/47ng/nuqs/issues/1215\n          from: '/',\n          to: pathname + renderQueryString(search),\n          replace: options.history === 'replace',\n          resetScroll: options.scroll,\n          hash: prevHash => prevHash ?? '',\n          state: state => state\n        })\n      })\n    },\n    [navigate, pathname]\n  )\n\n  return {\n    searchParams,\n    updateUrl,\n    rateLimitFactor: 1\n  }\n}\n\nexport const NuqsAdapter: AdapterProvider = createAdapterProvider(\n  useNuqsTanstackRouterAdapter\n)\n"
  },
  {
    "path": "packages/nuqs/src/adapters/testing.ts",
    "content": "import {\n  createElement,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n  type ReactElement,\n  type ReactNode\n} from 'react'\nimport { resetQueues } from '../lib/queues/reset'\nimport { renderQueryString } from './custom'\nimport { context, type AdapterProps } from './lib/context'\nimport type { AdapterInterface, AdapterOptions } from './lib/defs'\n\nexport type UrlUpdateEvent = {\n  searchParams: URLSearchParams\n  queryString: string\n  options: Required<AdapterOptions>\n}\n\nexport type OnUrlUpdateFunction = (event: UrlUpdateEvent) => void\n\ntype TestingAdapterProps = Pick<AdapterInterface, 'autoResetQueueOnUpdate'> & {\n  /**\n   * An initial value for the search params.\n   */\n  searchParams?: string | Record<string, string> | URLSearchParams\n\n  /**\n   * A function that will be called whenever the URL is updated.\n   * Connect that to a spy in your tests to assert the URL updates.\n   */\n  onUrlUpdate?: OnUrlUpdateFunction\n\n  /**\n   * Internal: enable throttling during tests.\n   *\n   * @default 0 (no throttling)\n   */\n  rateLimitFactor?: number\n\n  /**\n   * Internal: Whether to reset the url update queue on mount.\n   *\n   * Since the update queue is a shared global, each test clears\n   * it on mount to avoid interference between tests.\n   *\n   * @default true\n   */\n  resetUrlUpdateQueueOnMount?: boolean\n\n  /**\n   * If true, the adapter will store the search params in memory and\n   * update that memory on each updateUrl call, to simulate a real adapter.\n   *\n   * Otherwise, the search params will be frozen to the initial value.\n   *\n   * @default false\n   */\n  hasMemory?: boolean\n\n  children: ReactNode\n} & AdapterProps\n\nfunction renderInitialSearchParams(\n  searchParams: TestingAdapterProps['searchParams']\n): string {\n  if (!searchParams) {\n    return ''\n  }\n  if (typeof searchParams === 'string') {\n    return searchParams\n  }\n  if (searchParams instanceof URLSearchParams) {\n    return searchParams.toString()\n  }\n  return new URLSearchParams(searchParams).toString()\n}\n\nexport function NuqsTestingAdapter({\n  resetUrlUpdateQueueOnMount = true,\n  autoResetQueueOnUpdate = true,\n  defaultOptions,\n  processUrlSearchParams,\n  rateLimitFactor = 0,\n  hasMemory = false,\n  onUrlUpdate,\n  children,\n  searchParams: initialSearchParams = ''\n}: TestingAdapterProps): ReactElement {\n  const renderedInitialSearchParams =\n    renderInitialSearchParams(initialSearchParams)\n  // Simulate a central location.search in memory\n  // for the getSearchParamsSnapshot to be referentially stable.\n  const locationSearchRef = useRef(renderedInitialSearchParams)\n  if (resetUrlUpdateQueueOnMount) {\n    resetQueues()\n  }\n  const [searchParams, setSearchParams] = useState(\n    () => new URLSearchParams(locationSearchRef.current)\n  )\n  useEffect(() => {\n    if (!hasMemory) {\n      return\n    }\n    const synced = new URLSearchParams(initialSearchParams)\n    setSearchParams(synced)\n    locationSearchRef.current = synced.toString()\n  }, [hasMemory, renderedInitialSearchParams])\n  const updateUrl = useCallback<AdapterInterface['updateUrl']>(\n    (search, options) => {\n      const queryString = renderQueryString(search)\n      const searchParams = new URLSearchParams(search) // make a copy\n      if (hasMemory) {\n        setSearchParams(searchParams)\n        locationSearchRef.current = queryString\n      }\n      onUrlUpdate?.({\n        searchParams,\n        queryString,\n        options\n      })\n    },\n    [onUrlUpdate, hasMemory]\n  )\n  const getSearchParamsSnapshot = useCallback(() => {\n    return new URLSearchParams(locationSearchRef.current)\n  }, [renderedInitialSearchParams])\n  const useAdapter = (): AdapterInterface => ({\n    searchParams,\n    updateUrl,\n    getSearchParamsSnapshot,\n    rateLimitFactor,\n    autoResetQueueOnUpdate\n  })\n  return createElement(\n    context.Provider,\n    { value: { useAdapter, defaultOptions, processUrlSearchParams } },\n    children\n  )\n}\n\n/**\n * A higher order component that wraps the children with the NuqsTestingAdapter\n *\n * It allows creating wrappers for testing purposes by providing only the\n * necessary props to the NuqsTestingAdapter.\n *\n * Usage:\n * ```tsx\n * render(<MyComponent />, {\n *   wrapper: withNuqsTestingAdapter({ searchParams: '?foo=bar' })\n * })\n * ```\n */\nexport function withNuqsTestingAdapter(\n  props: Omit<TestingAdapterProps, 'children'> = {}\n) {\n  return function NuqsTestingAdapterWrapper({\n    children\n  }: {\n    children: ReactNode\n  }): ReactElement {\n    return createElement(\n      NuqsTestingAdapter,\n      // @ts-expect-error - Ignore missing children error\n      props,\n      children\n    )\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/api.test.ts",
    "content": "import { fileURLToPath } from 'node:url'\nimport { expect, it } from 'vitest'\nimport { getPackageExportsManifest } from 'vitest-package-exports'\n\n// Update the snapshot when updating the API\n// (and adjust the documentation accordingly).\nconst exports = `\n{\n  \".\": {\n    \"createLoader\": \"function\",\n    \"createMultiParser\": \"function\",\n    \"createParser\": \"function\",\n    \"createSerializer\": \"function\",\n    \"createStandardSchemaV1\": \"function\",\n    \"debounce\": \"function\",\n    \"defaultRateLimit\": \"object\",\n    \"parseAsArrayOf\": \"function\",\n    \"parseAsBoolean\": \"object\",\n    \"parseAsFloat\": \"object\",\n    \"parseAsHex\": \"object\",\n    \"parseAsIndex\": \"object\",\n    \"parseAsInteger\": \"object\",\n    \"parseAsIsoDate\": \"object\",\n    \"parseAsIsoDateTime\": \"object\",\n    \"parseAsJson\": \"function\",\n    \"parseAsNativeArrayOf\": \"function\",\n    \"parseAsNumberLiteral\": \"function\",\n    \"parseAsString\": \"object\",\n    \"parseAsStringEnum\": \"function\",\n    \"parseAsStringLiteral\": \"function\",\n    \"parseAsTimestamp\": \"object\",\n    \"throttle\": \"function\",\n    \"useQueryState\": \"function\",\n    \"useQueryStates\": \"function\",\n  },\n  \"./adapters/custom\": {\n    \"renderQueryString\": \"function\",\n    \"unstable_createAdapterProvider\": \"function\",\n  },\n  \"./adapters/next\": {\n    \"NuqsAdapter\": \"function\",\n  },\n  \"./adapters/next/app\": {\n    \"NuqsAdapter\": \"function\",\n  },\n  \"./adapters/next/pages\": {\n    \"NuqsAdapter\": \"function\",\n  },\n  \"./adapters/react\": {\n    \"NuqsAdapter\": \"function\",\n    \"enableHistorySync\": \"function\",\n  },\n  \"./adapters/react-router\": {\n    \"NuqsAdapter\": \"function\",\n    \"useOptimisticSearchParams\": \"function\",\n  },\n  \"./adapters/react-router/v6\": {\n    \"NuqsAdapter\": \"function\",\n    \"useOptimisticSearchParams\": \"function\",\n  },\n  \"./adapters/react-router/v7\": {\n    \"NuqsAdapter\": \"function\",\n    \"useOptimisticSearchParams\": \"function\",\n  },\n  \"./adapters/remix\": {\n    \"NuqsAdapter\": \"function\",\n    \"useOptimisticSearchParams\": \"function\",\n  },\n  \"./adapters/tanstack-router\": {\n    \"NuqsAdapter\": \"function\",\n  },\n  \"./adapters/testing\": {\n    \"NuqsTestingAdapter\": \"function\",\n    \"withNuqsTestingAdapter\": \"function\",\n  },\n  \"./server\": {\n    \"createLoader\": \"function\",\n    \"createMultiParser\": \"function\",\n    \"createParser\": \"function\",\n    \"createSearchParamsCache\": \"function\",\n    \"createSerializer\": \"function\",\n    \"createStandardSchemaV1\": \"function\",\n    \"debounce\": \"function\",\n    \"defaultRateLimit\": \"object\",\n    \"parseAsArrayOf\": \"function\",\n    \"parseAsBoolean\": \"object\",\n    \"parseAsFloat\": \"object\",\n    \"parseAsHex\": \"object\",\n    \"parseAsIndex\": \"object\",\n    \"parseAsInteger\": \"object\",\n    \"parseAsIsoDate\": \"object\",\n    \"parseAsIsoDateTime\": \"object\",\n    \"parseAsJson\": \"function\",\n    \"parseAsNativeArrayOf\": \"function\",\n    \"parseAsNumberLiteral\": \"function\",\n    \"parseAsString\": \"object\",\n    \"parseAsStringEnum\": \"function\",\n    \"parseAsStringLiteral\": \"function\",\n    \"parseAsTimestamp\": \"object\",\n    \"throttle\": \"function\",\n  },\n  \"./testing\": {\n    \"isParserBijective\": \"function\",\n    \"testParseThenSerialize\": \"function\",\n    \"testSerializeThenParse\": \"function\",\n  },\n}\n`\n\nit('has a stable exported API (dist)', async () => {\n  const manifest = await getPackageExportsManifest({\n    importMode: 'dist',\n    cwd: fileURLToPath(import.meta.url)\n  })\n  expect(manifest.exports).toMatchInlineSnapshot(exports)\n})\n"
  },
  {
    "path": "packages/nuqs/src/cache.test.ts",
    "content": "import { describe, expect, it, vi } from 'vitest'\nimport { compareSearchParams, createSearchParamsCache } from './cache'\nimport { parseAsInteger, parseAsString } from './parsers'\n\n// provide a simple mock for React cache\nvi.mock('react', () => {\n  return {\n    cache<T, CachedFunction extends () => T>(fn: CachedFunction) {\n      let cache: T | undefined = undefined\n      function cachedFn() {\n        cache ??= fn()\n        return cache\n      }\n      return cachedFn\n    }\n  }\n})\n\ndescribe('cache', () => {\n  describe('createSearchParamsCache', () => {\n    const input = {\n      string: \"I'm a string\"\n    }\n\n    it('allows parsing the same object multiple times in a request', () => {\n      const cache = createSearchParamsCache({\n        string: parseAsString\n      })\n\n      // parse the input and perform some sanity checks\n      expect(cache.parse(input).string).toBe(input.string)\n      const all = cache.all()\n      expect(all.string).toBe(input.string)\n\n      // second call should be successful and return the same result\n      expect(cache.parse(input).string).toBe(input.string)\n      expect(cache.all()).toBe(all)\n    })\n\n    it('allows parsing the same content with different references', () => {\n      const cache = createSearchParamsCache({\n        string: parseAsString\n      })\n      const copy = { ...input }\n      expect(cache.parse(input).string).toBe(input.string)\n      expect(cache.parse(copy).string).toBe(input.string)\n    })\n\n    it('disallows parsing different objects in a request', () => {\n      const cache = createSearchParamsCache({\n        string: parseAsString\n      })\n      expect(cache.parse(input).string).toBe(input.string)\n      const all = cache.all()\n\n      // second call with different object should fail\n      expect(() => cache.parse({ string: 'I am a different string' })).toThrow()\n\n      // cache still works though\n      expect(cache.all()).toBe(all)\n    })\n\n    it('supports urlKeys', () => {\n      const cache = createSearchParamsCache(\n        {\n          string: parseAsString\n        },\n        {\n          urlKeys: {\n            string: 'str'\n          }\n        }\n      )\n      const parseOutput = cache.parse({\n        str: 'this one is used',\n        string: 'not this one'\n      })\n      expect(parseOutput.string).toBe('this one is used')\n      // @ts-expect-error - Making sure types & runtime are in sync\n      expect(parseOutput.str).toBeUndefined()\n      expect(cache.all().string).toBe('this one is used')\n      expect(cache.get('string')).toBe('this one is used')\n    })\n\n    it('supports partial urlKeys', () => {\n      const cache = createSearchParamsCache(\n        {\n          foo: parseAsString,\n          bar: parseAsString\n        },\n        {\n          urlKeys: {\n            foo: 'f'\n          }\n        }\n      )\n      const parseOutput = cache.parse({\n        f: 'foo',\n        bar: 'bar'\n      })\n      expect(parseOutput.foo).toBe('foo')\n      expect(parseOutput.bar).toBe('bar')\n    })\n\n    it('supports a Promise as input', async () => {\n      const cache = createSearchParamsCache({\n        string: parseAsString\n      })\n      expect((await cache.parse(Promise.resolve(input))).string).toBe(\n        input.string\n      )\n      const all = cache.all()\n      expect(all.string).toBe(input.string)\n    })\n\n    it('does not throw on invalid values when not using strict mode', () => {\n      const cache = createSearchParamsCache({\n        count: parseAsInteger.withDefault(0)\n      })\n      expect(() => cache.parse({ count: 'not-a-number' })).not.toThrow()\n    })\n    it('does not throw on invalid values when not using strict mode', () => {\n      const cache = createSearchParamsCache({\n        count: parseAsInteger.withDefault(0)\n      })\n      expect(() =>\n        cache.parse({ count: 'not-a-number' }, { strict: true })\n      ).toThrow(\n        '[nuqs] Failed to parse query `not-a-number` for key `count` (got null)'\n      )\n    })\n\n    it('supports strict mode for async parsing', async () => {\n      const cache = createSearchParamsCache({\n        count: parseAsInteger.withDefault(0)\n      })\n      await expect(\n        cache.parse(Promise.resolve({ count: 'not-a-number' }), {\n          strict: true\n        })\n      ).rejects.toThrow(\n        '[nuqs] Failed to parse query `not-a-number` for key `count` (got null)'\n      )\n    })\n  })\n\n  describe('compareSearchParams', () => {\n    it('works on empty search params', () => {\n      expect(compareSearchParams({}, {})).toBe(true)\n    })\n    it('rejects different lengths', () => {\n      expect(compareSearchParams({ a: 'a' }, { a: 'a', b: 'b' })).toBe(false)\n    })\n    it('rejects different values', () => {\n      expect(compareSearchParams({ x: 'a' }, { x: 'b' })).toBe(false)\n    })\n    it('does not care about order', () => {\n      expect(compareSearchParams({ x: 'a', y: 'b' }, { y: 'b', x: 'a' })).toBe(\n        true\n      )\n    })\n    it('supports array values (referentially stable)', () => {\n      const array = ['a', 'b']\n      expect(compareSearchParams({ x: array }, { x: array })).toBe(true)\n    })\n    it('does not do deep comparison', () => {\n      expect(compareSearchParams({ x: ['a', 'b'] }, { x: ['a', 'b'] })).toBe(\n        false\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/cache.ts",
    "content": "import * as React from 'react'\nimport type { SearchParams, UrlKeys } from './defs'\nimport { error } from './lib/errors'\nimport { createLoader, type LoaderFunctionOptions } from './loader'\nimport type { inferParserType, ParserMap } from './parsers'\n\nconst $input: unique symbol = Symbol('Input')\n\ntype CacheInterface<Parsers extends ParserMap> = {\n  parse: {\n    /**\n     * Parse the incoming `searchParams` page prop using the parsers provided,\n     * and make it available to the RSC tree.\n     *\n     * @argument searchParams - The `searchParams` prop from the page component.\n     * @argument loaderOptions.strict - When `true`, the loader will throw an error\n     *  if a search params value is invalid for the given parser, rather than falling\n     * back to the parser's default value (or `null` if no default is set).\n     *\n     * @returns The parsed search params for direct use in the page component.\n     *\n     * Note: Next.js 15 introduced a breaking change in making their\n     * `searchParam` prop a Promise. You will need to await this function\n     * to use the Promise version in Next.js 15.\n     */\n    (\n      searchParams: SearchParams,\n      loaderOptions?: LoaderFunctionOptions\n    ): inferParserType<Parsers>\n\n    /**\n     * Parse the incoming `searchParams` page prop using the parsers provided,\n     * and make it available to the RSC tree.\n     *\n     * @argument searchParams - The `searchParams` prop from the page component (Promise).\n     * @argument loaderOptions.strict - When `true`, the Promise returned from the loader\n     * will reject if a search params value is invalid for the given parser,\n     * rather than falling back to the parser's default value (or `null` if no default is set).\n     *\n     * @returns The parsed search params for direct use in the page component.\n     *\n     * Note: this async version requires Next.js 15 or later.\n     */\n    (\n      searchParams: Promise<any>,\n      loaderOptions?: LoaderFunctionOptions\n    ): Promise<inferParserType<Parsers>>\n  }\n  all: () => inferParserType<Parsers>\n  get: <Key extends keyof Parsers>(key: Key) => inferParserType<Parsers[Key]>\n}\n\nexport function createSearchParamsCache<Parsers extends ParserMap>(\n  parsers: Parsers,\n  { urlKeys = {} }: { urlKeys?: UrlKeys<Parsers> } = {}\n): CacheInterface<Parsers> {\n  const load = createLoader(parsers, { urlKeys })\n  type Keys = keyof Parsers\n  type ParsedSearchParams = inferParserType<Parsers>\n\n  type Cache = {\n    searchParams: Partial<ParsedSearchParams>\n    [$input]?: SearchParams\n  }\n\n  // Why not use a good old object here ?\n  // React's `cache` is bound to the render lifecycle of a page,\n  // whereas a simple object would be bound to the lifecycle of the process,\n  // which may be reused between requests in a serverless environment\n  // (warm lambdas on Vercel or AWS).\n  const getCache = React.cache<() => Cache>(() => ({\n    searchParams: {}\n  }))\n\n  function parseSync(\n    searchParams: SearchParams,\n    loaderOptions: LoaderFunctionOptions\n  ): ParsedSearchParams {\n    const c = getCache()\n    if (Object.isFrozen(c.searchParams)) {\n      // Parse has already been called...\n      if (c[$input] && compareSearchParams(searchParams, c[$input])) {\n        // ...but we're being called with the same contents again,\n        // so we can safely return the same cached result (an example of when\n        // this occurs would be if parse was called in generateMetadata as well\n        // as the page itself).\n        return all()\n      }\n      // Different inputs in the same request - fail\n      throw new Error(error(501))\n    }\n    c.searchParams = load(searchParams, loaderOptions)\n    c[$input] = searchParams\n    return Object.freeze(c.searchParams) as ParsedSearchParams\n  }\n\n  function parse(\n    searchParams: SearchParams,\n    loaderOptions?: LoaderFunctionOptions\n  ): ParsedSearchParams\n  function parse(\n    searchParams: Promise<any>,\n    loaderOptions?: LoaderFunctionOptions\n  ): Promise<ParsedSearchParams>\n  function parse(\n    searchParams: SearchParams | Promise<any>,\n    loaderOptions: LoaderFunctionOptions = {}\n  ) {\n    if (searchParams instanceof Promise) {\n      return searchParams.then(searchParams =>\n        parseSync(searchParams, loaderOptions)\n      )\n    }\n    return parseSync(searchParams, loaderOptions)\n  }\n  function all() {\n    const { searchParams } = getCache()\n    if (Object.keys(searchParams).length === 0) {\n      throw new Error(error(500))\n    }\n    return searchParams as ParsedSearchParams\n  }\n  function get<Key extends Keys>(key: Key): ParsedSearchParams[Key] {\n    const { searchParams } = getCache()\n    const entry = searchParams[key]\n    if (typeof entry === 'undefined') {\n      throw new Error(\n        error(500) +\n          `\n  in get(${String(key)})`\n      )\n    }\n    // @ts-ignore\n    return entry\n  }\n  return { parse, get, all }\n}\n\nexport function compareSearchParams(a: SearchParams, b: SearchParams): boolean {\n  if (a === b) {\n    return true\n  }\n  if (Object.keys(a).length !== Object.keys(b).length) {\n    return false\n  }\n  for (const key in a) {\n    if (a[key] !== b[key]) {\n      return false\n    }\n  }\n  return true\n}\n"
  },
  {
    "path": "packages/nuqs/src/defs.ts",
    "content": "import type { TransitionStartFunction } from 'react'\n\nexport type SearchParams = Record<string, string | string[] | undefined>\nexport type HistoryOptions = 'replace' | 'push'\nexport type LimitUrlUpdates =\n  | { method: 'debounce'; timeMs: number }\n  | { method: 'throttle'; timeMs: number }\n\nexport type Options = {\n  /**\n   * How the query update affects page history\n   *\n   * `push` will create a new history entry, allowing to use the back/forward\n   * buttons to navigate state updates.\n   * `replace` (default) will keep the current history point and only replace\n   * the query string.\n   */\n  history?: HistoryOptions\n\n  /**\n   * Scroll to top after a query state update\n   *\n   * Defaults to `false`, unlike the Next.js router page navigation methods.\n   */\n  scroll?: boolean\n\n  /**\n   * Shallow mode (true by default) keeps query states update client-side only,\n   * meaning there won't be calls to the server.\n   *\n   * Setting it to `false` will trigger a network request to the server with\n   * the updated querystring.\n   */\n  shallow?: boolean\n\n  /**\n   * Maximum amount of time (ms) to wait between updates of the URL query string.\n   *\n   * This is to alleviate rate-limiting of the Web History API in browsers,\n   * and defaults to 50ms. Safari requires a higher value of around 120ms.\n   *\n   * Note: the value will be limited to a minimum of 50ms, anything lower\n   * will not have any effect.\n   *\n   * @deprecated use `limitUrlUpdates: { 'method': 'throttle', timeMs: number }`\n   * or use the shorthand:\n   * ```ts\n   * import { throttle } from 'nuqs'\n   *\n   * limitUrlUpdates: throttle(100) // time in ms\n   * ```\n   */\n  throttleMs?: number\n\n  /**\n   * Limit the rate of URL updates to prevent spamming the browser history,\n   * and the server if `shallow: false`.\n   *\n   * This is to alleviate rate-limiting of the Web History API in browsers,\n   * and defaults to 50ms. Safari requires a higher value of around 120ms.\n   *\n   * Note: the value will be limited to a minimum of 50ms, anything lower\n   * will not have any effect.\n   *\n   * If both `throttleMs` and `limitUrlUpdates` are set, `limitUrlUpdates` will\n   * take precedence.\n   */\n  limitUrlUpdates?: LimitUrlUpdates\n\n  /**\n   * In RSC frameworks, opt-in to observing Server Component loading states when\n   * doing non-shallow updates by passing a `startTransition` from the\n   * `React.useTransition()` hook.\n   *\n   * In other frameworks, navigation events triggered by a query update can also\n   * be wrapped in a transition this way (e.g. `React.startTransition`).\n   */\n  startTransition?: TransitionStartFunction\n\n  /**\n   * Clear the key-value pair from the URL query string when setting the state\n   * to the default value.\n   *\n   * Defaults to `true` to keep URLs clean.\n   *\n   * Set it to `false` to keep backwards-compatiblity when the default value\n   * changes (prefer explicit URLs whose meaning don't change).\n   */\n  clearOnDefault?: boolean\n}\n\nexport type Nullable<T> = {\n  [K in keyof T]: T[K] | null\n} & {}\n\n/**\n * Helper type to define and reuse urlKey options to rename search params keys\n *\n * Usage:\n * ```ts\n * import { type UrlKeys } from 'nuqs' // or 'nuqs/server'\n *\n * export const coordinatesSearchParams = {\n *   latitude: parseAsFloat.withDefault(0),\n *   longitude: parseAsFloat.withDefault(0),\n * }\n * export const coordinatesUrlKeys: UrlKeys<typeof coordinatesSearchParams> = {\n *   latitude: 'lat',\n *   longitude: 'lng',\n * }\n *\n * // Later in the code:\n * useQueryStates(coordinatesSearchParams, {\n *   urlKeys: coordinatesUrlKeys\n * })\n * createSerializer(coordinatesSearchParams, {\n *   urlKeys: coordinatesUrlKeys\n * })\n * createSearchParamsCache(coordinatesSearchParams, {\n *   urlKeys: coordinatesUrlKeys\n * })\n * ```\n */\nexport type UrlKeys<Parsers extends Record<string, any>> = Partial<\n  Record<keyof Parsers, string>\n>\n"
  },
  {
    "path": "packages/nuqs/src/index.server.ts",
    "content": "export { createSearchParamsCache } from './cache'\nexport type {\n  HistoryOptions,\n  Nullable,\n  Options,\n  SearchParams,\n  UrlKeys\n} from './defs'\nexport {\n  debounce,\n  defaultRateLimit,\n  throttle\n} from './lib/queues/rate-limiting'\nexport {\n  createLoader,\n  type LoaderFunction,\n  type LoaderInput,\n  type LoaderOptions,\n  type CreateLoaderOptions\n} from './loader'\nexport * from './parsers'\nexport { createSerializer, type CreateSerializerOptions } from './serializer'\nexport { createStandardSchemaV1 } from './standard-schema'\n"
  },
  {
    "path": "packages/nuqs/src/index.ts",
    "content": "export type {\n  HistoryOptions,\n  Nullable,\n  Options,\n  SearchParams,\n  UrlKeys\n} from './defs'\nexport {\n  debounce,\n  defaultRateLimit,\n  throttle\n} from './lib/queues/rate-limiting'\nexport {\n  createLoader,\n  type LoaderFunction,\n  type LoaderInput,\n  type LoaderOptions,\n  type CreateLoaderOptions\n} from './loader'\nexport * from './parsers'\nexport { createSerializer, type CreateSerializerOptions } from './serializer'\nexport { createStandardSchemaV1 } from './standard-schema'\nexport * from './useQueryState'\nexport * from './useQueryStates'\n"
  },
  {
    "path": "packages/nuqs/src/lib/compare.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { compareQuery } from './compare'\n\ndescribe('compare', () => {\n  describe('strings', () => {\n    it('should return true for equal values', () => {\n      expect(compareQuery('a', 'a')).toBe(true)\n    })\n    it('should return false for different strings', () => {\n      expect(compareQuery('a', 'b')).toBe(false)\n    })\n  })\n  describe('arrays', () => {\n    it('should return true for equal arrays', () => {\n      expect(compareQuery(['a', 'b'], ['a', 'b'])).toBe(true)\n    })\n    it('should return true for same array instance', () => {\n      const arr = ['a', 'b']\n      expect(compareQuery(arr, arr)).toBe(true)\n    })\n    it('should return false for different arrays', () => {\n      expect(compareQuery(['a', 'b'], ['a', 'c'])).toBe(false)\n    })\n    it('should return false for different length arrays', () => {\n      expect(compareQuery(['a', 'b'], ['a', 'b', 'c'])).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/compare.ts",
    "content": "import type { Query } from './search-params'\n\nexport function compareQuery<T extends Query>(\n  a: T | null,\n  b: T | null\n): boolean {\n  if (a === b) {\n    return true // Referentially stable\n  }\n  if (a === null || b === null) {\n    return false\n  }\n  // we expect either strings or arrays, not a mix of both\n  if (typeof a === 'string' || typeof b === 'string') {\n    return false\n  }\n\n  if (a.length !== b.length) {\n    return false\n  }\n\n  return a.every((value, index) => value === b[index]!)\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/compose.test.ts",
    "content": "import { describe, expect, it, vi } from 'vitest'\nimport { compose } from './compose'\n\ndescribe('queues: compose', () => {\n  it('handles an empty array', () => {\n    const final = vi.fn()\n    compose([], final)\n    expect(final).toHaveBeenCalledOnce()\n  })\n  it('handles one item, calling it before the final', () => {\n    const a = vi\n      .fn()\n      .mockImplementation(x => x())\n      .mockName('a')\n    const final = vi.fn()\n    compose([a], final)\n    expect(a).toHaveBeenCalledOnce()\n    expect(final).toHaveBeenCalledOnce()\n    expect(a.mock.invocationCallOrder[0]).toBeLessThan(\n      final.mock.invocationCallOrder[0]!\n    )\n  })\n  it('composes several items, calling them in order', () => {\n    const a = vi.fn().mockImplementation(x => x())\n    const b = vi.fn().mockImplementation(x => x())\n    const c = vi.fn().mockImplementation(x => x())\n    const final = vi.fn()\n    compose([a, b, c], final)\n    expect(a).toHaveBeenCalledOnce()\n    expect(b).toHaveBeenCalledOnce()\n    expect(c).toHaveBeenCalledOnce()\n    expect(final).toHaveBeenCalledOnce()\n    expect(a.mock.invocationCallOrder[0]).toBeLessThan(\n      b.mock.invocationCallOrder[0]!\n    )\n    expect(b.mock.invocationCallOrder[0]).toBeLessThan(\n      c.mock.invocationCallOrder[0]!\n    )\n    expect(c.mock.invocationCallOrder[0]).toBeLessThan(\n      final.mock.invocationCallOrder[0]!\n    )\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/compose.ts",
    "content": "export function compose(\n  fns: React.TransitionStartFunction[],\n  final: () => void\n): void {\n  // Build a nested callback chain iteratively (avoids recursion helper)\n  let next = final\n  for (let i = fns.length - 1; i >= 0; i--) {\n    const fn = fns[i]\n    if (!fn) continue\n    const prev = next\n    next = () => fn(prev)\n  }\n  next()\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/debug.test.ts",
    "content": "import { describe, expect, it, vi } from 'vitest'\nimport { sprintf } from './debug'\n\ndescribe('debug/server (DEBUG env)', () => {\n  it('enables when DEBUG includes nuqs', async () => {\n    vi.stubEnv('DEBUG', 'nuqs')\n    vi.resetModules()\n    const { debugEnabled } = await import('./debug')\n    expect(debugEnabled).toBe(true)\n  })\n\n  it('enables when DEBUG contains nuqs among others', async () => {\n    vi.stubEnv('DEBUG', '*,nuqs,other')\n    vi.resetModules()\n    const { debugEnabled } = await import('./debug')\n    expect(debugEnabled).toBe(true)\n  })\n\n  it('disables when DEBUG is unset', async () => {\n    vi.stubEnv('DEBUG', '')\n    vi.resetModules()\n    const { debugEnabled } = await import('./debug')\n    expect(debugEnabled).toBe(false)\n  })\n\n  it('disables when DEBUG does not include nuqs', async () => {\n    vi.stubEnv('DEBUG', 'other,*')\n    vi.resetModules()\n    const { debugEnabled } = await import('./debug')\n    expect(debugEnabled).toBe(false)\n  })\n})\n\ndescribe('debug/sprintf', () => {\n  it('formats strings with %s', () => {\n    expect(sprintf('%s', 'foo')).toBe('foo')\n    expect(sprintf('%s', 1)).toBe('1')\n    expect(sprintf('%s', true)).toBe('true')\n    expect(sprintf('%s', null)).toBe('null')\n    expect(sprintf('%s', undefined)).toBe('undefined')\n    expect(sprintf('%s', {})).toBe('[object Object]')\n    expect(sprintf('%s', [])).toBe('')\n  })\n  it('formats integers with %d', () => {\n    expect(sprintf('%d', 1)).toBe('1')\n    expect(sprintf('%d', 1.5)).toBe('1.5')\n    expect(sprintf('%d', '1')).toBe('1')\n    expect(sprintf('%d', '1.5')).toBe('1.5')\n    expect(sprintf('%d', true)).toBe('true')\n    expect(sprintf('%d', false)).toBe('false')\n    expect(sprintf('%d', null)).toBe('null')\n    expect(sprintf('%d', undefined)).toBe('undefined')\n    expect(sprintf('%d', {})).toBe('[object Object]')\n    expect(sprintf('%d', [])).toBe('')\n  })\n  it('formats floats with %f', () => {\n    expect(sprintf('%f', 1)).toBe('1')\n    expect(sprintf('%f', 1.5)).toBe('1.5')\n    expect(sprintf('%f', '1')).toBe('1')\n    expect(sprintf('%f', '1.5')).toBe('1.5')\n    expect(sprintf('%f', true)).toBe('true')\n    expect(sprintf('%f', false)).toBe('false')\n    expect(sprintf('%f', null)).toBe('null')\n    expect(sprintf('%f', undefined)).toBe('undefined')\n    expect(sprintf('%f', {})).toBe('[object Object]')\n    expect(sprintf('%f', [])).toBe('')\n  })\n  it('formats objects with %O', () => {\n    expect(sprintf('%O', 'foo')).toBe('\"foo\"')\n    expect(sprintf('%O', 1)).toBe('1')\n    expect(sprintf('%O', true)).toBe('true')\n    expect(sprintf('%O', null)).toBe('null')\n    expect(sprintf('%O', undefined)).toBe('undefined')\n    expect(sprintf('%O', {})).toBe('{}')\n    expect(sprintf('%O', [])).toBe('[]')\n    expect(sprintf('%O', { hello: 'world' })).toBe('{hello:\"world\"}')\n  })\n  it('formats multiple arguments', () => {\n    expect(sprintf('%s %O', 'foo', { hello: 'world' })).toBe(\n      'foo {hello:\"world\"}'\n    )\n    expect(sprintf('%O %s', { hello: 'world' }, 'foo')).toBe(\n      '{hello:\"world\"} foo'\n    )\n  })\n  it('supports mismatching numbers of arguments and placeholders', () => {\n    expect(sprintf('%s %s', 'foo')).toBe('foo undefined')\n    expect(sprintf('%s %s', 'foo', 'bar', 'baz')).toBe('foo bar')\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/debug.ts",
    "content": "export const debugEnabled: boolean = isDebugEnabled()\n\nexport function debug(message: string, ...args: any[]): void {\n  if (!debugEnabled) {\n    return\n  }\n  const msg = sprintf(message, ...args)\n  performance.mark(msg)\n  try {\n    // Handle React Devtools not being able to console.log('%s', null)\n    console.log(message, ...args)\n  } catch {\n    console.log(msg)\n  }\n}\n\nexport function warn(message: string, ...args: any[]): void {\n  if (!debugEnabled) {\n    return\n  }\n  console.warn(message, ...args)\n}\n\nexport function sprintf(base: string, ...args: any[]): string {\n  return base.replace(/%[sfdO]/g, match => {\n    const arg = args.shift()\n    return match === '%O' && arg\n      ? JSON.stringify(arg).replace(/\"([^\"]+)\":/g, '$1:')\n      : String(arg)\n  })\n}\n\nfunction isDebugEnabled(): boolean {\n  // Issue: https://github.com/47ng/nuqs/issues/1336\n  // Backend (Node/server): use DEBUG env var, never touch localStorage.\n  // --localstorage-file triggers a warning.\n  if (typeof window === 'undefined') {\n    return (process.env.DEBUG || '').includes('nuqs')\n  }\n\n  // Check if localStorage is available.\n  // It may be unavailable in some environments,\n  // like Safari in private browsing mode.\n  // See https://github.com/47ng/nuqs/pull/588\n  try {\n    const test = 'nuqs-localStorage-test'\n    if (typeof localStorage === 'undefined') {\n      return false\n    }\n    localStorage.setItem(test, test)\n    const isStorageAvailable = localStorage.getItem(test) === test\n    localStorage.removeItem(test)\n    return (\n      isStorageAvailable &&\n      (localStorage.getItem('debug') || '').includes('nuqs')\n    )\n  } catch {\n    return false\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/emitter.test.ts",
    "content": "import { describe, expect, it, vi } from 'vitest'\nimport { createEmitter } from './emitter'\n\ntype Events = {\n  test: string\n}\n\ndescribe('emitter', () => {\n  it('allows subscribing to events', () => {\n    const emitter = createEmitter<Events>()\n    const handler = vi.fn()\n    emitter.on('test', handler)\n    emitter.emit('test', 'pass')\n    expect(handler).toHaveBeenCalledExactlyOnceWith('pass')\n  })\n  it('allows unsubscribing from events from the returned callback', () => {\n    const emitter = createEmitter<Events>()\n    const handler = vi.fn()\n    const unsubscribe = emitter.on('test', handler)\n    unsubscribe()\n    emitter.emit('test', 'pass')\n    expect(handler).not.toHaveBeenCalled()\n  })\n  it('allows unsubscribing from events from an off method', () => {\n    const emitter = createEmitter<Events>()\n    const handler = vi.fn()\n    emitter.on('test', handler)\n    emitter.off('test', handler)\n    emitter.emit('test', 'pass')\n    expect(handler).not.toHaveBeenCalled()\n  })\n  it('allows emitting events with no payload', () => {\n    const emitter = createEmitter<{ test: never }>()\n    const handler = vi.fn()\n    emitter.on('test', handler)\n    emitter.emit('test')\n    expect(handler).toHaveBeenCalledExactlyOnceWith(undefined)\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/emitter.ts",
    "content": "export type Emitter<Events extends Record<string, unknown>> = {\n  on<Key extends keyof Events>(\n    type: Key,\n    handler: (event: Events[Key]) => any\n  ): () => void\n  off<Key extends keyof Events>(\n    type: Key,\n    handler?: (event: Events[Key]) => any\n  ): void\n  emit<Key extends keyof Events>(\n    type: Key,\n    event?: Events[Key] extends undefined ? never : Events[Key]\n  ): void\n}\n\nexport function createEmitter<\n  Events extends Record<string, unknown>\n>(): Emitter<Events> {\n  const all: Map<\n    keyof Events,\n    Array<(event: Events[keyof Events]) => any>\n  > = new Map()\n  return {\n    on<Key extends keyof Events>(\n      type: Key,\n      handler: (event: Events[Key]) => any\n    ): () => void {\n      const handlers = all.get(type) || []\n      handlers.push(handler as (event: Events[keyof Events]) => any)\n      all.set(type, handlers)\n      return () => this.off(type, handler)\n    },\n    off<Key extends keyof Events>(\n      type: Key,\n      handler: (event: Events[Key]) => any\n    ): void {\n      const handlers = all.get(type)\n      if (handlers) {\n        all.set(\n          type,\n          handlers.filter(h => h !== handler)\n        )\n      }\n    },\n    emit<Key extends keyof Events>(\n      type: Key,\n      event?: Events[Key] extends undefined ? never : Events[Key]\n    ): void {\n      const handlers = all.get(type)\n      handlers?.forEach(handler => handler(event as Events[keyof Events]))\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/errors.ts",
    "content": "export const errors = {\n  303: 'Multiple adapter contexts detected. This might happen in monorepos.',\n  404: 'nuqs requires an adapter to work with your framework.',\n  409: 'Multiple versions of the library are loaded. This may lead to unexpected behavior. Currently using `%s`, but `%s` (via the %s adapter) was about to load on top.',\n  414: 'Max safe URL length exceeded. Some browsers may not be able to accept this URL. Consider limiting the amount of state stored in the URL.',\n  422: 'Invalid options combination: `limitUrlUpdates: debounce` should be used in SSR scenarios, with `shallow: false`',\n  429: 'URL update rate-limited by the browser. Consider increasing `throttleMs` for key(s) `%s`. %O',\n  500: \"Empty search params cache. Search params can't be accessed in Layouts.\",\n  501: 'Search params cache already populated. Have you called `parse` twice?'\n} as const\n\nexport function error(code: keyof typeof errors) {\n  return `[nuqs] ${errors[code]}\n  See https://nuqs.dev/NUQS-${code}`\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/debounce.test.ts",
    "content": "import { setTimeout } from 'node:timers/promises'\nimport { describe, expect, it, vi } from 'vitest'\nimport type { UpdateUrlFunction } from '../../adapters/lib/defs'\nimport { DebounceController, DebouncedPromiseQueue } from './debounce'\nimport { ThrottledQueue, type UpdateQueueAdapterContext } from './throttle'\n\nasync function passThrough<T>(value: T): Promise<T> {\n  return value\n}\n\ndescribe('debounce: DebouncedPromiseQueue', () => {\n  it('calls the callback after the timer expired', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn().mockResolvedValue('output')\n    const queue = new DebouncedPromiseQueue(spy)\n    queue.push('value', 100)\n    vi.advanceTimersToNextTimer()\n    expect(spy).toHaveBeenCalledExactlyOnceWith('value')\n  })\n  it('debounces the queue', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn().mockResolvedValue('output')\n    const queue = new DebouncedPromiseQueue(spy)\n    queue.push('a', 100)\n    queue.push('b', 100)\n    queue.push('c', 100)\n    vi.advanceTimersToNextTimer()\n    expect(spy).toHaveBeenCalledExactlyOnceWith('c')\n  })\n  it('returns a stable promise to the next time the callback is called', async () => {\n    vi.useFakeTimers()\n    const queue = new DebouncedPromiseQueue(passThrough)\n    const p1 = queue.push('a', 100)\n    const p2 = queue.push('b', 100)\n    expect(p1).toBe(p2)\n    vi.advanceTimersToNextTimer()\n    await expect(p1).resolves.toBe('b')\n  })\n  it('returns a new Promise once the callback is called', async () => {\n    vi.useFakeTimers()\n    let count = 0\n    const queue = new DebouncedPromiseQueue(() => Promise.resolve(count++))\n    const p1 = queue.push('value', 100)\n    vi.advanceTimersToNextTimer()\n    await expect(p1).resolves.toBe(0)\n    const p2 = queue.push('value', 100)\n    expect(p2).not.toBe(p1)\n    vi.advanceTimersToNextTimer()\n    await expect(p2).resolves.toBe(1)\n  })\n  it('keeps a record of the last queued value', async () => {\n    vi.useFakeTimers()\n    const queue = new DebouncedPromiseQueue(passThrough)\n    const p = queue.push('a', 100)\n    expect(queue.queuedValue).toBe('a')\n    vi.advanceTimersToNextTimer()\n    await expect(p).resolves.toBe('a')\n    expect(queue.queuedValue).toBeUndefined()\n  })\n  it('clears the queued value when the callback returns its promise (not when it resolves)', () => {\n    vi.useFakeTimers()\n    const queue = new DebouncedPromiseQueue(async input => {\n      await setTimeout(100)\n      return input\n    })\n    queue.push('a', 100)\n    vi.advanceTimersByTime(100)\n    expect(queue.queuedValue).toBeUndefined()\n  })\n  it('clears the queued value when the callback throws an error synchronously', async () => {\n    vi.useFakeTimers()\n    const queue = new DebouncedPromiseQueue(() => {\n      throw new Error('error')\n    })\n    const p = queue.push('a', 100)\n    vi.advanceTimersToNextTimer()\n    expect(queue.queuedValue).toBeUndefined()\n    await expect(p).rejects.toThrowError('error')\n  })\n  it('clears the queued value when the callback rejects', async () => {\n    vi.useFakeTimers()\n    const queue = new DebouncedPromiseQueue(() =>\n      Promise.reject(new Error('error'))\n    )\n    const p = queue.push('a', 100)\n    vi.advanceTimersToNextTimer()\n    expect(queue.queuedValue).toBeUndefined()\n    await expect(p).rejects.toThrowError('error')\n  })\n  it('returns a new Promise when an update is pushed while the callback is pending', async () => {\n    vi.useFakeTimers()\n    const queue = new DebouncedPromiseQueue(async input => {\n      await setTimeout(100)\n      return input\n    })\n    const p1 = queue.push('a', 100)\n    vi.advanceTimersByTime(150) // 100ms debounce + half the callback settle time\n    const p2 = queue.push('b', 100)\n    expect(p1).not.toBe(p2)\n    vi.advanceTimersToNextTimer()\n    await expect(p1).resolves.toBe('a')\n    await expect(p2).resolves.toBe('b')\n  })\n})\n\ndescribe('debounce: DebounceController', () => {\n  it('schedules an update and calls the adapter with it', async () => {\n    vi.useFakeTimers()\n    const fakeAdapter: UpdateQueueAdapterContext = {\n      updateUrl: vi.fn<UpdateUrlFunction>(),\n      getSearchParamsSnapshot() {\n        return new URLSearchParams()\n      }\n    }\n    const controller = new DebounceController()\n    const promise = controller.push(\n      {\n        key: 'key',\n        query: 'value',\n        options: {}\n      },\n      100,\n      fakeAdapter\n    )\n    const queue = controller.queues.get('key')\n    expect(queue).toBeInstanceOf(DebouncedPromiseQueue)\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams('?key=value'))\n    expect(fakeAdapter.updateUrl).toHaveBeenCalledExactlyOnceWith(\n      new URLSearchParams('?key=value'),\n      {\n        history: 'replace',\n        scroll: false,\n        shallow: true\n      }\n    )\n  })\n  it('isolates debounce queues per key', async () => {\n    vi.useFakeTimers()\n    const fakeAdapter: UpdateQueueAdapterContext = {\n      updateUrl: vi.fn<UpdateUrlFunction>(),\n      getSearchParamsSnapshot() {\n        return new URLSearchParams()\n      }\n    }\n    const controller = new DebounceController()\n    const promise1 = controller.push(\n      {\n        key: 'a',\n        query: 'a',\n        options: {}\n      },\n      100,\n      fakeAdapter\n    )\n    const promise2 = controller.push(\n      {\n        key: 'b',\n        query: 'b',\n        options: {}\n      },\n      200,\n      fakeAdapter\n    )\n    expect(promise1).not.toBe(promise2)\n    vi.runAllTimers()\n    await expect(promise1).resolves.toEqual(new URLSearchParams('?a=a'))\n    await expect(promise2).resolves.toEqual(new URLSearchParams('?b=b'))\n    expect(fakeAdapter.updateUrl).toHaveBeenCalledTimes(2)\n  })\n  it('keeps a record of pending updates', async () => {\n    vi.useFakeTimers()\n    const fakeAdapter: UpdateQueueAdapterContext = {\n      updateUrl: vi.fn<UpdateUrlFunction>(),\n      getSearchParamsSnapshot() {\n        return new URLSearchParams()\n      }\n    }\n    const throttleQueue = new ThrottledQueue()\n    const controller = new DebounceController(throttleQueue)\n    controller.push(\n      {\n        key: 'key',\n        query: 'value',\n        options: {}\n      },\n      100,\n      fakeAdapter\n    )\n    expect(controller.getQueuedQuery('key')).toEqual('value')\n    vi.runAllTimers()\n    expect(controller.getQueuedQuery('key')).toBeUndefined()\n    throttleQueue.reset()\n    expect(controller.getQueuedQuery('key')).toBeUndefined()\n  })\n  it('falls back to the throttle queue pending values if nothing is debounced', () => {\n    const throttleQueue = new ThrottledQueue()\n    throttleQueue.push({\n      key: 'key',\n      query: 'value',\n      options: {}\n    })\n    const controller = new DebounceController(throttleQueue)\n    expect(controller.getQueuedQuery('key')).toEqual('value')\n  })\n  it('aborts an update and chains the Promise onto another one that overrides it', async () => {\n    vi.useFakeTimers()\n    const fakeAdapter: UpdateQueueAdapterContext = {\n      updateUrl: vi.fn<UpdateUrlFunction>(),\n      getSearchParamsSnapshot() {\n        return new URLSearchParams()\n      }\n    }\n    const controller = new DebounceController()\n    const debouncedPromise = controller.push(\n      {\n        key: 'key',\n        query: 'value',\n        options: {}\n      },\n      100,\n      fakeAdapter\n    )\n    const attach = controller.abort('key')\n    expect(attach).toBeInstanceOf(Function)\n    vi.runAllTimers()\n    const resolvedPromise = Promise.resolve(\n      new URLSearchParams('?key=override')\n    )\n    const attachedPromise = attach(resolvedPromise)\n    expect(attachedPromise).toBe(resolvedPromise) // Referential equality\n    await expect(debouncedPromise).resolves.toEqual(\n      new URLSearchParams('?key=override')\n    )\n  })\n  it('does not queue an update with a timeout of Infinity', async () => {\n    const fakeAdapter: UpdateQueueAdapterContext = {\n      updateUrl: vi.fn<UpdateUrlFunction>(),\n      getSearchParamsSnapshot() {\n        return new URLSearchParams('?test=init')\n      }\n    }\n    const controller = new DebounceController()\n    const promise = controller.push(\n      {\n        key: 'key',\n        query: 'value',\n        options: {}\n      },\n      Infinity,\n      fakeAdapter\n    )\n    expect(controller.getQueuedQuery('key')).toBeUndefined()\n    await expect(promise).resolves.toEqual(new URLSearchParams('?test=init'))\n  })\n  it('aborts all pending queues', async () => {\n    const fakeAdapter: UpdateQueueAdapterContext = {\n      updateUrl: vi.fn<UpdateUrlFunction>(),\n      getSearchParamsSnapshot() {\n        return new URLSearchParams('?test=init')\n      }\n    }\n    const throttleQueue = new ThrottledQueue()\n    const controller = new DebounceController(throttleQueue)\n    const promise = controller.push(\n      {\n        key: 'key',\n        query: 'value',\n        options: {}\n      },\n      100,\n      fakeAdapter\n    )\n    const queue = controller.queues.get('key')\n    expect(queue).toBeInstanceOf(DebouncedPromiseQueue)\n    controller.abortAll()\n    expect(controller.queues.size).toEqual(0)\n    expect(queue!.queuedValue).toBeUndefined()\n    await expect(promise).resolves.toEqual(new URLSearchParams())\n    await expect(queue!.resolvers.promise).resolves.toEqual(\n      new URLSearchParams()\n    )\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/debounce.ts",
    "content": "import { debug } from '../debug'\nimport { createEmitter, type Emitter } from '../emitter'\nimport type { Query } from '../search-params'\nimport { timeout } from '../timeout'\nimport { withResolvers, type Resolvers } from '../with-resolvers'\nimport {\n  getSearchParamsSnapshotFromLocation,\n  globalThrottleQueue,\n  ThrottledQueue,\n  type UpdateQueueAdapterContext,\n  type UpdateQueuePushArgs\n} from './throttle'\nimport { useSyncExternalStores } from './useSyncExternalStores'\n\nexport class DebouncedPromiseQueue<ValueType, OutputType> {\n  callback: (value: ValueType) => Promise<OutputType>\n  resolvers: Resolvers<OutputType> = withResolvers<OutputType>()\n  controller: AbortController = new AbortController()\n  queuedValue: ValueType | undefined = undefined\n\n  constructor(callback: (value: ValueType) => Promise<OutputType>) {\n    this.callback = callback\n  }\n\n  abort(): void {\n    this.controller.abort()\n    this.queuedValue = undefined\n  }\n\n  push(value: ValueType, timeMs: number): Promise<OutputType> {\n    this.queuedValue = value\n    this.controller.abort()\n    this.controller = new AbortController()\n    timeout(\n      () => {\n        // Keep the resolvers in a separate variable to reset the queue\n        // while the callback is pending, so that the next push can be\n        // assigned to a new Promise (and not dropped).\n        const outputResolvers = this.resolvers\n        try {\n          debug('[nuqs dq] Flushing debounce queue', value)\n          const callbackPromise = this.callback(value)\n          debug('[nuqs dq] Reset debounce queue %O', this.queuedValue)\n          this.queuedValue = undefined\n          this.resolvers = withResolvers<OutputType>()\n          callbackPromise\n            .then(output => outputResolvers.resolve(output))\n            .catch(error => outputResolvers.reject(error))\n        } catch (error) {\n          this.queuedValue = undefined\n          outputResolvers.reject(error)\n        }\n      },\n      timeMs,\n      this.controller.signal\n    )\n    return this.resolvers.promise\n  }\n}\n\n// --\n\ntype DebouncedUpdateQueue = DebouncedPromiseQueue<\n  Omit<UpdateQueuePushArgs, 'timeMs'>,\n  URLSearchParams\n>\n\nexport class DebounceController {\n  throttleQueue: ThrottledQueue\n  queues: Map<string, DebouncedUpdateQueue> = new Map()\n  queuedQuerySync: Emitter<Record<string, undefined>> = createEmitter()\n\n  constructor(throttleQueue: ThrottledQueue = new ThrottledQueue()) {\n    this.throttleQueue = throttleQueue\n  }\n\n  useQueuedQueries(keys: string[]): Record<string, Query | null | undefined> {\n    return useSyncExternalStores(\n      keys,\n      (key, callback) => this.queuedQuerySync.on(key, callback),\n      (key: string) => this.getQueuedQuery(key)\n    )\n  }\n\n  push(\n    update: Omit<UpdateQueuePushArgs, 'timeMs'>,\n    timeMs: number,\n    adapter: UpdateQueueAdapterContext,\n    processUrlSearchParams?: (search: URLSearchParams) => URLSearchParams\n  ): Promise<URLSearchParams> {\n    if (!Number.isFinite(timeMs)) {\n      const getSnapshot =\n        adapter.getSearchParamsSnapshot ?? getSearchParamsSnapshotFromLocation\n      return Promise.resolve(getSnapshot())\n    }\n    const key = update.key\n    if (!this.queues.has(key)) {\n      debug('[nuqs dqc] Creating debounce queue for `%s`', key)\n      const queue = new DebouncedPromiseQueue<\n        Omit<UpdateQueuePushArgs, 'timeMs'>,\n        URLSearchParams\n      >(update => {\n        this.throttleQueue.push(update)\n        return this.throttleQueue\n          .flush(adapter, processUrlSearchParams)\n          .finally(() => {\n            const queuedValue = this.queues.get(update.key)?.queuedValue\n            if (queuedValue === undefined) {\n              debug('[nuqs dqc] Cleaning up empty queue for `%s`', update.key)\n              this.queues.delete(update.key)\n            }\n            this.queuedQuerySync.emit(update.key)\n          })\n      })\n      this.queues.set(key, queue)\n    }\n    debug('[nuqs dqc] Enqueueing debounce update %O', update)\n    const promise = this.queues.get(key)!.push(update, timeMs)\n    this.queuedQuerySync.emit(key)\n    return promise\n  }\n\n  abort(\n    key: string\n  ): (promise: Promise<URLSearchParams>) => Promise<URLSearchParams> {\n    const queue = this.queues.get(key)\n    if (!queue) {\n      return passThrough => passThrough\n    }\n    debug(\n      '[nuqs dqc] Aborting debounce queue %s=%s',\n      key,\n      queue.queuedValue?.query\n    )\n    this.queues.delete(key)\n    queue.abort() // Don't run to completion\n    this.queuedQuerySync.emit(key)\n    return promise => {\n      promise.then(queue.resolvers.resolve, queue.resolvers.reject)\n      // Don't chain: keep reference equality\n      return promise\n    }\n  }\n\n  abortAll(): void {\n    for (const [key, queue] of this.queues.entries()) {\n      debug(\n        '[nuqs dqc] Aborting debounce queue %s=%s',\n        key,\n        queue.queuedValue?.query\n      )\n      queue.abort()\n      // todo: Better abort handling\n      queue.resolvers.resolve(new URLSearchParams()) // Don't leave the Promise pending\n      this.queuedQuerySync.emit(key)\n    }\n    this.queues.clear()\n  }\n\n  getQueuedQuery(key: string): Query | null | undefined {\n    // The debounced queued values are more likely to be up-to-date\n    // than any updates pending in the throttle queue, which comes last\n    // in the update chain.\n    const debouncedQueued = this.queues.get(key)?.queuedValue?.query\n    if (debouncedQueued !== undefined) {\n      return debouncedQueued\n    }\n    return this.throttleQueue.getQueuedQuery(key)\n  }\n}\n\nexport const debounceController: DebounceController = new DebounceController(\n  globalThrottleQueue\n)\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/rate-limiting.ts",
    "content": "import type { LimitUrlUpdates } from '../../defs'\n\n// 50ms between calls to the history API seems to satisfy Chrome and Firefox.\n// Safari remains annoying with at most 100 calls in 30 seconds.\n// edit: Safari 17 now allows 100 calls per 10 seconds, a bit better.\nfunction getDefaultThrottle() {\n  if (typeof window === 'undefined') return 50\n  // https://stackoverflow.com/questions/7944460/detect-safari-browser\n  // @ts-expect-error\n  const isSafari = Boolean(window.GestureEvent)\n  if (!isSafari) {\n    return 50\n  }\n  try {\n    const match = navigator.userAgent?.match(/version\\/([\\d\\.]+) safari/i)\n    return parseFloat(match![1]!) >= 17 ? 120 : 320\n  } catch {\n    return 320\n  }\n}\n\nexport function throttle(timeMs: number): LimitUrlUpdates {\n  return { method: 'throttle', timeMs }\n}\n\nexport function debounce(timeMs: number): LimitUrlUpdates {\n  return { method: 'debounce', timeMs }\n}\n\nexport const defaultRateLimit: LimitUrlUpdates = throttle(getDefaultThrottle())\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/reset.ts",
    "content": "import { debug } from '../debug'\nimport { debounceController } from './debounce'\nimport { globalThrottleQueue } from './throttle'\n\nlet mutex = 0\n\nexport function setQueueResetMutex(value = 1): void {\n  mutex = value\n}\n\nexport function spinQueueResetMutex(onReset: () => void = resetQueues): void {\n  // Don't let values become too negatively large and wrap around\n  mutex = Math.max(0, mutex - 1)\n  if (mutex > 0) {\n    return\n  }\n  onReset()\n}\n\nexport function resetQueues(): void {\n  debug('[nuqs] Aborting queues')\n  debounceController.abortAll()\n  const abortedKeys = globalThrottleQueue.abort()\n  abortedKeys.forEach(key => debounceController.queuedQuerySync.emit(key))\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/throttle.test.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\nimport type { UpdateUrlFunction } from '../../adapters/lib/defs'\nimport { defaultRateLimit } from './rate-limiting'\nimport { ThrottledQueue, type UpdateQueueAdapterContext } from './throttle'\n\nfunction createMockAdapter(): UpdateQueueAdapterContext {\n  return {\n    updateUrl: vi.fn<UpdateUrlFunction>(),\n    getSearchParamsSnapshot() {\n      return new URLSearchParams()\n    }\n  }\n}\n\ndescribe('throttle: ThrottleQueue value queueing', () => {\n  it('should enqueue key & values', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'key', query: 'value', options: {} })\n    expect(queue.getQueuedQuery('key')).toEqual('value')\n  })\n  it('should replace more recent values with the same key', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'key', query: 'a', options: {} })\n    queue.push({ key: 'key', query: 'b', options: {} })\n    expect(queue.getQueuedQuery('key')).toEqual('b')\n  })\n  it('should enqueue multiple keys', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'key1', query: 'a', options: {} })\n    queue.push({ key: 'key2', query: 'b', options: {} })\n    expect(queue.getQueuedQuery('key1')).toEqual('a')\n    expect(queue.getQueuedQuery('key2')).toEqual('b')\n  })\n  it('should enqueue null values (to clear a key from the URL)', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'key', query: 'a', options: {} })\n    queue.push({ key: 'key', query: null, options: {} })\n    expect(queue.getQueuedQuery('key')).toBeNull()\n  })\n  it('should return an undefined queued value if no push occurred', () => {\n    const queue = new ThrottledQueue()\n    expect(queue.getQueuedQuery('key')).toBeUndefined()\n  })\n})\n\ndescribe('throttle: ThrottleQueue option combination logic', () => {\n  it('should resolve with the default options', () => {\n    const queue = new ThrottledQueue()\n    expect(queue.options).toEqual({\n      history: 'replace',\n      scroll: false,\n      shallow: true\n    })\n  })\n  it('should combine history options (push takes precedence)', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'a', query: null, options: { history: 'replace' } })\n    queue.push({ key: 'b', query: null, options: { history: 'push' } })\n    queue.push({ key: 'c', query: null, options: { history: 'replace' } })\n    expect(queue.options.history).toEqual('push')\n  })\n  it('should combine scroll options (true takes precedence)', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'a', query: null, options: { scroll: false } })\n    queue.push({ key: 'b', query: null, options: { scroll: true } })\n    queue.push({ key: 'c', query: null, options: { scroll: false } })\n    expect(queue.options.scroll).toEqual(true)\n  })\n  it('should combine shallow options (false takes precedence)', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'a', query: null, options: { shallow: true } })\n    queue.push({ key: 'b', query: null, options: { shallow: false } })\n    queue.push({ key: 'c', query: null, options: { shallow: true } })\n    expect(queue.options.shallow).toEqual(false)\n  })\n  it('should compose transitions', async () => {\n    const mockStartTransition = (callback: () => void) => {\n      callback()\n    }\n    const mockAdapter = createMockAdapter()\n    const startTransitionA = vi.fn().mockImplementation(mockStartTransition)\n    const startTransitionB = vi.fn().mockImplementation(mockStartTransition)\n    const queue = new ThrottledQueue()\n    queue.push({\n      key: 'a',\n      query: null,\n      options: { startTransition: startTransitionA }\n    })\n    queue.push({\n      key: 'b',\n      query: null,\n      options: { startTransition: startTransitionB }\n    })\n    await queue.flush(mockAdapter)\n    expect(startTransitionA).toHaveBeenCalledOnce()\n    expect(startTransitionB).toHaveBeenCalledOnce()\n    expect(startTransitionA).toHaveBeenCalledBefore(startTransitionB)\n  })\n  it('keeps the maximum value for timeMs', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'a', query: null, options: {} }, 100)\n    queue.push({ key: 'b', query: null, options: {} }, 200)\n    queue.push({ key: 'c', query: null, options: {} }, 300)\n    expect(queue.timeMs).toEqual(300)\n  })\n  it('clamps the minimum value for timeMs to the default rate limit', () => {\n    expect(defaultRateLimit.timeMs).toBeGreaterThan(10) // precondition\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'a', query: null, options: {} }, 10)\n    expect(queue.timeMs).toEqual(defaultRateLimit.timeMs)\n  })\n  it('supports passing Infinity to the timeMs option (but can be cleared)', () => {\n    const queue = new ThrottledQueue()\n    queue.push({ key: 'a', query: null, options: {} }, Infinity)\n    expect(queue.timeMs).toBe(Infinity)\n    queue.push({ key: 'b', query: null, options: {} }, 100)\n    expect(queue.timeMs).toBe(100)\n  })\n})\n\ndescribe('throttle: Abort & reset logic', () => {\n  beforeEach(() => {\n    vi.useFakeTimers()\n  })\n  afterEach(() => {\n    vi.restoreAllMocks()\n  })\n  it('creates the abort controller lazily', async () => {\n    const queue = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    expect(queue.controller).toBeNull()\n    queue.push({ key: 'a', query: 'a', options: {} })\n    expect(queue.controller).toBeNull()\n    const promise = queue.flush(mockAdapter) // AbortController created on flush\n    expect(queue.controller).not.toBeNull()\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams('?a=a'))\n  })\n  it('does not abort pending flushes when resetting', async () => {\n    const queue = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    queue.push({ key: 'a', query: 'a', options: {} })\n    expect(queue.resolvers?.promise).toBeUndefined()\n    const promise = queue.flush(mockAdapter)\n    const controller = queue.controller!\n    controller.signal.throwIfAborted()\n    expect(queue.resolvers!.promise).toBe(promise)\n    const abortedKeys = queue.reset()\n    expect(abortedKeys).toEqual(['a'])\n    // The promise should exist and be pending\n    expect(queue.resolvers!.promise).toBe(promise)\n    expect(queue.controller).toBe(controller)\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams(''))\n    expect(mockAdapter.updateUrl).not.toHaveBeenCalled()\n    expect(queue.resolvers).toBeNull()\n  })\n  it('does reset when aborting', async () => {\n    const queue = new ThrottledQueue()\n    const controller = queue.controller\n    const mockAdapter = createMockAdapter()\n    queue.push({ key: 'a', query: 'a', options: {} })\n    const promise = queue.flush(mockAdapter)\n    const abortedKeys = queue.abort()\n    expect(abortedKeys).toEqual(['a'])\n    vi.runAllTimers()\n    expect(mockAdapter.updateUrl).not.toHaveBeenCalled()\n    expect(queue.updateMap.size).toBe(0)\n    expect(queue.resolvers).toBeNull()\n    expect(queue.controller).not.toBe(controller) // Reassigned after abort\n    await expect(promise).resolves.toEqual(new URLSearchParams(''))\n  })\n})\n\ndescribe('throttle: flush', () => {\n  beforeEach(() => {\n    vi.useFakeTimers()\n  })\n  afterEach(() => {\n    vi.restoreAllMocks()\n  })\n\n  it('returns a Promise of the current search params if flushed without updates', async () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    const promise = throttle.flush(mockAdapter)\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams())\n    expect(mockAdapter.updateUrl).not.toHaveBeenCalled()\n  })\n\n  it('returns a Promise of updated URL search params', async () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    throttle.push({ key: 'a', query: 'a', options: {} })\n    const promise = throttle.flush(mockAdapter)\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams('?a=a'))\n    expect(mockAdapter.updateUrl).toHaveBeenCalledExactlyOnceWith(\n      new URLSearchParams('?a=a'),\n      {\n        history: 'replace',\n        scroll: false,\n        shallow: true\n      }\n    )\n  })\n  it('combines updates in order of push', async () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    throttle.push({ key: 'b', query: 'b', options: {} })\n    throttle.push({ key: 'a', query: 'a', options: {} })\n    const promise = throttle.flush(mockAdapter)\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams('?b=b&a=a'))\n    expect(mockAdapter.updateUrl).toHaveBeenCalledExactlyOnceWith(\n      new URLSearchParams('?b=b&a=a'),\n      {\n        history: 'replace',\n        scroll: false,\n        shallow: true\n      }\n    )\n  })\n  it('returns the same Promise for multiple flushes in the same tick', () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    throttle.push({ key: 'b', query: 'b', options: {} })\n    const p1 = throttle.flush(mockAdapter)\n    throttle.push({ key: 'a', query: 'a', options: {} })\n    const p2 = throttle.flush(mockAdapter)\n    expect(p1).toBe(p2)\n    vi.runAllTimers()\n    expect(mockAdapter.updateUrl).toHaveBeenCalledExactlyOnceWith(\n      new URLSearchParams('?b=b&a=a'),\n      {\n        history: 'replace',\n        scroll: false,\n        shallow: true\n      }\n    )\n  })\n  it('returns the same Promise if the initial flush has no updates', () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    const p1 = throttle.flush(mockAdapter)\n    throttle.push({ key: 'a', query: 'a', options: {} })\n    const p2 = throttle.flush(mockAdapter)\n    expect(p1).toBe(p2)\n    vi.runAllTimers()\n    expect(mockAdapter.updateUrl).toHaveBeenCalledExactlyOnceWith(\n      new URLSearchParams('?a=a'),\n      {\n        history: 'replace',\n        scroll: false,\n        shallow: true\n      }\n    )\n  })\n  it('returns the same Promise if the second flush has no updates', () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    throttle.push({ key: 'a', query: 'a', options: {} })\n    const p1 = throttle.flush(mockAdapter)\n    const p2 = throttle.flush(mockAdapter)\n    expect(p1).toBe(p2)\n    vi.runAllTimers()\n    expect(mockAdapter.updateUrl).toHaveBeenCalledExactlyOnceWith(\n      new URLSearchParams('?a=a'),\n      {\n        history: 'replace',\n        scroll: false,\n        shallow: true\n      }\n    )\n  })\n  it('does not call the adapter when passing Infinity to timeMs', async () => {\n    const throttle = new ThrottledQueue()\n    const mockAdapter = createMockAdapter()\n    throttle.push({ key: 'a', query: 'a', options: {} }, Infinity)\n    const p = throttle.flush(mockAdapter)\n    vi.runAllTimers()\n    await expect(p).resolves.toEqual(new URLSearchParams(''))\n    expect(mockAdapter.updateUrl).not.toHaveBeenCalled()\n  })\n  it('rejects the Promise with what should have been applied if the updateUrl function throws', async () => {\n    const consoleErrorSpy = vi\n      .spyOn(console, 'error')\n      .mockImplementation(() => {})\n    const throttle = new ThrottledQueue()\n    throttle.push({ key: 'a', query: 'a', options: {} })\n    const p = throttle.flush({\n      getSearchParamsSnapshot() {\n        return new URLSearchParams('?initial=search')\n      },\n      updateUrl: vi.fn().mockImplementation(() => {\n        throw new Error('updateUrl error')\n      })\n    })\n    vi.runAllTimers()\n    await expect(p).rejects.toEqual(new URLSearchParams('?initial=search&a=a'))\n    expect(consoleErrorSpy).toHaveBeenCalledExactlyOnceWith(\n      '[nuqs] URL update rate-limited by the browser. Consider increasing `throttleMs` for key(s) `%s`. %O\\n  See https://nuqs.dev/NUQS-429',\n      'a',\n      new Error('updateUrl error')\n    )\n  })\n  it('should process url search params', async () => {\n    const mockAdapter = createMockAdapter()\n    const queue = new ThrottledQueue()\n    queue.push({\n      key: 'a',\n      query: 'a',\n      options: {}\n    })\n    const promise = queue.flush(mockAdapter, function (search) {\n      const params = new URLSearchParams(search)\n      params.set('b', 'b')\n      return params\n    })\n    expect(queue.controller).not.toBeNull()\n    vi.runAllTimers()\n    await expect(promise).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n  describe('should process url search params', () => {\n    it('should add new params', async () => {\n      const mockAdapter = createMockAdapter()\n      const queue = new ThrottledQueue()\n      queue.push({\n        key: 'a',\n        query: 'a',\n        options: {}\n      })\n      const promise = queue.flush(mockAdapter, search => {\n        const params = new URLSearchParams(search)\n        params.set('b', 'b')\n        return params\n      })\n      expect(queue.controller).not.toBeNull()\n      vi.runAllTimers()\n      await expect(promise).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n    })\n    it('should sort params', async () => {\n      const mockAdapter = createMockAdapter()\n      const queue = new ThrottledQueue()\n      queue.push({\n        key: 'b',\n        query: 'b',\n        options: {}\n      })\n      queue.push({\n        key: 'a',\n        query: 'a',\n        options: {}\n      })\n      const promise = queue.flush(mockAdapter, search => {\n        search.sort()\n        return search\n      })\n      expect(queue.controller).not.toBeNull()\n      vi.runAllTimers()\n      await expect(promise).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/throttle.ts",
    "content": "import type { AdapterInterface, AdapterOptions } from '../../adapters/lib/defs'\nimport type { Options } from '../../defs'\nimport { compose } from '../compose'\nimport { debug } from '../debug'\nimport { error } from '../errors'\nimport { write, type Query } from '../search-params'\nimport { timeout } from '../timeout'\nimport { withResolvers, type Resolvers } from '../with-resolvers'\nimport { defaultRateLimit } from './rate-limiting'\n\ntype UpdateMap = Map<string, Query | null>\ntype TransitionSet = Set<React.TransitionStartFunction>\nexport type UpdateQueueAdapterContext = Pick<\n  AdapterInterface,\n  | 'updateUrl'\n  | 'getSearchParamsSnapshot'\n  | 'rateLimitFactor'\n  | 'autoResetQueueOnUpdate'\n>\n\nexport type UpdateQueuePushArgs = {\n  key: string\n  query: Query | null\n  options: AdapterOptions & Pick<Options, 'startTransition'>\n}\n\nexport function getSearchParamsSnapshotFromLocation(): URLSearchParams {\n  return new URLSearchParams(location.search)\n}\n\nexport class ThrottledQueue {\n  updateMap: UpdateMap = new Map()\n  options: Required<AdapterOptions> = {\n    history: 'replace',\n    scroll: false,\n    shallow: true\n  }\n  timeMs: number = defaultRateLimit.timeMs\n  transitions: TransitionSet = new Set()\n  resolvers: Resolvers<URLSearchParams> | null = null\n  controller: AbortController | null = null\n  lastFlushedAt = 0\n  resetQueueOnNextPush = false\n\n  push(\n    { key, query, options }: UpdateQueuePushArgs,\n    timeMs: number = defaultRateLimit.timeMs\n  ): void {\n    if (this.resetQueueOnNextPush) {\n      this.reset()\n      this.resetQueueOnNextPush = false\n    }\n    debug('[nuqs gtq] Enqueueing %s=%s %O', key, query, options)\n    // Enqueue update\n    this.updateMap.set(key, query)\n    if (options.history === 'push') {\n      this.options.history = 'push'\n    }\n    if (options.scroll) {\n      this.options.scroll = true\n    }\n    if (options.shallow === false) {\n      this.options.shallow = false\n    }\n    if (options.startTransition) {\n      this.transitions.add(options.startTransition)\n    }\n    // Keep the maximum finite throttle value (or set if previous was Infinity)\n    if (!Number.isFinite(this.timeMs) || timeMs > this.timeMs) {\n      this.timeMs = timeMs\n    }\n  }\n\n  getQueuedQuery(key: string): Query | null | undefined {\n    return this.updateMap.get(key)\n  }\n\n  getPendingPromise({\n    getSearchParamsSnapshot = getSearchParamsSnapshotFromLocation\n  }: UpdateQueueAdapterContext): Promise<URLSearchParams> {\n    return this.resolvers?.promise ?? Promise.resolve(getSearchParamsSnapshot())\n  }\n\n  flush(\n    {\n      getSearchParamsSnapshot = getSearchParamsSnapshotFromLocation,\n      rateLimitFactor = 1,\n      ...adapter\n    }: UpdateQueueAdapterContext,\n    processUrlSearchParams?: (search: URLSearchParams) => URLSearchParams\n  ): Promise<URLSearchParams> {\n    this.controller ??= new AbortController()\n    if (!Number.isFinite(this.timeMs)) {\n      debug('[nuqs gtq] Skipping flush due to throttleMs=Infinity')\n      return Promise.resolve(getSearchParamsSnapshot())\n    }\n    if (this.resolvers) {\n      // Flush already scheduled\n      return this.resolvers.promise\n    }\n    this.resolvers = withResolvers<URLSearchParams>()\n    const flushNow = () => {\n      this.lastFlushedAt = performance.now()\n      const [search, error] = this.applyPendingUpdates(\n        {\n          ...adapter,\n          autoResetQueueOnUpdate: adapter.autoResetQueueOnUpdate ?? true,\n          getSearchParamsSnapshot\n        },\n        processUrlSearchParams\n      )\n      if (error === null) {\n        this.resolvers!.resolve(search)\n        this.resetQueueOnNextPush = true\n      } else {\n        this.resolvers!.reject(search)\n      }\n      this.resolvers = null\n    }\n    // We run the logic on the next event loop tick to allow\n    // multiple query updates to batch in the same event loop tick\n    // and possibly set their own throttleMs value.\n    const runOnNextTick = () => {\n      const now = performance.now()\n      const timeSinceLastFlush = now - this.lastFlushedAt\n      const timeMs = this.timeMs\n      const flushInMs =\n        rateLimitFactor * Math.max(0, timeMs - timeSinceLastFlush)\n      debug(\n        `[nuqs gtq] Scheduling flush in %f ms. Throttled at %f ms (x%f)`,\n        flushInMs,\n        timeMs,\n        rateLimitFactor\n      )\n      if (flushInMs === 0) {\n        // Since we're already in the \"next tick\" from queued updates,\n        // no need to do setTimeout(0) here.\n        flushNow()\n      } else {\n        timeout(flushNow, flushInMs, this.controller!.signal)\n      }\n    }\n    timeout(runOnNextTick, 0, this.controller.signal)\n    return this.resolvers.promise\n  }\n\n  abort(): string[] {\n    this.controller?.abort()\n    this.controller = new AbortController()\n    // todo: Better abort handling\n    this.resolvers?.resolve(new URLSearchParams())\n    this.resolvers = null\n    return this.reset()\n  }\n\n  reset(): string[] {\n    const queuedKeys = Array.from(this.updateMap.keys())\n    debug(\n      '[nuqs gtq] Resetting queue %s',\n      JSON.stringify(Object.fromEntries(this.updateMap))\n    )\n    this.updateMap.clear()\n    this.transitions.clear()\n    this.options = {\n      history: 'replace',\n      scroll: false,\n      shallow: true\n    }\n    this.timeMs = defaultRateLimit.timeMs\n    return queuedKeys\n  }\n\n  applyPendingUpdates(\n    adapter: Required<Omit<UpdateQueueAdapterContext, 'rateLimitFactor'>>,\n    processUrlSearchParams?: (search: URLSearchParams) => URLSearchParams\n  ): [URLSearchParams, null | unknown] {\n    const { updateUrl, getSearchParamsSnapshot } = adapter\n    let search = getSearchParamsSnapshot()\n    debug(\n      `[nuqs gtq] Applying %d pending update(s) on top of %s`,\n      this.updateMap.size,\n      search.toString()\n    )\n    if (this.updateMap.size === 0) {\n      return [search, null]\n    }\n    // Work on a copy and clear the queue immediately\n    const items = Array.from(this.updateMap.entries())\n    const options = { ...this.options }\n    const transitions = Array.from(this.transitions)\n    // Let the adapters choose whether to reset, as it depends on how they\n    // handle concurrent rendering (see the life-and-death.cy.ts e2e test).\n    if (adapter.autoResetQueueOnUpdate) {\n      this.reset()\n    }\n    debug('[nuqs gtq] Flushing queue %O with options %O', items, options)\n    for (const [key, value] of items) {\n      if (value === null) {\n        search.delete(key)\n      } else {\n        search = write(value, key, search)\n      }\n    }\n    if (processUrlSearchParams) {\n      search = processUrlSearchParams(search)\n    }\n    try {\n      compose(transitions, () => {\n        updateUrl(search, options)\n      })\n      return [search, null]\n    } catch (err) {\n      // This may fail due to rate-limiting of history methods,\n      // for example Safari only allows 100 updates in a 30s window.\n      console.error(error(429), items.map(([key]) => key).join(), err)\n      return [search, err]\n    }\n  }\n}\n\nexport const globalThrottleQueue: ThrottledQueue = new ThrottledQueue()\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/useSyncExternalStores.browser.test.ts",
    "content": "import { useRef } from 'react'\nimport { describe, expect, it } from 'vitest'\nimport { renderHook } from 'vitest-browser-react'\nimport { createEmitter } from '../emitter'\nimport { useSyncExternalStores } from './useSyncExternalStores'\n\ndescribe('useSyncExternalStores', () => {\n  it('should handle an empty array of keys', async () => {\n    const useTest = () =>\n      useSyncExternalStores(\n        [],\n        (_, callback) => callback,\n        _ => 'snapshot'\n      )\n    const { result } = await renderHook(useTest)\n    expect(result.current).toEqual({})\n  })\n  it('should handle a single key', async () => {\n    const useTest = () =>\n      useSyncExternalStores(\n        ['a'],\n        (_, callback) => callback,\n        _ => 'snapshot'\n      )\n    const { result } = await renderHook(useTest)\n    expect(result.current).toEqual({ a: 'snapshot' })\n  })\n  it('should be reactive to changes in the store', async () => {\n    const emitter = createEmitter()\n    const store: Record<string, number> = {\n      a: 0\n    }\n    const useTest = () =>\n      useSyncExternalStores(\n        ['a'],\n        (key, callback) => {\n          emitter.on(key, callback)\n          return () => emitter.off(key, callback)\n        },\n        key => store[key]\n      )\n    const { result, act } = await renderHook(useTest)\n    expect(result.current).toEqual({ a: 0 })\n    // Update the store\n    act(() => {\n      store.a = 1\n      emitter.emit('a')\n    })\n    expect(result.current).toEqual({ a: 1 })\n  })\n  it('should be reactive to changes in the keys', async () => {\n    const emitter = createEmitter()\n    const store: Record<string, number> = {\n      a: 0,\n      b: 0\n    }\n    const useTest = ({ keys = ['a'] }: { keys?: string[] } = {}) =>\n      useSyncExternalStores(\n        keys,\n        (key, callback) => {\n          emitter.on(key, callback)\n          return () => emitter.off(key, callback)\n        },\n        key => store[key]\n      )\n    const { result, rerender } = await renderHook(useTest)\n    expect(result.current).toEqual({ a: 0 })\n    rerender({ keys: ['b'] })\n    expect(result.current).toEqual({ b: 0 })\n  })\n  it('should not re-render when a non-listened key changes', async () => {\n    const emitter = createEmitter()\n    const store: Record<string, number> = {\n      a: 0,\n      b: 0\n    }\n    const useTest = () => {\n      const renderCount = useRef(0)\n      const result = useSyncExternalStores(\n        ['a'],\n        (key, callback) => {\n          emitter.on(key, callback)\n          return () => emitter.off(key, callback)\n        },\n        key => store[key]\n      )\n      return {\n        renderCount: ++renderCount.current,\n        result\n      }\n    }\n    const { result, act } = await renderHook(useTest)\n    expect(result.current.result).toEqual({ a: 0 })\n    expect(result.current.renderCount).toBe(1)\n    // Update another key\n    act(() => {\n      store.b = 1\n      emitter.emit('b')\n    })\n    expect(result.current.result).toEqual({ a: 0 })\n    expect(result.current.renderCount).toBe(1)\n    // Update the listened key\n    act(() => {\n      store.a = 1\n      emitter.emit('a')\n    })\n    expect(result.current.result).toEqual({ a: 1 })\n    expect(result.current.renderCount).toBe(2)\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/queues/useSyncExternalStores.ts",
    "content": "import { useCallback, useRef, useSyncExternalStore } from 'react'\n\n/**\n * Like `useSyncExternalStore`, but for subscribing to multiple keys.\n *\n * Each key becomes the key of the returned object,\n * and the value is the result of calling `getKeySnapshot` with that key.\n *\n * @param keys - A list of keys to subscribe to.\n * @param subscribeKey - A function that takes a key and a callback,\n * subscribes to an external store using that key (calling the callback when\n * state changes occur), and returns a function to unsubscribe from that key.\n * @param getKeySnapshot - A function that takes a key and returns the snapshot for that key.\n * It will be called on the server and on the client, so it needs to handle both\n * environments.\n */\nexport function useSyncExternalStores<T>(\n  keys: string[],\n  subscribeKey: (key: string, callback: () => void) => () => void,\n  getKeySnapshot: (key: string) => T\n): Record<string, T> {\n  const snapshot = useCallback((): [string, Record<string, T>] => {\n    const record = Object.fromEntries(\n      keys.map(key => [key, getKeySnapshot(key)])\n    )\n    const cacheKey = JSON.stringify(record)\n    return [cacheKey, record]\n  }, [keys.join(','), getKeySnapshot])\n  const cacheRef = useRef<null | [string, Record<string, T>]>(null)\n  // Initialize the cache with the initial snapshot\n  if (cacheRef.current === null) {\n    cacheRef.current = snapshot()\n  }\n  const subscribe = useCallback(\n    (callback: () => void) => {\n      const off = keys.map(key => subscribeKey(key, callback))\n      return () => off.forEach(unsubscribe => unsubscribe())\n    },\n    [keys.join(','), subscribeKey]\n  )\n  return useSyncExternalStore<Record<string, T>>(\n    subscribe,\n    () => {\n      const [cacheKey, record] = snapshot()\n      if (cacheRef.current![0] === cacheKey) {\n        return cacheRef.current![1]!\n      }\n      cacheRef.current = [cacheKey, record]\n      return record\n    },\n    () => cacheRef.current![1]!\n  )\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/safe-parse.ts",
    "content": "import { warn } from './debug'\n\nexport function safeParse<I, R>(\n  parser: (arg: I) => R,\n  value: I,\n  key?: string\n): R | null {\n  try {\n    return parser(value)\n  } catch (error) {\n    warn(\n      '[nuqs] Error while parsing value `%s`: %O' +\n        (key ? ' (for key `%s`)' : ''),\n      value,\n      error,\n      key\n    )\n    return null\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/search-params.ts",
    "content": "export type Query = string | Array<string>\n\nexport function isAbsentFromUrl(query: Query | null): query is null | [] {\n  return query === null || (Array.isArray(query) && query.length === 0)\n}\n\nexport function write(\n  serialized: Query,\n  key: string,\n  searchParams: URLSearchParams\n): URLSearchParams {\n  if (typeof serialized === 'string') {\n    searchParams.set(key, serialized)\n  } else {\n    searchParams.delete(key)\n    for (const v of serialized) {\n      searchParams.append(key, v)\n    }\n    // if we get here with an empty iterable, no values were appended\n    // however, an empty iterable here means we explicitly want to set the key\n    // because for default values, we don't call write at all\n    if (!searchParams.has(key)) {\n      searchParams.set(key, '')\n    }\n  }\n  return searchParams\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/sync.browser.test.tsx",
    "content": "import React from 'react'\nimport { describe, expect, it } from 'vitest'\nimport { page, userEvent } from 'vitest/browser'\nimport { withNuqsTestingAdapter } from '../adapters/testing'\nimport { parseAsInteger, useQueryState, useQueryStates } from '../index'\nimport { render } from 'vitest-browser-react'\n\ntype TestComponentProps = {\n  testId: string\n}\n\ndescribe('sync', () => {\n  it('should sync two hooks state', async () => {\n    const TestComponent = ({ testId }: TestComponentProps) => {\n      const [count, setCount] = useQueryState(\n        'count',\n        parseAsInteger.withDefault(0)\n      )\n      return (\n        <button data-testid={testId} onClick={() => setCount(c => c + 1)}>\n          count is {count}\n        </button>\n      )\n    }\n\n    const user = userEvent.setup()\n    await render(\n      <>\n        <TestComponent testId=\"a\" />\n        <TestComponent testId=\"b\" />\n      </>,\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    // Act\n    const buttonA = page.getByTestId('a')\n    const buttonB = page.getByTestId('b')\n    await user.click(buttonA)\n    await expect.element(buttonA).toHaveTextContent('count is 1')\n    await expect.element(buttonB).toHaveTextContent('count is 1')\n    await user.click(buttonB)\n    await expect.element(buttonA).toHaveTextContent('count is 2')\n    await expect.element(buttonB).toHaveTextContent('count is 2')\n  })\n\n  it('should sync useQueryState and useQueryStates', async () => {\n    const TestComponentA = ({ testId }: TestComponentProps) => {\n      const [count, setCount] = useQueryState(\n        'count',\n        parseAsInteger.withDefault(0)\n      )\n      return (\n        <button data-testid={testId} onClick={() => setCount(c => c + 1)}>\n          count is {count}\n        </button>\n      )\n    }\n    const TestComponentB = ({ testId }: TestComponentProps) => {\n      const [{ count }, setCount] = useQueryStates({\n        count: parseAsInteger.withDefault(0)\n      })\n      return (\n        <button\n          data-testid={testId}\n          onClick={() => setCount(c => ({ count: c.count + 1 }))}\n        >\n          count is {count}\n        </button>\n      )\n    }\n\n    const user = userEvent.setup()\n    await render(\n      <>\n        <TestComponentA testId=\"a\" />\n        <TestComponentB testId=\"b\" />\n      </>,\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    // Act\n    const buttonA = page.getByTestId('a')\n    const buttonB = page.getByTestId('b')\n    await user.click(buttonA)\n    await expect.element(buttonA).toHaveTextContent('count is 1')\n    await expect.element(buttonB).toHaveTextContent('count is 1')\n    await user.click(buttonB)\n    await expect.element(buttonA).toHaveTextContent('count is 2')\n    await expect.element(buttonB).toHaveTextContent('count is 2')\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/sync.ts",
    "content": "import { createEmitter, type Emitter } from './emitter'\nimport type { Query } from './search-params'\n\nexport type CrossHookSyncPayload = {\n  state: any\n  query: Query | null\n}\n\ntype EventMap = {\n  [key: string]: CrossHookSyncPayload\n}\n\nexport const emitter: Emitter<EventMap> = createEmitter()\n"
  },
  {
    "path": "packages/nuqs/src/lib/timeout.test.ts",
    "content": "import { describe, expect, it, vi } from 'vitest'\nimport { timeout } from './timeout'\n\ndescribe('utils: timeout', () => {\n  it('should resolve after the timeout if no signal is triggered', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn()\n    const controller = new AbortController()\n    timeout(() => spy(), 100, controller.signal)\n    vi.advanceTimersToNextTimer()\n    expect(spy).toHaveBeenCalledOnce()\n  })\n  it('should abort the timeout if the signal is triggered', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn()\n    const controller = new AbortController()\n    timeout(() => spy(), 100, controller.signal)\n    controller.abort()\n    vi.advanceTimersToNextTimer()\n    expect(spy).not.toHaveBeenCalled()\n  })\n  it('does not throw when aborting after timeout', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn()\n    const controller = new AbortController()\n    timeout(() => spy(), 100, controller.signal)\n    vi.advanceTimersToNextTimer()\n    expect(() => controller.abort()).not.toThrow()\n    expect(spy).toHaveBeenCalledOnce()\n  })\n  it('reuses the same signal to abort multiple timeouts', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn()\n    const controller = new AbortController()\n    timeout(() => spy(), 100, controller.signal)\n    timeout(() => spy(), 100, controller.signal)\n    controller.abort()\n    vi.advanceTimersToNextTimer()\n    expect(spy).not.toHaveBeenCalled()\n  })\n  it('aborts when using a signal already used before', () => {\n    vi.useFakeTimers()\n    const spy = vi.fn()\n    const controller = new AbortController()\n    timeout(() => spy(), 100, controller.signal)\n    controller.abort()\n    timeout(() => spy(), 100, controller.signal)\n    vi.advanceTimersToNextTimer()\n    expect(spy).toHaveBeenCalledOnce()\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/timeout.ts",
    "content": "// Source:\n// https://www.bennadel.com/blog/4195-using-abortcontroller-to-debounce-settimeout-calls-in-javascript.htm\n\nexport function timeout(\n  callback: () => void,\n  ms: number,\n  signal: AbortSignal\n): void {\n  function onTick() {\n    callback()\n    signal.removeEventListener('abort', onAbort)\n  }\n  const id = setTimeout(onTick, ms)\n  function onAbort() {\n    clearTimeout(id)\n    signal.removeEventListener('abort', onAbort)\n  }\n  signal.addEventListener('abort', onAbort)\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/url-encoding.browser.test.ts",
    "content": "import fc from 'fast-check'\nimport { describe, expect, test, vi } from 'vitest'\nimport { encodeQueryValue, renderQueryString } from './url-encoding'\n\ndescribe('url-encoding/encodeQueryValue', () => {\n  test('spaces are encoded as +', () => {\n    expect(encodeQueryValue(' ')).toBe('+')\n  })\n  test('+ are encoded', () => {\n    expect(encodeQueryValue('+')).toBe(encodeURIComponent('+'))\n  })\n  test('Hashes are encoded', () => {\n    expect(encodeQueryValue('#')).toBe(encodeURIComponent('#'))\n  })\n  test('Ampersands are encoded', () => {\n    expect(encodeQueryValue('&')).toBe(encodeURIComponent('&'))\n  })\n  test('Percent signs are encoded', () => {\n    expect(encodeQueryValue('%')).toBe(encodeURIComponent('%'))\n  })\n  test('Characters that break URLs are encoded', () => {\n    expect(encodeQueryValue('\"')).toEqual(encodeURIComponent('\"'))\n    expect(encodeQueryValue(\"'\")).toEqual('%27') // encodeURIComponent does not encode single quotes\n    expect(encodeQueryValue('`')).toEqual(encodeURIComponent('`'))\n    expect(encodeQueryValue('<')).toEqual(encodeURIComponent('<'))\n    expect(encodeQueryValue('>')).toEqual(encodeURIComponent('>'))\n  })\n  test('hidden ASCII characters are encoded', () => {\n    const chars = Array.from({ length: 32 }, (_, i) => String.fromCharCode(i))\n    chars.forEach(char => {\n      expect(encodeQueryValue(char)).toBe(encodeURIComponent(char))\n    })\n  })\n  test('Alphanumericals are passed through', () => {\n    const input = 'abcdefghijklmnopqrstuvwxyz0123456789'\n    expect(encodeQueryValue(input)).toBe(input)\n  })\n  test('Other special characters are passed through', () => {\n    const input = '-._~!$()*,;=:@/?[]{}\\\\|^'\n    expect(encodeQueryValue(input)).toBe(input)\n  })\n  test('practical use-cases', () => {\n    const e = encodeQueryValue\n    expect(e('a b')).toBe('a+b')\n    expect(e('some#secret')).toBe('some%23secret')\n    expect(e('2+2=5')).toBe('2%2B2=5')\n    expect(e('100%')).toBe('100%25')\n    expect(e('kool&thegang')).toBe('kool%26thegang')\n    expect(e('a&b=c')).toBe('a%26b=c')\n  })\n\n  test.each([\n    { label: 'ASCII', unit: 'binary-ascii' },\n    { label: 'Printable characters', unit: 'grapheme' },\n    { label: 'Full Unicode range', unit: 'binary' },\n    {\n      label: 'Special ASCII characters',\n      unit: fc.constantFrom(...'-._~!$()*,;=:@/?[]{}\\\\|^')\n    }\n  ] as const)('Property-based fuzzy testing - $label', ({ unit }) => {\n    fc.assert(\n      fc.property(fc.string({ unit }), str => {\n        const search = `?key=${encodeQueryValue(str)}`\n        const expected = new URLSearchParams(search).get('key')\n        expect(expected).toBe(str)\n      })\n    )\n  })\n})\n\ndescribe('url-encoding/renderQueryString', () => {\n  test('empty query', () => {\n    expect(renderQueryString(new URLSearchParams())).toBe('')\n  })\n  test('simple key-value pair', () => {\n    const search = new URLSearchParams()\n    search.set('foo', 'bar')\n    expect(renderQueryString(search)).toBe('?foo=bar')\n  })\n  test('encoding', () => {\n    const search = new URLSearchParams()\n    search.set('test', '-._~!$()*,;=:@/?[]{}\\\\|^')\n    expect(renderQueryString(search)).toBe('?test=-._~!$()*,;=:@/?[]{}\\\\|^')\n  })\n  test('decoding', () => {\n    const search = new URLSearchParams()\n    const value = '!\"#$%&\\'()*+,-./:;<=>?@[\\\\]^_`{|}~'\n    search.set('test', value)\n    const url = new URL('http://example.com' + renderQueryString(search))\n    expect(url.searchParams.get('test')).toBe(value)\n  })\n  test('decoding plus and spaces', () => {\n    const search = new URLSearchParams()\n    const value = 'a b+c'\n    search.set('test', value)\n    const url = new URL('http://example.com' + renderQueryString(search))\n    expect(url.searchParams.get('test')).toBe(value)\n  })\n  test('decoding hashes and fragment', () => {\n    const search = new URLSearchParams()\n    const value = 'foo#bar'\n    search.set('test', value)\n    const url = new URL(\n      'http://example.com' + renderQueryString(search) + '#egg'\n    )\n    expect(url.searchParams.get('test')).toBe(value)\n  })\n  test('decoding ampersands', () => {\n    const search = new URLSearchParams()\n    const value = 'a&b=c'\n    search.set('test', value)\n    const url = new URL(\n      'http://example.com' + renderQueryString(search) + '&egg=spam'\n    )\n    expect(url.searchParams.get('test')).toBe(value)\n  })\n  test('it renders query string with special characters', () => {\n    const search = new URLSearchParams()\n    search.set('name', 'John Doe')\n    search.set('email', 'foo.bar+egg-spam@example.com')\n    search.set('message', 'Hello, world! #greeting')\n    const query = renderQueryString(search)\n    expect(query).toBe(\n      '?name=John+Doe&email=foo.bar%2Begg-spam@example.com&message=Hello,+world!+%23greeting'\n    )\n  })\n  test('practical use-cases', () => {\n    // https://github.com/47ng/nuqs/issues/355\n    {\n      const value =\n        'leftOfBicycleLane:car_lanes,curb|pavementHasShops:true|pavementWidth:narrow'\n      const search = new URLSearchParams()\n      search.set('filter', value)\n      const query = renderQueryString(search)\n      expect(query.slice('?filter='.length)).toBe(value)\n    }\n    {\n      const url = new URL(\n        'https://radverkehrsatlas.de/regionen/trto?lat=53.6774&lng=13.267&zoom=10.6&theme=fromTo&bg=default&config=!(i~fromTo~topics~!(i~shops~s~!(i~hidden~a~_F)(i~default~a))(i~education~s~!(i~hidden~a)(i~default~a~_F))(i~places~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~buildings~s~!(i~hidden~a)(i~default~a~_F))(i~landuse~s~!(i~hidden~a~_F)(i~default~a))(i~barriers~s~!(i~hidden~a~_F)(i~default~a))(i~boundaries~s~!(i~hidden~a)(i~default~a~_F)(i~level-8~a~_F)(i~level-9-10~a~_F)))(i~bikelanes~topics~!(i~bikelanes~s~!(i~hidden~a~_F)(i~default~a)(i~verification~a~_F)(i~completeness~a~_F))(i~bikelanesPresence*_legacy~s~!(i~hidden~a)(i~default~a~_F))(i~places~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~landuse~s~!(i~hidden~a)(i~default~a~_F)))(i~roadClassification~topics~!(i~roadClassification*_legacy~s~!(i~hidden~a~_F)(i~default~a)(i~oneway~a~_F))(i~bikelanes~s~!(i~hidden~a)(i~default~a~_F)(i~verification~a~_F)(i~completeness~a~_F))(i~maxspeed*_legacy~s~!(i~hidden~a)(i~default~a~_F)(i~details~a~_F))(i~surfaceQuality*_legacy~s~!(i~hidden~a)(i~default~a~_F)(i~bad~a~_F)(i~completeness~a~_F)(i~freshness~a~_F))(i~places~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~landuse~s~!(i~hidden~a)(i~default~a~_F)))(i~lit~topics~!(i~lit*_legacy~s~!(i~hidden~a~_F)(i~default~a)(i~completeness~a~_F)(i~verification~a~_F)(i~freshness~a~_F))(i~places~s~!(i~hidden~a)(i~default~a~_F)(i~circle~a~_F))(i~landuse~s~!(i~hidden~a)(i~default~a~_F)))~'\n      )\n      const search = renderQueryString(url.searchParams)\n      expect(search).toBe(url.search)\n    }\n  })\n  test('keys with special characters get escaped', () => {\n    const search = new URLSearchParams()\n    search.set('a &b?c=d#e%f+g\"h\\'i`j<k>l(m)n*o,p.q:r;s/t', 'value')\n    expect(renderQueryString(search)).toBe(\n      '?a %26b%3Fc%3Dd%23e%f%2Bg\"h\\'i`j<k>l(m)n*o,p.q:r;s/t=value'\n    )\n  })\n  test('emits a warning if the URL is too long', () => {\n    const search = new URLSearchParams()\n    search.set('a', 'a'.repeat(2000))\n    const warn = console.warn\n    console.warn = vi.fn()\n    renderQueryString(search)\n    expect(console.warn).toHaveBeenCalledTimes(1)\n    console.warn = warn\n  })\n})\n\ntest.skip('encodeURI vs encodeURIComponent vs custom encoding', () => {\n  const chars = '!\"#$%&\\'()*+,-./:;<=>?@[\\\\]^_`{|}~'.split('')\n  const table = chars.map(char => ({\n    char,\n    encodeQueryValue: encodeQueryValue(char),\n    encodeURI: encodeURI(char),\n    encodeURIComponent: encodeURIComponent(char)\n  }))\n  console.table(table)\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/url-encoding.ts",
    "content": "import { error } from './errors'\n\nexport function renderQueryString(search: URLSearchParams): string {\n  if (search.size === 0) {\n    return ''\n  }\n  const query: string[] = []\n  for (const [key, value] of search.entries()) {\n    // Replace disallowed characters in keys,\n    // see https://github.com/47ng/nuqs/issues/599\n    const safeKey = key\n      .replace(/#/g, '%23')\n      .replace(/&/g, '%26')\n      .replace(/\\+/g, '%2B')\n      .replace(/=/g, '%3D')\n      .replace(/\\?/g, '%3F')\n    query.push(`${safeKey}=${encodeQueryValue(value)}`)\n  }\n  const queryString = '?' + query.join('&')\n  warnIfURLIsTooLong(queryString)\n  return queryString\n}\n\nexport function encodeQueryValue(input: string): string {\n  return (\n    input\n      // Encode existing % signs first to avoid appearing\n      // as an incomplete escape sequence:\n      .replace(/%/g, '%25')\n      // Note: spaces are encoded as + in RFC 3986,\n      // so we pre-encode existing + signs to avoid confusion\n      // before converting spaces to + signs.\n      .replace(/\\+/g, '%2B')\n      .replace(/ /g, '+')\n      // Encode other URI-reserved characters\n      .replace(/#/g, '%23')\n      .replace(/&/g, '%26')\n      // Encode characters that break URL detection on some platforms\n      // and would drop the tail end of the querystring:\n      .replace(/\"/g, '%22')\n      .replace(/'/g, '%27')\n      .replace(/`/g, '%60')\n      .replace(/</g, '%3C')\n      .replace(/>/g, '%3E')\n      // Encode invisible ASCII control characters\n      .replace(/[\\x00-\\x1F]/g, char => encodeURIComponent(char))\n  )\n}\n\n// Note: change error documentation (NUQS-414) when changing this value.\nexport const URL_MAX_LENGTH = 2000\n\nexport function warnIfURLIsTooLong(queryString: string): void {\n  if (process.env.NODE_ENV === 'production') {\n    return\n  }\n  if (typeof location === 'undefined') {\n    return\n  }\n  const url = new URL(location.href)\n  url.search = queryString\n  if (url.href.length > URL_MAX_LENGTH) {\n    console.warn(error(414))\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/lib/with-resolvers.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { withResolvers } from './with-resolvers'\n\ndescribe('utils: withResolvers', () => {\n  it('supports built-in Promise.withResolvers', async () => {\n    expect('withResolvers' in Promise).toBe(true)\n    const resolving = withResolvers()\n    expect(resolving.promise).toBeInstanceOf(Promise)\n    expect(resolving.resolve).toBeInstanceOf(Function)\n    expect(resolving.reject).toBeInstanceOf(Function)\n    resolving.resolve('foo')\n    await expect(resolving.promise).resolves.toBe('foo')\n    const rejecting = withResolvers()\n    rejecting.reject('bar')\n    await expect(rejecting.promise).rejects.toBe('bar')\n  })\n  it('polyfills when support is not available', async () => {\n    if ('withResolvers' in Promise) {\n      // @ts-expect-error\n      delete Promise.withResolvers\n    }\n    expect('withResolvers' in Promise).toBe(false)\n    const resolving = withResolvers()\n    expect(resolving.promise).toBeInstanceOf(Promise)\n    expect(resolving.resolve).toBeInstanceOf(Function)\n    expect(resolving.reject).toBeInstanceOf(Function)\n    resolving.resolve('foo')\n    await expect(resolving.promise).resolves.toBe('foo')\n    const rejecting = withResolvers()\n    rejecting.reject('bar')\n    await expect(rejecting.promise).rejects.toBe('bar')\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/lib/with-resolvers.ts",
    "content": "export type Resolvers<T> = {\n  promise: Promise<T>\n  resolve: (value: T | PromiseLike<T>) => void\n  reject: (reason?: any) => void\n}\n\nexport function withResolvers<T>(): Resolvers<T> {\n  const P = Promise<T>\n  if (Promise.hasOwnProperty('withResolvers')) {\n    return Promise.withResolvers<T>()\n  }\n  // todo: Remove this once Promise.withResolvers is Baseline GA (September 2026)\n  let resolve: (value: T | PromiseLike<T>) => void = () => {}\n  let reject: () => void = () => {}\n  const promise = new P((res, rej) => {\n    resolve = res\n    reject = rej\n  })\n  return { promise, resolve, reject }\n}\n"
  },
  {
    "path": "packages/nuqs/src/loader.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { createLoader } from './loader'\nimport {\n  createParser,\n  parseAsInteger,\n  parseAsNativeArrayOf,\n  parseAsString\n} from './parsers'\n\ndescribe('loader', () => {\n  describe('sync', () => {\n    it('parses a URL object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(new URL('http://example.com/?a=1&b=2'))\n      expect(result).toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a Request object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(new Request('http://example.com/?a=1&b=2'))\n      expect(result).toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a URLSearchParams object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(new URLSearchParams('a=1&b=2'))\n      expect(result).toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a Record<string, string | string[] | undefined> object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load({\n        a: '1',\n        b: '2'\n      })\n      expect(result).toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a URL string', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load('https://example.com/?a=1&b=2')\n      expect(result).toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a search params string', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load('?a=1&b=2')\n      expect(result).toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('supports urlKeys', () => {\n      const load = createLoader(\n        {\n          urlKey: parseAsInteger\n        },\n        {\n          urlKeys: {\n            urlKey: 'a'\n          }\n        }\n      )\n      const result = load('?a=1')\n      expect(result).toEqual({\n        urlKey: 1\n      })\n    })\n    it('supports default values', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger.withDefault(2)\n      })\n      const result = load('')\n      expect(result).toEqual({\n        a: null,\n        b: 2\n      })\n    })\n    it('throws errors in strict mode when the parser returns null on non-empty queries', () => {\n      const load = createLoader({\n        test: createParser({\n          parse: () => null,\n          serialize: String\n        })\n      })\n      expect(() => load('?test=will-be-null', { strict: true })).toThrow(\n        '[nuqs] Failed to parse query `will-be-null` for key `test` (got null)'\n      )\n    })\n    it('throws errors in strict mode when the parser throws an error', () => {\n      const load = createLoader({\n        test: createParser({\n          parse: (): any => {\n            throw new Error('Boom')\n          },\n          serialize: String\n        })\n      })\n      expect(() => load('?test=will-throw', { strict: true })).toThrow(\n        '[nuqs] Error while parsing query `will-throw` for key `test`: Error: Boom'\n      )\n    })\n  })\n\n  describe('async', () => {\n    it('parses a URL object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(\n        Promise.resolve(new URL('http://example.com/?a=1&b=2'))\n      )\n      return expect(result).resolves.toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a Request object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(\n        Promise.resolve(new Request('http://example.com/?a=1&b=2'))\n      )\n      return expect(result).resolves.toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a URLSearchParams object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(Promise.resolve(new URLSearchParams('a=1&b=2')))\n      return expect(result).resolves.toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a Record<string, string | string[] | undefined> object', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(\n        Promise.resolve({\n          a: '1',\n          b: '2'\n        })\n      )\n      return expect(result).resolves.toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a URL string', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(Promise.resolve('https://example.com/?a=1&b=2'))\n      return expect(result).resolves.toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('parses a search params string', () => {\n      const load = createLoader({\n        a: parseAsInteger,\n        b: parseAsInteger\n      })\n      const result = load(Promise.resolve('?a=1&b=2'))\n      return expect(result).resolves.toEqual({\n        a: 1,\n        b: 2\n      })\n    })\n    it('supports urlKeys', () => {\n      const load = createLoader(\n        {\n          urlKey: parseAsInteger\n        },\n        {\n          urlKeys: {\n            urlKey: 'a'\n          }\n        }\n      )\n      const result = load(Promise.resolve('?a=1'))\n      return expect(result).resolves.toEqual({\n        urlKey: 1\n      })\n    })\n  })\n\n  describe('multi-parser', () => {\n    it('supports multi-parsers', () => {\n      const load = createLoader({\n        a: parseAsNativeArrayOf(parseAsInteger)\n      })\n      const result = load(new Request('http://example.com/?a=1&a=2&a=3'))\n      expect(result).toStrictEqual({ a: [1, 2, 3] })\n    })\n\n    it('removes un-parseable values', () => {\n      const load = createLoader({\n        a: parseAsNativeArrayOf(parseAsInteger)\n      })\n      const result = load(new Request('http://example.com/?a=foo&a=1'))\n      expect(result).toStrictEqual({ a: [1] })\n    })\n    it('defaults if everything is unparseable', () => {\n      const load = createLoader({\n        a: parseAsNativeArrayOf(parseAsInteger).withDefault([42])\n      })\n      const result = load(new Request('http://example.com/?a=foo&a=bar'))\n      expect(result).toStrictEqual({ a: [42] })\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/loader.ts",
    "content": "import type { UrlKeys } from './defs'\nimport { isAbsentFromUrl } from './lib/search-params'\nimport type { inferParserType, ParserMap } from './parsers'\n\nexport type LoaderInput =\n  | URL\n  | Request\n  | URLSearchParams\n  | Record<string, string | string[] | undefined>\n  | string\n\n/**\n * @deprecated Use `CreateLoaderOptions` instead.\n */\nexport type LoaderOptions<Parsers extends ParserMap> = {\n  urlKeys?: UrlKeys<Parsers>\n}\nexport type CreateLoaderOptions<P extends ParserMap> = LoaderOptions<P>\nexport type LoaderFunctionOptions = {\n  /**\n   * Whether to use strict parsing. If true, the loader will throw an error if\n   * any of the parsers fail to parse their respective values. If false, the\n   * loader will return null or their default value for any failed parsers.\n   */\n  strict?: boolean\n}\n\nexport type LoaderFunction<Parsers extends ParserMap> = {\n  /**\n   * Load & parse search params from (almost) any input.\n   *\n   * While loaders are typically used in the context of a React Router / Remix\n   * loader function, it can also be used in Next.js API routes or\n   * getServerSideProps functions, or even with the app router `searchParams`\n   * page prop (sync or async), if you don't need the cache behaviours.\n   */\n  (\n    input: LoaderInput,\n    options?: LoaderFunctionOptions\n  ): inferParserType<Parsers>\n  /**\n   * Load & parse search params from (almost) any input.\n   *\n   * While loaders are typically used in the context of a React Router / Remix\n   * loader function, it can also be used in Next.js API routes or\n   * getServerSideProps functions, or even with the app router `searchParams`\n   * page prop (sync or async), if you don't need the cache behaviours.\n   *\n   * Note: this async overload makes it easier to use against the `searchParams`\n   * page prop in Next.js 15 app router:\n   *\n   * ```tsx\n   * export default async function Page({ searchParams }) {\n   *   const parsedSearchParamsPromise = loadSearchParams(searchParams)\n   *   return (\n   *     // Pre-render & stream the shell immediately\n   *     <StaticShell>\n   *       <Suspense>\n   *         // Stream the Promise down\n   *         <DynamicComponent searchParams={parsedSearchParamsPromise} />\n   *       </Suspense>\n   *      </StaticShell>\n   *   )\n   * }\n   * ```\n   */\n  (\n    input: Promise<LoaderInput>,\n    options?: LoaderFunctionOptions\n  ): Promise<inferParserType<Parsers>>\n}\n\nexport function createLoader<Parsers extends ParserMap>(\n  parsers: Parsers,\n  { urlKeys = {} }: CreateLoaderOptions<Parsers> = {}\n): LoaderFunction<Parsers> {\n  type ParsedSearchParams = inferParserType<Parsers>\n\n  function loadSearchParams(\n    input: LoaderInput,\n    options?: LoaderFunctionOptions\n  ): ParsedSearchParams\n\n  function loadSearchParams(\n    input: Promise<LoaderInput>,\n    options?: LoaderFunctionOptions\n  ): Promise<ParsedSearchParams>\n\n  function loadSearchParams(\n    input: LoaderInput | Promise<LoaderInput>,\n    { strict = false }: LoaderFunctionOptions = {}\n  ) {\n    if (input instanceof Promise) {\n      return input.then(i => loadSearchParams(i, { strict }))\n    }\n    const searchParams = extractSearchParams(input)\n    const result = {} as any\n    for (const [key, parser] of Object.entries(parsers)) {\n      const urlKey = urlKeys[key] ?? key\n      const query =\n        parser.type === 'multi'\n          ? searchParams.getAll(urlKey)\n          : searchParams.get(urlKey)\n      if (isAbsentFromUrl(query)) {\n        result[key] = parser.defaultValue ?? null\n        continue\n      }\n      let parsedValue\n      try {\n        // we have properly narrowed `query` here, but TS doesn't keep track of that\n        parsedValue = parser.parse(query as string & Array<string>)\n      } catch (error) {\n        if (strict) {\n          throw new Error(\n            `[nuqs] Error while parsing query \\`${query}\\` for key \\`${key}\\`: ${error}`\n          )\n        }\n        parsedValue = null\n      }\n      if (strict && query && parsedValue === null) {\n        throw new Error(\n          `[nuqs] Failed to parse query \\`${query}\\` for key \\`${key}\\` (got null)`\n        )\n      }\n      result[key] = parsedValue ?? parser.defaultValue ?? null\n    }\n    return result\n  }\n  return loadSearchParams\n}\n\nfunction extractSearchParams(input: LoaderInput): URLSearchParams {\n  try {\n    if (input instanceof Request) {\n      return input.url ? new URL(input.url).searchParams : new URLSearchParams()\n    }\n    if (input instanceof URL) {\n      return input.searchParams\n    }\n    if (input instanceof URLSearchParams) {\n      return input\n    }\n    if (typeof input === 'object') {\n      const searchParams = new URLSearchParams()\n      for (const [key, value] of Object.entries(input)) {\n        if (Array.isArray(value)) {\n          for (const v of value) {\n            searchParams.append(key, v)\n          }\n        } else if (value !== undefined) {\n          searchParams.set(key, value)\n        }\n      }\n      return searchParams\n    }\n    if (typeof input === 'string') {\n      if (URL.hasOwnProperty('canParse') && URL.canParse(input)) {\n        return new URL(input).searchParams\n      }\n      return new URLSearchParams(input)\n    }\n  } catch {}\n  return new URLSearchParams()\n}\n"
  },
  {
    "path": "packages/nuqs/src/parsers.test.ts",
    "content": "import { type } from 'arktype'\nimport * as v from 'valibot'\nimport { describe, expect, it } from 'vitest'\nimport { z } from 'zod'\nimport {\n  parseAsArrayOf,\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsHex,\n  parseAsIndex,\n  parseAsInteger,\n  parseAsIsoDate,\n  parseAsIsoDateTime,\n  parseAsJson,\n  parseAsNativeArrayOf,\n  parseAsNumberLiteral,\n  parseAsString,\n  parseAsStringEnum,\n  parseAsStringLiteral,\n  parseAsTimestamp\n} from './parsers'\nimport {\n  isParserBijective,\n  testParseThenSerialize,\n  testSerializeThenParse\n} from './testing'\n\ndescribe('parsers', () => {\n  it('parseAsString', () => {\n    expect(parseAsString.parse('')).toBe('')\n    expect(parseAsString.parse('foo')).toBe('foo')\n    expect(isParserBijective(parseAsString, 'foo', 'foo')).toBe(true)\n  })\n  it('parseAsInteger', () => {\n    expect(parseAsInteger.parse('')).toBeNull()\n    expect(parseAsInteger.parse('1')).toBe(1)\n    expect(parseAsInteger.parse('3.14')).toBe(3)\n    expect(parseAsInteger.parse('3,14')).toBe(3)\n    expect(parseAsInteger.serialize(3.14)).toBe('3')\n    expect(isParserBijective(parseAsInteger, '3', 3)).toBe(true)\n    expect(() => testParseThenSerialize(parseAsInteger, '3.14')).toThrow()\n    expect(() => testSerializeThenParse(parseAsInteger, 3.14)).toThrow()\n  })\n  it('parseAsHex', () => {\n    expect(parseAsHex.parse('')).toBeNull()\n    expect(parseAsHex.parse('1')).toBe(1)\n    expect(parseAsHex.parse('a')).toBe(0xa)\n    expect(parseAsHex.parse('g')).toBeNull()\n    expect(parseAsHex.serialize(0xa)).toBe('0a')\n    for (let byte = 0; byte < 256; byte++) {\n      const hexString = byte.toString(16).padStart(2, '0')\n      expect(isParserBijective(parseAsHex, hexString, byte)).toBe(true)\n    }\n  })\n  it('parseAsFloat', () => {\n    expect(parseAsFloat.parse('')).toBeNull()\n    expect(parseAsFloat.parse('1')).toBe(1)\n    expect(parseAsFloat.parse('3.14')).toBe(3.14)\n    expect(parseAsFloat.parse('3,14')).toBe(3)\n    expect(parseAsFloat.serialize(3.14)).toBe('3.14')\n    // https://0.30000000000000004.com/\n    expect(parseAsFloat.serialize(0.1 + 0.2)).toBe('0.30000000000000004')\n    expect(isParserBijective(parseAsFloat, '3.14', 3.14)).toBe(true)\n  })\n  it('parseAsIndex', () => {\n    expect(parseAsIndex.parse('')).toBeNull()\n    expect(parseAsIndex.parse('1')).toBe(0)\n    expect(parseAsIndex.parse('3.14')).toBe(2)\n    expect(parseAsIndex.parse('3,14')).toBe(2)\n    expect(parseAsIndex.parse('0')).toBe(-1)\n    expect(parseAsIndex.parse('-1')).toBe(-2)\n    expect(parseAsIndex.serialize(0)).toBe('1')\n    expect(parseAsIndex.serialize(3.14)).toBe('4')\n    expect(isParserBijective(parseAsIndex, '1', 0)).toBe(true)\n    expect(isParserBijective(parseAsIndex, '2', 1)).toBe(true)\n  })\n  it('parseAsHex', () => {\n    expect(parseAsHex.parse('')).toBeNull()\n    expect(parseAsHex.parse('1')).toBe(1)\n    expect(parseAsHex.parse('a')).toBe(0xa)\n    expect(parseAsHex.parse('g')).toBeNull()\n    expect(parseAsHex.serialize(0x0a)).toBe('0a')\n    expect(parseAsHex.serialize(0x2a)).toBe('2a')\n    expect(isParserBijective(parseAsHex, '0a', 0x0a)).toBe(true)\n    expect(isParserBijective(parseAsHex, '2a', 0x2a)).toBe(true)\n  })\n  it('parseAsBoolean', () => {\n    expect(parseAsBoolean.parse('')).toBe(false)\n    // It only triggers on 'true' (case insensitive), everything else is false\n    expect(parseAsBoolean.parse('true')).toBe(true)\n    expect(parseAsBoolean.parse('TRUE')).toBe(true)\n    expect(parseAsBoolean.parse('True')).toBe(true)\n    expect(parseAsBoolean.parse('false')).toBe(false)\n    expect(parseAsBoolean.parse('FALSE')).toBe(false)\n    expect(parseAsBoolean.parse('0')).toBe(false)\n    expect(parseAsBoolean.parse('1')).toBe(false)\n    expect(parseAsBoolean.parse('yes')).toBe(false)\n    expect(parseAsBoolean.parse('no')).toBe(false)\n    expect(parseAsBoolean.serialize(true)).toBe('true')\n    expect(parseAsBoolean.serialize(false)).toBe('false')\n    expect(isParserBijective(parseAsBoolean, 'true', true)).toBe(true)\n    expect(isParserBijective(parseAsBoolean, 'false', false)).toBe(true)\n  })\n\n  it('parseAsTimestamp', () => {\n    expect(parseAsTimestamp.parse('')).toBeNull()\n    expect(parseAsTimestamp.parse('0')).toStrictEqual(new Date(0))\n    expect(testParseThenSerialize(parseAsTimestamp, '0')).toBe(true)\n    expect(testSerializeThenParse(parseAsTimestamp, new Date(1234567890))).toBe(\n      true\n    )\n    expect(isParserBijective(parseAsTimestamp, '0', new Date(0))).toBe(true)\n    expect(\n      isParserBijective(parseAsTimestamp, '1234567890', new Date(1234567890))\n    ).toBe(true)\n  })\n  it('parseAsIsoDateTime', () => {\n    expect(parseAsIsoDateTime.parse('')).toBeNull()\n    expect(parseAsIsoDateTime.parse('not-a-date')).toBeNull()\n    const moment = '2020-01-01T00:00:00.000Z'\n    const ref = new Date(moment)\n    expect(parseAsIsoDateTime.parse(moment)).toStrictEqual(ref)\n    expect(parseAsIsoDateTime.parse(moment.slice(0, 10))).toStrictEqual(ref)\n    expect(parseAsIsoDateTime.parse(moment.slice(0, 16) + 'Z')).toStrictEqual(\n      ref\n    )\n    expect(testParseThenSerialize(parseAsIsoDateTime, moment)).toBe(true)\n    expect(testSerializeThenParse(parseAsIsoDateTime, ref)).toBe(true)\n    expect(isParserBijective(parseAsIsoDateTime, moment, ref)).toBe(true)\n  })\n  it('parseAsIsoDate', () => {\n    expect(parseAsIsoDate.parse('')).toBeNull()\n    expect(parseAsIsoDate.parse('not-a-date')).toBeNull()\n    const moment = '2020-01-01'\n    const ref = new Date(moment)\n    expect(parseAsIsoDate.parse(moment)).toStrictEqual(ref)\n    expect(parseAsIsoDate.serialize(ref)).toEqual(moment)\n    expect(testParseThenSerialize(parseAsIsoDate, moment)).toBe(true)\n    expect(testSerializeThenParse(parseAsIsoDate, ref)).toBe(true)\n    expect(isParserBijective(parseAsIsoDate, moment, ref)).toBe(true)\n  })\n  it('parseAsStringEnum', () => {\n    enum Test {\n      A = 'a',\n      B = 'b',\n      C = 'c'\n    }\n    const parser = parseAsStringEnum<Test>(Object.values(Test))\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('a')).toBe('a')\n    expect(parser.parse('b')).toBe('b')\n    expect(parser.parse('c')).toBe('c')\n    expect(parser.parse('d')).toBeNull()\n    expect(parser.serialize(Test.A)).toBe('a')\n    expect(parser.serialize(Test.B)).toBe('b')\n    expect(parser.serialize(Test.C)).toBe('c')\n    expect(testParseThenSerialize(parser, 'a')).toBe(true)\n    expect(testSerializeThenParse(parser, Test.A)).toBe(true)\n    expect(isParserBijective(parser, 'b', Test.B)).toBe(true)\n  })\n  it('parseAsStringLiteral', () => {\n    const parser = parseAsStringLiteral(['a', 'b', 'c'])\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('a')).toBe('a')\n    expect(parser.parse('b')).toBe('b')\n    expect(parser.parse('c')).toBe('c')\n    expect(parser.parse('d')).toBeNull()\n    expect(parser.serialize('a')).toBe('a')\n    expect(parser.serialize('b')).toBe('b')\n    expect(parser.serialize('c')).toBe('c')\n    expect(testParseThenSerialize(parser, 'a')).toBe(true)\n    expect(testSerializeThenParse(parser, 'a')).toBe(true)\n    expect(isParserBijective(parser, 'a', 'a')).toBe(true)\n    expect(isParserBijective(parser, 'b', 'b')).toBe(true)\n    expect(isParserBijective(parser, 'c', 'c')).toBe(true)\n  })\n  it('parseAsNumberLiteral', () => {\n    const parser = parseAsNumberLiteral([1, 2, 3])\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('1')).toBe(1)\n    expect(parser.parse('2')).toBe(2)\n    expect(parser.parse('3')).toBe(3)\n    expect(parser.parse('4')).toBeNull()\n    expect(parser.serialize(1)).toBe('1')\n    expect(parser.serialize(2)).toBe('2')\n    expect(parser.serialize(3)).toBe('3')\n    expect(testParseThenSerialize(parser, '1')).toBe(true)\n    expect(testSerializeThenParse(parser, 1)).toBe(true)\n    expect(isParserBijective(parser, '1', 1)).toBe(true)\n    expect(isParserBijective(parser, '2', 2)).toBe(true)\n    expect(isParserBijective(parser, '3', 3)).toBe(true)\n  })\n\n  it('parseAsJson (validator: ArkType)', () => {\n    const schema = type({\n      foo: 'string',\n      bar: 'number'\n    })\n    const parser = parseAsJson(schema) // note: using the schema directly\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":42}')).toEqual({\n      foo: 'abc',\n      bar: 42\n    })\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":\"not-a-number\"}')).toBeNull()\n    expect(parser.serialize({ foo: 'abc', bar: 42 })).toBe(\n      '{\"foo\":\"abc\",\"bar\":42}'\n    )\n    expect(testParseThenSerialize(parser, '{\"foo\":\"abc\",\"bar\":42}')).toBe(true)\n    expect(testSerializeThenParse(parser, { foo: 'abc', bar: 42 })).toBe(true)\n    expect(\n      isParserBijective(parser, '{\"foo\":\"abc\",\"bar\":42}', {\n        foo: 'abc',\n        bar: 42\n      })\n    ).toBe(true)\n  })\n\n  it('parseAsJson (validator: Valibot)', () => {\n    const schema = v.object({\n      foo: v.string(),\n      bar: v.number()\n    })\n    const parser = parseAsJson(schema) // note: using the schema directly\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":42}')).toEqual({\n      foo: 'abc',\n      bar: 42\n    })\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":\"not-a-number\"}')).toBeNull()\n    expect(parser.serialize({ foo: 'abc', bar: 42 })).toBe(\n      '{\"foo\":\"abc\",\"bar\":42}'\n    )\n    expect(testParseThenSerialize(parser, '{\"foo\":\"abc\",\"bar\":42}')).toBe(true)\n    expect(testSerializeThenParse(parser, { foo: 'abc', bar: 42 })).toBe(true)\n    expect(\n      isParserBijective(parser, '{\"foo\":\"abc\",\"bar\":42}', {\n        foo: 'abc',\n        bar: 42\n      })\n    ).toBe(true)\n  })\n\n  it('parseAsJson (validator: Zod parse function)', () => {\n    const schema = z.object({\n      foo: z.string(),\n      bar: z.number()\n    })\n    const parser = parseAsJson(schema.parse)\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":42}')).toEqual({\n      foo: 'abc',\n      bar: 42\n    })\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":\"not-a-number\"}')).toBeNull()\n    expect(parser.serialize({ foo: 'abc', bar: 42 })).toBe(\n      '{\"foo\":\"abc\",\"bar\":42}'\n    )\n    expect(testParseThenSerialize(parser, '{\"foo\":\"abc\",\"bar\":42}')).toBe(true)\n    expect(testSerializeThenParse(parser, { foo: 'abc', bar: 42 })).toBe(true)\n    expect(\n      isParserBijective(parser, '{\"foo\":\"abc\",\"bar\":42}', {\n        foo: 'abc',\n        bar: 42\n      })\n    ).toBe(true)\n  })\n\n  it('parseAsJson (validator: Zod schema via Standard Schema)', () => {\n    const schema = z.object({\n      foo: z.string(),\n      bar: z.number()\n    })\n    const parser = parseAsJson(schema) // note: using the schema directly\n    expect(parser.parse('')).toBeNull()\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":42}')).toEqual({\n      foo: 'abc',\n      bar: 42\n    })\n    expect(parser.parse('{\"foo\":\"abc\",\"bar\":\"not-a-number\"}')).toBeNull()\n    expect(parser.serialize({ foo: 'abc', bar: 42 })).toBe(\n      '{\"foo\":\"abc\",\"bar\":42}'\n    )\n    expect(testParseThenSerialize(parser, '{\"foo\":\"abc\",\"bar\":42}')).toBe(true)\n    expect(testSerializeThenParse(parser, { foo: 'abc', bar: 42 })).toBe(true)\n    expect(\n      isParserBijective(parser, '{\"foo\":\"abc\",\"bar\":42}', {\n        foo: 'abc',\n        bar: 42\n      })\n    ).toBe(true)\n  })\n\n  it('parseAsArrayOf', () => {\n    const parser = parseAsArrayOf(parseAsString)\n    expect(parser.serialize([])).toBe('')\n    // It encodes its separator\n    expect(parser.serialize(['a', ',', 'b'])).toBe('a,%2C,b')\n    expect(testParseThenSerialize(parser, 'a,b')).toBe(true)\n    expect(testSerializeThenParse(parser, ['a', 'b'])).toBe(true)\n    expect(isParserBijective(parser, 'a,b', ['a', 'b'])).toBe(true)\n    expect(() =>\n      isParserBijective(parser, 'not-an-array', ['a', 'b'])\n    ).toThrow()\n  })\n\n  describe('parseAsNativeArrayOf', () => {\n    it('serializes', () => {\n      const parser = parseAsNativeArrayOf(parseAsString)\n      expect(parser.serialize([])).toStrictEqual([])\n      expect(parser.serialize(['a', ',', 'b'])).toStrictEqual(['a', ',', 'b'])\n    })\n    it('parses', () => {\n      const parser = parseAsNativeArrayOf(parseAsInteger)\n      expect(parser.parse([])).toStrictEqual(null)\n      expect(parser.parse(['1', '2'])).toStrictEqual([1, 2])\n    })\n    it('defaults to null', () => {\n      const parser = parseAsNativeArrayOf(parseAsInteger)\n      expect(parser.parse(['not', 'a', 'number'])).toStrictEqual(null)\n    })\n    it('is bijective', () => {\n      const parser = parseAsNativeArrayOf(parseAsString)\n      expect(isParserBijective(parser, ['a', 'b'], ['a', 'b'])).toBe(true)\n      expect(() => isParserBijective(parser, ['1', '2'], ['a', 'b'])).toThrow()\n    })\n  })\n\n  it('parseServerSide with default (#384)', () => {\n    const p = parseAsString.withDefault('default')\n    const searchParams = {\n      string: 'foo',\n      stringArray: ['bar', 'egg'],\n      undef: undefined\n    }\n    expect(p.parseServerSide(searchParams.undef)).toBe('default')\n    expect(p.parseServerSide(searchParams.string)).toBe('foo')\n    expect(p.parseServerSide(searchParams.stringArray)).toBe('bar')\n    // @ts-expect-error - Implicitly undefined\n    expect(p.parseServerSide(searchParams.nope)).toBe('default')\n  })\n\n  it('does not reset options when chaining them', () => {\n    const p = parseAsString.withOptions({ scroll: true }).withOptions({})\n    expect(p.scroll).toBe(true)\n  })\n  it('merges options when chaining them', () => {\n    const p = parseAsString\n      .withOptions({ scroll: true })\n      .withOptions({ history: 'push' })\n    expect(p.scroll).toBe(true)\n    expect(p.history).toBe('push')\n  })\n  it('merges default values when chaining options', () => {\n    const p = parseAsString\n      .withOptions({ scroll: true })\n      .withDefault('default')\n      .withOptions({ history: 'push' })\n    expect(p.scroll).toBe(true)\n    expect(p.history).toBe('push')\n    expect(p.defaultValue).toBe('default')\n    expect(p.parseServerSide(undefined)).toBe('default')\n  })\n  it('allows changing the default value', () => {\n    const p = parseAsString.withDefault('foo').withDefault('bar')\n    expect(p.defaultValue).toBe('bar')\n    expect(p.parseServerSide(undefined)).toBe('bar')\n  })\n})\n\ndescribe('parsers/equality', () => {\n  it('parseAsArrayOf', () => {\n    const eq = parseAsArrayOf(parseAsString).eq!\n    expect(eq([], [])).toBe(true)\n    expect(eq(['foo'], ['foo'])).toBe(true)\n    expect(eq(['foo', 'bar'], ['foo', 'bar'])).toBe(true)\n    expect(eq([], ['foo'])).toBe(false)\n    expect(eq(['foo'], ['bar'])).toBe(false)\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/parsers.ts",
    "content": "import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { Options } from './defs'\nimport { safeParse } from './lib/safe-parse'\n\ntype Require<T, Keys extends keyof T> = Pick<Required<T>, Keys> & Omit<T, Keys>\n\nexport type SingleParser<T> = {\n  type?: 'single'\n  /**\n   * Convert a query string value into a state value.\n   *\n   * If the string value does not represent a valid state value,\n   * the parser should return `null`. Throwing an error is also supported.\n   */\n  parse: (value: string) => T | null\n\n  /**\n   * Render the state value into a query string value.\n   */\n  serialize?: (value: T) => string\n\n  /**\n   * Check if two state values are equal.\n   *\n   * This is used when using the `clearOnDefault` value, to compare the default\n   * value with the set value.\n   *\n   * It makes sense to provide this function when the state value is an object\n   * or an array, as the default referential equality check will not work.\n   */\n  eq?: (a: T, b: T) => boolean\n}\n\nexport type MultiParser<T> = {\n  type: 'multi'\n  parse: (value: ReadonlyArray<string>) => T | null\n  serialize?: (value: T) => Array<string>\n  eq?: (a: T, b: T) => boolean\n}\n\nexport type GenericParser<T> = SingleParser<T> | MultiParser<T>\nexport type GenericParserBuilder<T> =\n  | SingleParserBuilder<T>\n  | MultiParserBuilder<T>\n\n/* type aliases for backwards compatibility */\n/** @deprecated use SingleParser instead */\nexport type Parser<T> = SingleParser<T>\n/** @deprecated use SingleParserBuilder instead */\nexport type ParserBuilder<T> = SingleParserBuilder<T>\n\nexport type SingleParserBuilder<T> = Required<SingleParser<T>> &\n  Options & {\n    /**\n     * Set history type, shallow routing and scroll restoration options\n     * at the hook declaration level.\n     *\n     * Note that you can override those options in individual calls to the\n     * state updater function.\n     */\n    withOptions<This>(this: This, options: Options): This\n\n    /**\n     * Specifying a default value makes the hook state non-nullable when the\n     * query is missing from the URL: the default value is returned instead\n     * of `null`.\n     *\n     * Setting the state to the default value¹ will clear the query string key\n     * from the URL, unless `clearOnDefault` is set to `false`.\n     *\n     * Setting the state to `null` will always clear the query string key\n     * from the URL, and return the default value.\n     *\n     * ¹: Equality is checked with the parser's `eq` function, or referential\n     * equality if not provided.\n     *\n     * @param defaultValue\n     */\n    withDefault(\n      this: SingleParserBuilder<T>,\n      defaultValue: NonNullable<T>\n    ): Omit<SingleParserBuilder<T>, 'parseServerSide'> & {\n      readonly defaultValue: NonNullable<T>\n\n      /**\n       * Use the parser in Server Components\n       *\n       * `parse` is intended to be used only by the hook, but you can use this\n       * method to hydrate query values on server-side rendered pages.\n       * See the `server-side-parsing` demo for an example.\n       *\n       * Note that when multiple queries are presented to the parser\n       * (eg: `/?a=1&a=2`), only the **first** will be parsed, to mimic the\n       * behaviour of URLSearchParams:\n       * https://url.spec.whatwg.org/#dom-urlsearchparams-get\n       *\n       * @param value as coming from page props\n       *\n       * @deprecated prefer using loaders instead, as they enforce a strong\n       * bond between the data type and the search param key.\n       */\n      parseServerSide(value: string | string[] | undefined): NonNullable<T>\n    }\n\n    /**\n     * Use the parser in Server Components\n     *\n     * `parse` is intended to be used only by the hook, but you can use this\n     * method to hydrate query values on server-side rendered pages.\n     * See the `server-side-parsing` demo for an example.\n     *\n     * Note that when multiple queries are presented to the parser\n     * (eg: `/?a=1&a=2`), only the **first** will be parsed, to mimic the\n     * behaviour of URLSearchParams:\n     * https://url.spec.whatwg.org/#dom-urlsearchparams-get\n     *\n     * @param value as coming from page props\n     *\n     * @deprecated prefer using loaders instead, as they enforce a strong\n     * bond between the data type and the search param key.\n     */\n    parseServerSide(value: string | string[] | undefined): T | null\n  }\n\nexport type MultiParserBuilder<T> = Required<MultiParser<T>> &\n  Options & {\n    withOptions<This>(this: This, options: Options): This\n    withDefault(\n      this: MultiParserBuilder<T>,\n      defaultValue: NonNullable<T>\n    ): Omit<MultiParserBuilder<T>, 'parseServerSide'> & {\n      readonly defaultValue: NonNullable<T>\n      /**\n       * @deprecated exposed for symmetry with SingleParserBuilder only,\n       * prefer using loaders instead.\n       */\n      parseServerSide(value: string | string[] | undefined): NonNullable<T>\n    }\n    /**\n     * @deprecated exposed for symmetry with SingleParserBuilder only,\n     * prefer using loaders instead.\n     */\n    parseServerSide(value: string | string[] | undefined): T | null\n  }\n\n/**\n * Wrap a set of parse/serialize functions into a builder pattern parser\n * you can pass to one of the hooks, making its default value type safe.\n */\nexport function createParser<T>(\n  parser: Require<SingleParser<T>, 'parse' | 'serialize'>\n): SingleParserBuilder<T> {\n  function parseServerSideNullable(value: string | string[] | undefined) {\n    if (typeof value === 'undefined') {\n      return null\n    }\n    let str = ''\n    if (Array.isArray(value)) {\n      // Follow the spec:\n      // https://url.spec.whatwg.org/#dom-urlsearchparams-get\n      if (value[0] === undefined) {\n        return null\n      }\n      str = value[0]\n    }\n    if (typeof value === 'string') {\n      str = value\n    }\n    return safeParse(parser.parse, str)\n  }\n\n  return {\n    type: 'single',\n    eq: (a, b) => a === b,\n    ...parser,\n    parseServerSide: parseServerSideNullable,\n    withDefault(defaultValue) {\n      return {\n        ...this,\n        defaultValue,\n        parseServerSide(value) {\n          return parseServerSideNullable(value) ?? defaultValue\n        }\n      }\n    },\n    withOptions(options: Options) {\n      return {\n        ...this,\n        ...options\n      }\n    }\n  }\n}\n\nexport function createMultiParser<T>(\n  parser: Omit<Require<MultiParser<T>, 'parse' | 'serialize'>, 'type'>\n): MultiParserBuilder<T> {\n  function parseServerSideNullable(value: string | string[] | undefined) {\n    if (typeof value === 'undefined') {\n      return null\n    }\n    return safeParse(parser.parse, Array.isArray(value) ? value : [value])\n  }\n\n  return {\n    type: 'multi',\n    eq: (a, b) => a === b,\n    ...parser,\n    parseServerSide: parseServerSideNullable,\n    withDefault(defaultValue) {\n      return {\n        ...this,\n        defaultValue,\n        parseServerSide(value) {\n          return parseServerSideNullable(value) ?? defaultValue\n        }\n      }\n    },\n    withOptions(options: Options) {\n      return {\n        ...this,\n        ...options\n      }\n    }\n  }\n}\n\n// Parsers implementations -----------------------------------------------------\n\nexport const parseAsString: SingleParserBuilder<string> = createParser({\n  parse: v => v,\n  serialize: String\n})\n\nexport const parseAsInteger: SingleParserBuilder<number> = createParser({\n  parse: v => {\n    const int = parseInt(v)\n    return int == int ? int : null // NaN check at low bundle size cost\n  },\n  serialize: v => '' + Math.round(v)\n})\n\nexport const parseAsIndex: SingleParserBuilder<number> = createParser({\n  parse: v => {\n    const int = parseInt(v)\n    return int == int ? int - 1 : null // NaN check at low bundle size cost\n  },\n  serialize: v => '' + Math.round(v + 1)\n})\n\nexport const parseAsHex: SingleParserBuilder<number> = createParser({\n  parse: v => {\n    const int = parseInt(v, 16)\n    return int == int ? int : null // NaN check at low bundle size cost\n  },\n  serialize: v => {\n    const hex = Math.round(v).toString(16)\n    return (hex.length & 1 ? '0' : '') + hex\n  }\n})\n\nexport const parseAsFloat: SingleParserBuilder<number> = createParser({\n  parse: v => {\n    const float = parseFloat(v)\n    return float == float ? float : null // NaN check at low bundle size cost\n  },\n  serialize: String\n})\n\nexport const parseAsBoolean: SingleParserBuilder<boolean> = createParser({\n  parse: v => v.toLowerCase() === 'true',\n  serialize: String\n})\n\nfunction compareDates(a: Date, b: Date) {\n  return a.valueOf() === b.valueOf()\n}\n\n/**\n * Querystring encoded as the number of milliseconds since epoch,\n * and returned as a Date object.\n */\nexport const parseAsTimestamp: SingleParserBuilder<Date> = createParser({\n  parse: v => {\n    const ms = parseInt(v)\n    return ms == ms ? new Date(ms) : null // NaN check at low bundle size cost\n  },\n  serialize: (v: Date) => '' + v.valueOf(),\n  eq: compareDates\n})\n\n/**\n * Querystring encoded as an ISO-8601 string (UTC),\n * and returned as a Date object.\n */\nexport const parseAsIsoDateTime: SingleParserBuilder<Date> = createParser({\n  parse: v => {\n    const date = new Date(v)\n    // NaN check at low bundle size cost\n    return date.valueOf() == date.valueOf() ? date : null\n  },\n  serialize: (v: Date) => v.toISOString(),\n  eq: compareDates\n})\n\n/**\n * Querystring encoded as an ISO-8601 string (UTC)\n * without the time zone offset, and returned as\n * a Date object.\n *\n * The Date is parsed without the time zone offset,\n * making it at 00:00:00 UTC.\n */\nexport const parseAsIsoDate: SingleParserBuilder<Date> = createParser({\n  parse: v => {\n    const date = new Date(v.slice(0, 10))\n    // NaN check at low bundle size cost\n    return date.valueOf() == date.valueOf() ? date : null\n  },\n  serialize: (v: Date) => v.toISOString().slice(0, 10),\n  eq: compareDates\n})\n\n/**\n * String-based enums provide better type-safety for known sets of values.\n * You will need to pass the parseAsStringEnum function a list of your enum values\n * in order to validate the query string. Anything else will return `null`,\n * or your default value if specified.\n *\n * Example:\n * ```ts\n * enum Direction {\n *   up = 'UP',\n *   down = 'DOWN',\n *   left = 'LEFT',\n *   right = 'RIGHT'\n * }\n *\n * const [direction, setDirection] = useQueryState(\n *   'direction',\n *    parseAsStringEnum<Direction>(Object.values(Direction)) // pass a list of allowed values\n *      .withDefault(Direction.up)\n * )\n * ```\n *\n * Note: the query string value will be the value of the enum, not its name\n * (example above: `direction=UP`).\n *\n * @param validValues The values you want to accept\n */\nexport function parseAsStringEnum<Enum extends string>(\n  validValues: Enum[]\n): SingleParserBuilder<Enum> {\n  // Delegate implementation to parseAsStringLiteral to avoid duplication.\n  return parseAsStringLiteral(validValues as readonly Enum[])\n}\n\n/**\n * String-based literals provide better type-safety for known sets of values.\n * You will need to pass the parseAsStringLiteral function a list of your string values\n * in order to validate the query string. Anything else will return `null`,\n * or your default value if specified.\n *\n * Example:\n * ```ts\n * const colors = [\"red\", \"green\", \"blue\"] as const\n *\n * const [color, setColor] = useQueryState(\n *   'color',\n *    parseAsStringLiteral(colors) // pass a readonly list of allowed values\n *      .withDefault(\"red\")\n * )\n * ```\n *\n * @param validValues The values you want to accept\n */\nexport function parseAsStringLiteral<const Literal extends string>(\n  validValues: readonly Literal[]\n): SingleParserBuilder<Literal> {\n  return createParser({\n    parse: (query: string) => {\n      const asConst = query as unknown as Literal\n      return validValues.includes(asConst) ? asConst : null\n    },\n    serialize: String\n  })\n}\n\n/**\n * Number-based literals provide better type-safety for known sets of values.\n * You will need to pass the parseAsNumberLiteral function a list of your number values\n * in order to validate the query string. Anything else will return `null`,\n * or your default value if specified.\n *\n * Example:\n * ```ts\n * const diceSides = [1, 2, 3, 4, 5, 6] as const\n *\n * const [side, setSide] = useQueryState(\n *   'side',\n *    parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values\n *      .withDefault(4)\n * )\n * ```\n *\n * @param validValues The values you want to accept\n */\nexport function parseAsNumberLiteral<const Literal extends number>(\n  validValues: readonly Literal[]\n): SingleParserBuilder<Literal> {\n  return createParser({\n    parse: (query: string) => {\n      const asConst = parseFloat(query) as unknown as Literal\n      if (validValues.includes(asConst)) {\n        return asConst\n      }\n      return null\n    },\n    serialize: String\n  })\n}\n\n/**\n * Encode any object shape into the querystring value as JSON.\n * Note: you may want to use `useQueryStates` for finer control over\n * multiple related query keys.\n *\n * @param runtimeParser Runtime parser (eg: Zod schema or Standard Schema) to validate after JSON.parse\n */\nexport function parseAsJson<T>(\n  validator: ((value: unknown) => T | null) | StandardSchemaV1<T>\n): SingleParserBuilder<T> {\n  return createParser({\n    parse: query => {\n      try {\n        const obj = JSON.parse(query)\n        if ('~standard' in validator) {\n          const result = validator['~standard'].validate(obj)\n          if (result instanceof Promise) {\n            throw new Error(\n              '[nuqs] Only synchronous Standard Schemas are supported in parseAsJson.'\n            )\n          }\n          return result.issues ? null : result.value\n        }\n        return validator(obj)\n      } catch {\n        return null\n      }\n    },\n    serialize: value => JSON.stringify(value),\n    eq(a, b) {\n      // Check for referential equality first\n      return a === b || JSON.stringify(a) === JSON.stringify(b)\n    }\n  })\n}\n\n/**\n * A comma-separated list of items.\n * Items are URI-encoded for safety, so they may not look nice in the URL.\n *\n * @param itemParser Parser for each individual item in the array\n * @param separator The character to use to separate items (default ',')\n */\nexport function parseAsArrayOf<ItemType>(\n  itemParser: SingleParser<ItemType>,\n  separator = ','\n): SingleParserBuilder<ItemType[]> {\n  const itemEq = itemParser.eq ?? ((a: ItemType, b: ItemType) => a === b)\n  const encodedSeparator = encodeURIComponent(separator)\n  // todo: Handle default item values and make return type non-nullable\n  return createParser({\n    parse: query => {\n      if (query === '') {\n        // Empty query should not go through the split/map/filter logic,\n        // see https://github.com/47ng/nuqs/issues/329\n        return [] as ItemType[]\n      }\n      return query\n        .split(separator)\n        .map((item, index) =>\n          safeParse(\n            itemParser.parse,\n            item.replaceAll(encodedSeparator, separator),\n            `[${index}]`\n          )\n        )\n        .filter(value => value !== null && value !== undefined) as ItemType[]\n    },\n    serialize: values =>\n      values\n        .map<string>(value => {\n          const str = itemParser.serialize\n            ? itemParser.serialize(value)\n            : String(value)\n          return str.replaceAll(separator, encodedSeparator)\n        })\n        .join(separator),\n    eq(a, b) {\n      if (a === b) {\n        return true // Referentially stable\n      }\n      if (a.length !== b.length) {\n        return false\n      }\n      return a.every((value, index) => itemEq(value, b[index]!))\n    }\n  })\n}\n\nexport function parseAsNativeArrayOf<ItemType>(\n  itemParser: SingleParser<ItemType>\n): ReturnType<MultiParserBuilder<ItemType[]>['withDefault']> {\n  const itemEq = itemParser.eq ?? ((a: ItemType, b: ItemType) => a === b)\n  return createMultiParser({\n    parse: query => {\n      const parsed = query\n        .map((item, index) => safeParse(itemParser.parse, item, `[${index}]`))\n        .filter(value => value !== null && value !== undefined) as ItemType[]\n      return parsed.length === 0 ? null : parsed\n    },\n    serialize: values => {\n      // defensive check because we potentially get a single value passed from a standard schema\n      const safeValues = Array.isArray(values) ? values : [values]\n      return safeValues.flatMap(value => {\n        const serialized = itemParser.serialize?.(value) ?? String(value)\n        return typeof serialized === 'string' ? [serialized] : [...serialized]\n      })\n    },\n    eq(a, b) {\n      if (a === b) {\n        return true // Referentially stable\n      }\n      if (a.length !== b.length) {\n        return false\n      }\n      return a.every((value, index) => itemEq(value, b[index]!))\n    }\n  }).withDefault([])\n}\n\ntype inferSingleParserType<Parser> = Parser extends GenericParserBuilder<\n  infer Value\n> & {\n  defaultValue: infer Value\n}\n  ? Value\n  : Parser extends GenericParserBuilder<infer Value>\n    ? Value | null\n    : never\n\ntype inferParserRecordType<\n  Map extends Record<string, GenericParserBuilder<any>>\n> = {\n  [Key in keyof Map]: inferSingleParserType<Map[Key]>\n} & {}\n\n/**\n * Type helper to extract the underlying returned data type of a parser\n * or of an object describing multiple parsers and their associated keys.\n *\n * Usage:\n *\n * ```ts\n * import { type inferParserType } from 'nuqs' // or 'nuqs/server'\n *\n * const intNullable = parseAsInteger\n * const intNonNull = parseAsInteger.withDefault(0)\n *\n * inferParserType<typeof intNullable> // number | null\n * inferParserType<typeof intNonNull> // number\n *\n * const parsers = {\n *  a: parseAsInteger,\n *  b: parseAsBoolean.withDefault(false)\n * }\n *\n * inferParserType<typeof parsers>\n * // { a: number | null, b: boolean }\n * ```\n */\nexport type inferParserType<Input> =\n  Input extends GenericParserBuilder<any>\n    ? inferSingleParserType<Input>\n    : Input extends Record<string, GenericParserBuilder<any>>\n      ? inferParserRecordType<Input>\n      : never\n\nexport type ParserWithOptionalDefault<T> = GenericParserBuilder<T> & {\n  defaultValue?: T\n}\nexport type ParserMap = Record<string, ParserWithOptionalDefault<any>>\n"
  },
  {
    "path": "packages/nuqs/src/serializer.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport type { Options } from './defs'\nimport {\n  parseAsArrayOf,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsJson,\n  parseAsNativeArrayOf,\n  parseAsString\n} from './parsers'\nimport { createSerializer } from './serializer'\n\nconst parsers = {\n  str: parseAsString,\n  int: parseAsInteger,\n  bool: parseAsBoolean,\n  multi: parseAsNativeArrayOf(parseAsString)\n}\n\ndescribe('serializer', () => {\n  it('handles empty inputs', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize({})\n    expect(result).toBe('')\n  })\n  it('handles a single item', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize({ str: 'foo' })\n    expect(result).toBe('?str=foo')\n  })\n  it('handles several items', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize({ str: 'foo', int: 1, bool: true })\n    expect(result).toBe('?str=foo&int=1&bool=true')\n  })\n  it('does not render null items', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize({ str: null })\n    expect(result).toBe('')\n  })\n  it('handles a string base', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize('/foo', { str: 'foo' })\n    expect(result).toBe('/foo?str=foo')\n  })\n  it('handles a string base with search params', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize('/foo?bar=egg', { str: 'foo' })\n    expect(result).toBe('/foo?bar=egg&str=foo')\n  })\n  it('handles a URLSearchParams base', () => {\n    const serialize = createSerializer(parsers)\n    const search = new URLSearchParams('?bar=egg')\n    const result = serialize(search, { str: 'foo' })\n    expect(result).toBe('?bar=egg&str=foo')\n  })\n  it('does not mutate existing params with URLSearchParams base', () => {\n    const serialize = createSerializer(parsers)\n    const searchBefore = new URLSearchParams('?str=foo')\n    const result = serialize(searchBefore, { str: 'bar' })\n    expect(result).toBe('?str=bar')\n    expect(searchBefore.get('str')).toBe('foo')\n  })\n  it('handles a URL base', () => {\n    const serialize = createSerializer(parsers)\n    const url = new URL('https://example.com/path')\n    const result = serialize(url, { str: 'foo' })\n    expect(result).toBe('https://example.com/path?str=foo')\n  })\n  it('handles a URL base and merges search params', () => {\n    const serialize = createSerializer(parsers)\n    const url = new URL('https://example.com/path?bar=egg')\n    const result = serialize(url, { str: 'foo' })\n    expect(result).toBe('https://example.com/path?bar=egg&str=foo')\n  })\n  it('deletes a null value from base', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize('?str=bar&int=-1', { str: 'foo', int: null })\n    expect(result).toBe('?str=foo')\n  })\n  it('deletes all from base with a global null', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize('?str=bar&int=-1', null)\n    expect(result).toBe('')\n  })\n  it('keeps search params in the base when given undefined as value', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize('?str=foo', { str: undefined })\n    expect(result).toBe('?str=foo')\n  })\n  it('keeps search params not managed by the serializer when fed null', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize('?str=foo&external=kept', null)\n    expect(result).toBe('?external=kept')\n  })\n  it('clears value when setting null for a search param that has a default value', () => {\n    const serialize = createSerializer({\n      int: parseAsInteger.withDefault(0)\n    })\n    const result = serialize('?int=1&str=foo', { int: null })\n    expect(result).toBe('?str=foo')\n  })\n  it('clears value when setting null for æ search param that is set to its default value', () => {\n    const serialize = createSerializer({\n      int: parseAsInteger.withDefault(0)\n    })\n    const result = serialize('?int=0&str=foo', { int: null })\n    expect(result).toBe('?str=foo')\n  })\n  it('clears value when setting null for an array parser with a default value', () => {\n    const serialize = createSerializer({\n      arr: parseAsArrayOf(parseAsString).withDefault([])\n    })\n    const result = serialize('?arr=a&arr=b&str=foo', { arr: null })\n    expect(result).toBe('?str=foo')\n  })\n  it('clears value when setting the default value (`clearOnDefault: true` is the default)', () => {\n    const serialize = createSerializer({\n      int: parseAsInteger.withDefault(0),\n      str: parseAsString.withDefault(''),\n      bool: parseAsBoolean.withDefault(false),\n      arr: parseAsArrayOf(parseAsString).withDefault([]),\n      json: parseAsJson(x => x).withDefault({ foo: 'bar' })\n    })\n    const result = serialize({\n      int: 0,\n      str: '',\n      bool: false,\n      arr: [],\n      json: { foo: 'bar' }\n    })\n    expect(result).toBe('')\n  })\n  it('keeps value when setting the default value when `clearOnDefault: false`', () => {\n    const options: Options = { clearOnDefault: false }\n    const serialize = createSerializer({\n      int: parseAsInteger.withOptions(options).withDefault(0),\n      str: parseAsString.withOptions(options).withDefault(''),\n      bool: parseAsBoolean.withOptions(options).withDefault(false),\n      arr: parseAsArrayOf(parseAsString).withOptions(options).withDefault([]),\n      json: parseAsJson(x => x)\n        .withOptions(options)\n        .withDefault({ foo: 'bar' })\n    })\n    const result = serialize({\n      int: 0,\n      str: '',\n      bool: false,\n      arr: [],\n      json: { foo: 'bar' }\n    })\n    expect(result).toBe(\n      '?int=0&str=&bool=false&arr=&json={%22foo%22:%22bar%22}'\n    )\n  })\n  it('supports a global clearOnDefault option', () => {\n    const serialize = createSerializer(\n      {\n        int: parseAsInteger.withDefault(0),\n        str: parseAsString.withDefault(''),\n        bool: parseAsBoolean.withDefault(false),\n        arr: parseAsArrayOf(parseAsString).withDefault([]),\n        json: parseAsJson(x => x).withDefault({ foo: 'bar' })\n      },\n      { clearOnDefault: false }\n    )\n    const result = serialize({\n      int: 0,\n      str: '',\n      bool: false,\n      arr: [],\n      json: { foo: 'bar' }\n    })\n    expect(result).toBe(\n      '?int=0&str=&bool=false&arr=&json={%22foo%22:%22bar%22}'\n    )\n  })\n  it('gives precedence to parser clearOnDefault over global clearOnDefault', () => {\n    const serialize = createSerializer(\n      {\n        int: parseAsInteger\n          .withDefault(0)\n          .withOptions({ clearOnDefault: true }),\n        str: parseAsString.withDefault('')\n      },\n      { clearOnDefault: false }\n    )\n    const result = serialize({\n      int: 0,\n      str: ''\n    })\n    expect(result).toBe('?str=')\n  })\n  it('supports urlKeys', () => {\n    const serialize = createSerializer(parsers, {\n      urlKeys: {\n        bool: 'b',\n        int: 'i',\n        str: 's'\n      }\n    })\n    const result = serialize({ str: 'foo', int: 1, bool: true })\n    expect(result).toBe('?s=foo&i=1&b=true')\n  })\n  it('supports ? in the values', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize({ str: 'foo?bar', int: 1, bool: true })\n    expect(result).toBe('?str=foo?bar&int=1&bool=true')\n  })\n  it('supports & in the base', () => {\n    // Repro for https://github.com/47ng/nuqs/issues/812\n    const serialize = createSerializer(parsers)\n    const result = serialize('https://example.com/path?issue=is?here', {\n      str: 'foo?bar'\n    })\n    expect(result).toBe('https://example.com/path?issue=is?here&str=foo?bar')\n  })\n  it('supports native array values', () => {\n    const serialize = createSerializer(parsers)\n    const result = serialize({ multi: ['a', 'b', 'c'] })\n    expect(result).toBe('?multi=a&multi=b&multi=c')\n  })\n  describe('supports processUrlSearchParams', () => {\n    it('modifies search params in place', () => {\n      const serialize = createSerializer(parsers, {\n        processUrlSearchParams: searchParams => {\n          searchParams.set('processed', 'true')\n          return searchParams\n        }\n      })\n      const result = serialize({ str: 'foo' })\n      expect(result).toBe('?str=foo&processed=true')\n    })\n    it('sorts the search params alphabetically', () => {\n      const serialize = createSerializer(\n        {\n          // Note the order of keys here:\n          z: parseAsInteger,\n          a: parseAsInteger\n        },\n        {\n          processUrlSearchParams: searchParams => {\n            searchParams.sort()\n            return searchParams\n          }\n        }\n      )\n      const result = serialize('?foo=bar', { a: 1, z: 1 })\n      expect(result).toBe('?a=1&foo=bar&z=1')\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/serializer.ts",
    "content": "import type { Nullable, Options, UrlKeys } from './defs'\nimport { renderQueryString } from './lib/url-encoding'\nimport type { inferParserType, ParserMap } from './parsers'\nimport { write } from './lib/search-params'\n\ntype Base = string | URLSearchParams | URL\n\nexport type CreateSerializerOptions<Parsers extends ParserMap> = Pick<\n  Options,\n  'clearOnDefault'\n> & {\n  urlKeys?: UrlKeys<Parsers>\n  processUrlSearchParams?: (searchParams: URLSearchParams) => URLSearchParams\n}\n\ntype SerializeFunction<\n  Parsers extends ParserMap,\n  BaseType extends Base = Base,\n  Return = string\n> = {\n  /**\n   * Generate a query string for the given values.\n   */\n  (values: Partial<Nullable<inferParserType<Parsers>>>): Return\n  /**\n   * Append/amend the query string of the given base with the given values.\n   *\n   * Existing search param values will kept, unless:\n   * - the value is null, in which case the search param will be deleted\n   * - another value is given for an existing key, in which case the\n   *  search param will be updated\n   */\n  (\n    base: BaseType,\n    values: Partial<Nullable<inferParserType<Parsers>>> | null\n  ): Return\n}\n\nexport function createSerializer<\n  Parsers extends ParserMap,\n  BaseType extends Base = Base,\n  Return = string\n>(\n  parsers: Parsers,\n  {\n    clearOnDefault = true,\n    urlKeys = {},\n    processUrlSearchParams\n  }: CreateSerializerOptions<Parsers> = {}\n): SerializeFunction<Parsers, BaseType, Return> {\n  type Values = Partial<Nullable<inferParserType<Parsers>>>\n\n  /**\n   * Generate a query string for the given values.\n   */\n  function serialize(values: Values): Return\n  /**\n   * Append/amend the query string of the given base with the given values.\n   *\n   * Existing search param values will kept, unless:\n   * - the value is null, in which case the search param will be deleted\n   * - another value is given for an existing key, in which case the\n   *  search param will be updated\n   */\n  function serialize(base: BaseType, values: Values | null): Return\n  function serialize(\n    arg1BaseOrValues: BaseType | Values,\n    arg2values: Values | null = {}\n  ) {\n    let [base, search] = isBase<BaseType>(arg1BaseOrValues)\n      ? splitBase(arg1BaseOrValues)\n      : ['', new URLSearchParams()]\n    const values = isBase(arg1BaseOrValues) ? arg2values : arg1BaseOrValues\n    if (values === null) {\n      for (const key in parsers) {\n        const urlKey = urlKeys[key] ?? key\n        search.delete(urlKey)\n      }\n      if (processUrlSearchParams) {\n        search = processUrlSearchParams(search)\n      }\n      return (base + renderQueryString(search)) as Return\n    }\n    for (const key in parsers) {\n      const parser = parsers[key]\n      const value = values[key]\n      if (!parser || value === undefined) {\n        continue\n      }\n      const urlKey = urlKeys[key] ?? key\n      const isMatchingDefault =\n        parser.defaultValue !== undefined &&\n        value !== null &&\n        (parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue)\n\n      if (\n        value === null ||\n        ((parser.clearOnDefault ?? clearOnDefault ?? true) && isMatchingDefault)\n      ) {\n        search.delete(urlKey)\n      } else {\n        const serialized = parser.serialize(value)\n        search = write(serialized, urlKey, search)\n      }\n    }\n    if (processUrlSearchParams) {\n      search = processUrlSearchParams(search)\n    }\n    return base + renderQueryString(search)\n  }\n  return serialize\n}\n\nfunction isBase<BaseType>(base: any): base is BaseType {\n  return (\n    typeof base === 'string' ||\n    base instanceof URLSearchParams ||\n    base instanceof URL\n  )\n}\n\nfunction splitBase<BaseType extends Base>(base: BaseType) {\n  if (typeof base === 'string') {\n    const [path = '', ...search] = base.split('?')\n    return [path, new URLSearchParams(search.join('?'))] as const\n  } else if (base instanceof URLSearchParams) {\n    return ['', new URLSearchParams(base)] as const // Operate on a copy of URLSearchParams, as derived classes may restrict its allowed methods\n  } else {\n    return [\n      base.origin + base.pathname,\n      new URLSearchParams(base.searchParams)\n    ] as const\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/standard-schema.test.ts",
    "content": "import { assertType, describe, expect, it } from 'vitest'\nimport {\n  createParser,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsString\n} from './parsers'\nimport { createStandardSchemaV1 } from './standard-schema'\n\ndescribe('standard schema', () => {\n  it('allows deriving the types from a schema', () => {\n    const schema = {\n      foo: parseAsString,\n      bar: parseAsInteger.withDefault(0),\n      egg: parseAsBoolean.withDefault(false)\n    }\n    const validator = createStandardSchemaV1(schema)\n    assertType<\n      | {\n          foo: string | null\n          bar: number\n          egg: boolean\n        }\n      | undefined\n    >(validator['~standard'].types?.output)\n  })\n  it('validates an object of already the right shape', async () => {\n    const schema = {\n      foo: parseAsString,\n      bar: parseAsInteger.withDefault(0),\n      egg: parseAsBoolean.withDefault(false)\n    }\n    const validator = createStandardSchemaV1(schema)\n    const input = {\n      foo: 'foo',\n      bar: 42,\n      egg: true\n    }\n    const resultMaybePromise = validator['~standard'].validate(input)\n    expect(resultMaybePromise).not.toBeInstanceOf(Promise)\n    // But we have to await it for TypeScript to understand\n    const result = await resultMaybePromise\n    expect(result.issues).toBeUndefined()\n    if (result.issues === undefined) {\n      expect(result.value).toEqual(input)\n    }\n  })\n  it('will validate a missing key without a default', async () => {\n    const schema = {\n      test: parseAsString\n    }\n    const validator = createStandardSchemaV1(schema)\n    const input = {}\n    const result = await validator['~standard'].validate(input)\n    expect(result.issues).toBeUndefined()\n    if (result.issues === undefined) {\n      expect(result.value).toEqual({\n        test: null\n      })\n    }\n  })\n  it('will validate a missing key with a default', async () => {\n    const schema = {\n      test: parseAsString.withDefault('default')\n    }\n    const validator = createStandardSchemaV1(schema)\n    const input = {}\n    const result = await validator['~standard'].validate(input)\n    expect(result.issues).toBeUndefined()\n    if (result.issues === undefined) {\n      expect(result.value).toEqual({\n        test: 'default'\n      })\n    }\n  })\n  it('will not validate a parser that returns null', async () => {\n    const schema = {\n      test: createParser({\n        parse: () => null,\n        serialize: String\n      })\n    }\n    const validator = createStandardSchemaV1(schema)\n    const input = {\n      test: 'should-not-parse'\n    }\n    const result = await validator['~standard'].validate(input)\n    expect(result.issues).toBeDefined()\n    expect(result.issues).toHaveLength(1)\n    expect(result.issues![0]!.message).toEqual(\n      '[nuqs] Failed to parse query `should-not-parse` for key `test` (got null)'\n    )\n  })\n  it('will not validate a parser that throws', async () => {\n    const schema = {\n      test: createParser<string>({\n        parse: () => {\n          throw new Error('Boom')\n        },\n        serialize: String\n      })\n    }\n    const validator = createStandardSchemaV1(schema)\n    const input = {\n      test: 'should-not-parse'\n    }\n    const result = await validator['~standard'].validate(input)\n    expect(result.issues).toBeDefined()\n    expect(result.issues).toHaveLength(1)\n    expect(result.issues![0]!.message).toEqual(\n      '[nuqs] Error while parsing query `should-not-parse` for key `test`: Error: Boom'\n    )\n  })\n  it('allows for partial outputs', async () => {\n    const schema = {\n      foo: parseAsString,\n      bar: parseAsInteger.withDefault(0),\n      egg: parseAsBoolean.withDefault(false)\n    }\n    const validator = createStandardSchemaV1(schema, { partialOutput: true })\n    const input = {}\n    const resultMaybePromise = validator['~standard'].validate(input)\n    expect(resultMaybePromise).not.toBeInstanceOf(Promise)\n    // But we have to await it for TypeScript to understand\n    const result = await resultMaybePromise\n    expect(result.issues).toBeUndefined()\n    if (result.issues === undefined) {\n      expect(result.value).toEqual({})\n    }\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/standard-schema.ts",
    "content": "import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { createLoader, type CreateLoaderOptions } from './loader'\nimport type { ParserMap, inferParserType } from './parsers'\nimport { createSerializer } from './serializer'\n\nexport type CreateStandardSchemaV1Options<\n  Parsers extends ParserMap,\n  PartialOutput extends boolean = false\n> = CreateLoaderOptions<Parsers> & {\n  /**\n   * Marks the output type as Partial, and removes any keys\n   * from the output that are not present in the input.\n   *\n   * This is useful for TanStack Router, to avoid reflecting default values\n   * (or null) in the URL, and to make search params optional in Links,\n   * as default values are handled by nuqs.\n   *\n   * @default false\n   */\n  partialOutput?: PartialOutput\n}\n\ntype MaybePartial<Condition, Type> = Condition extends true\n  ? Partial<Type>\n  : Type\n\nexport function createStandardSchemaV1<\n  Parsers extends ParserMap,\n  PartialOutput extends boolean = false\n>(\n  parsers: Parsers,\n  {\n    urlKeys,\n    partialOutput = false as PartialOutput\n  }: CreateStandardSchemaV1Options<Parsers, PartialOutput> = {}\n): StandardSchemaV1<MaybePartial<PartialOutput, inferParserType<Parsers>>> {\n  const serialize = createSerializer(parsers, { urlKeys })\n  const load = createLoader(parsers, { urlKeys })\n  return {\n    '~standard': {\n      version: 1,\n      vendor: 'nuqs',\n      validate(input) {\n        try {\n          const url = serialize(input as any)\n          const value = load(url, { strict: true })\n          if (partialOutput) {\n            for (const key in value) {\n              if (!(key in (input as any))) {\n                delete value[key]\n              }\n            }\n          }\n          return { value }\n        } catch (error) {\n          return {\n            issues: [\n              {\n                message: error instanceof Error ? error.message : String(error)\n              }\n            ]\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/src/testing.ts",
    "content": "import { compareQuery } from './lib/compare'\nimport type {\n  GenericParserBuilder,\n  MultiParserBuilder,\n  SingleParserBuilder\n} from './parsers'\n\nexport function isParserBijective<T>(\n  parser: SingleParserBuilder<T>,\n  serialized: string,\n  input: T\n): boolean\nexport function isParserBijective<T>(\n  parser: MultiParserBuilder<T>,\n  serialized: Array<string>,\n  input: T\n): boolean\n\n/**\n * Test that a parser is bijective (serialize then parse gives back the same value).\n *\n * It will throw if the parser does not serialize the input to the expected serialized value,\n * or if the parser does not parse the serialized value to the expected input value.\n * The parser's `eq` function (if provided, otherwise `===`) is used to compare the values.\n *\n * Usage:\n * ```ts\n * // Expect it to pass (no error thrown)\n * expect(isParserBijective(parseAsInteger, '42', 42)).toBe(true)\n * // Expect it to fail\n * expect(() => isParserBijective(parseAsInteger, '42', 47)).toThrow()\n * ```\n *\n * @param parser The parser to test\n * @param serialized The serialized representation of the input to test against\n * @param input An input value to test against\n * @returns `true` if the test passes, otherwise it will throw.\n */\nexport function isParserBijective<T>(\n  parser: GenericParserBuilder<T>,\n  serialized: string | Array<string>,\n  input: T\n): boolean {\n  if (parser.type === 'multi' && Array.isArray(serialized)) {\n    // Test either sides of the bijectivitiy\n    testSerializeThenParse(parser, input)\n    testParseThenSerialize(parser, serialized)\n  } else if (parser.type !== 'multi' && typeof serialized === 'string') {\n    // Test either sides of the bijectivitiy\n    testSerializeThenParse(parser, input)\n    testParseThenSerialize(parser, serialized)\n  } else {\n    // Shouldn't happen with correct overload types, but better be safe and fail the test.\n    throw new Error(\n      `[nuqs] isParserBijective: mismatched parser type and serialized value type`\n    )\n  }\n  // Test value equality\n  if (!compareQuery(parser.serialize(input), serialized)) {\n    throw new Error(\n      `[nuqs] parser.serialize does not match expected serialized value\n  Expected: '${serialized}'\n  Received: '${parser.serialize(input)}'\n  `\n    )\n  }\n  // @ts-expect-error - might return null\n  const parsed: T = parser.parse(serialized)\n  if (!parser.eq(parsed, input)) {\n    throw new Error(\n      `[nuqs] parser.parse does not match expected input value\n  Expected: ${input}\n  Received: ${parsed}\n  `\n    )\n  }\n  return true\n}\n\nexport function testSerializeThenParse<T>(\n  parser: SingleParserBuilder<T>,\n  input: T\n): boolean\nexport function testSerializeThenParse<T>(\n  parser: MultiParserBuilder<T>,\n  input: T\n): boolean\n\n/**\n * Test that a parser is bijective (serialize then parse gives back the same value).\n *\n * It will throw if the parser is not bijective (if the parsed value is not equal to the input value).\n * The parser's `eq` function is used to compare the values.\n *\n * Usage:\n * ```ts\n * // Expect it to pass (no error thrown)\n * expect(testSerializeThenParse(myParser, 'foo')).toBe(true)\n * // Expect it to fail\n * expect(() => testSerializeThenParse(myParser, 'bar')).toThrow()\n * ```\n *\n * @param parser The parser to test\n * @param input An input value to test against\n * @returns `true` if the test passes, otherwise it will throw.\n */\nexport function testSerializeThenParse<T>(\n  parser: GenericParserBuilder<T>,\n  input: T\n): boolean {\n  const serialized = parser.serialize(input)\n  const parsed =\n    parser.type == 'multi' && Array.isArray(serialized)\n      ? parser.parse(serialized)\n      : parser.type !== 'multi' && typeof serialized === 'string'\n        ? parser.parse(serialized)\n        : null\n  if (parsed === null) {\n    throw new Error(\n      `[nuqs] testSerializeThenParse: parsed value is null (when parsing ${serialized} serialized from ${input})`\n    )\n  }\n  if (!parser.eq(input, parsed)) {\n    throw new Error(\n      `[nuqs] parser is not bijective (in testSerializeThenParse)\n  Expected value:         ${typeof input === 'object' ? JSON.stringify(input) : input}\n  Received parsed value:  ${typeof parsed === 'object' ? JSON.stringify(parsed) : parsed}\n  Serialized as: '${serialized}'\n  `\n    )\n  }\n  return true\n}\n\nexport function testParseThenSerialize<T>(\n  parser: SingleParserBuilder<T>,\n  input: string\n): boolean\nexport function testParseThenSerialize<T>(\n  parser: MultiParserBuilder<T>,\n  input: Array<string>\n): boolean\n\n/**\n * Tests that a parser is bijective (parse then serialize gives back the same query string).\n *\n * It will throw if the parser is not bijective (if the serialized value is not equal to the input query).\n *\n * Usage:\n * ```ts\n * // Expect it to pass (no error thrown)\n * expect(testParseThenSerialize(myParser, 'foo')).toBe(true)\n * // Expect it to fail\n * expect(() => testParseThenSerialize(myParser, 'bar')).toThrow()\n * ```\n *\n * @param parser The parser to test\n * @param input A query string to test against\n * @returns `true` if the test passes, otherwise it will throw.\n */\nexport function testParseThenSerialize<T>(\n  parser: GenericParserBuilder<T>,\n  input: string | Array<string>\n): boolean {\n  const parsed =\n    parser.type === 'multi' && Array.isArray(input)\n      ? parser.parse(input)\n      : parser.type !== 'multi' && typeof input === 'string'\n        ? parser.parse(input)\n        : null\n  if (parsed === null) {\n    throw new Error(\n      `[nuqs] testParseThenSerialize: parsed value is null (when parsing ${input})`\n    )\n  }\n  const serialized = parser.serialize(parsed)\n  if (!compareQuery(serialized, input)) {\n    throw new Error(\n      `[nuqs] parser is not bijective (in testParseThenSerialize)\n  Expected query: '${input}'\n  Received query: '${serialized}'\n  Parsed value: ${parsed}\n`\n    )\n  }\n  return true\n}\n"
  },
  {
    "path": "packages/nuqs/src/useQueryState.browser.test.tsx",
    "content": "import React, { useState } from 'react'\nimport { describe, expect, it, vi } from 'vitest'\nimport { render, renderHook } from 'vitest-browser-react'\nimport { page, userEvent } from 'vitest/browser'\nimport {\n  NullDetector,\n  useFakeLoadingState\n} from '../tests/components/repro-1099'\nimport {\n  withNuqsTestingAdapter,\n  type OnUrlUpdateFunction\n} from './adapters/testing'\nimport { debounce, throttle } from './lib/queues/rate-limiting'\nimport {\n  parseAsArrayOf,\n  parseAsInteger,\n  parseAsJson,\n  parseAsNativeArrayOf,\n  parseAsString\n} from './parsers'\nimport { useQueryState } from './useQueryState'\n\nconst waitForNextTick = () =>\n  new Promise<void>(resolve => {\n    setTimeout(resolve, 0)\n  })\n\ndescribe('useQueryState: referential equality', () => {\n  const defaults = {\n    str: 'foo',\n    obj: { initial: 'state' },\n    arr: [\n      {\n        initial: 'state'\n      }\n    ]\n  }\n\n  const useTestHookWithDefaults = (\n    { defaultValue } = { defaultValue: defaults.str }\n  ) => {\n    const str = useQueryState('str', parseAsString.withDefault(defaultValue))\n    const obj = useQueryState(\n      'obj',\n      parseAsJson<any>(x => x).withDefault(defaults.obj)\n    )\n    const arr = useQueryState(\n      'arr',\n      parseAsArrayOf(parseAsJson<any>(x => x)).withDefault(defaults.arr)\n    )\n    return { str, obj, arr }\n  }\n\n  it('should have referential equality on default values', async () => {\n    const { result } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter()\n    })\n    const { str, obj, arr } = result.current\n    expect(str[0]).toBe(defaults.str)\n    expect(obj[0]).toBe(defaults.obj)\n    expect(arr[0]).toBe(defaults.arr)\n    expect(arr[0][0]).toBe(defaults.arr[0])\n  })\n\n  it('should keep referential equality when resetting to defaults', async () => {\n    const { result, act } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: {\n          str: 'foo',\n          obj: '{\"hello\":\"world\"}',\n          arr: '{\"obj\":true},{\"arr\":true}'\n        }\n      })\n    })\n    await act(() => {\n      const { str, arr, obj } = result.current\n      str[1](null)\n      obj[1](null)\n      return arr[1](null)\n    })\n    const { str, arr, obj } = result.current\n    expect(str[0]).toBe(defaults.str)\n    expect(obj[0]).toBe(defaults.obj)\n    expect(arr[0]).toBe(defaults.arr)\n    expect(arr[0][0]).toBe(defaults.arr[0])\n  })\n\n  it('should keep referential equality when unrelated keys change', async () => {\n    const { result, act } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: {\n          str: 'foo',\n          obj: '{\"hello\":\"world\"}'\n          // Keep arr as default\n        }\n      })\n    })\n    const initialObj = result.current.obj[0]\n    const initialArr = result.current.arr[0]\n    await act(() => {\n      const { str } = result.current\n      return str[1]('bar')\n    })\n    const { str, obj, arr } = result.current\n    expect(str[0]).toBe('bar')\n    expect(obj[0]).toBe(initialObj)\n    expect(arr[0]).toBe(initialArr)\n  })\n\n  it('should keep referential equality when default changes for another key', async () => {\n    const { result, rerender } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter()\n    })\n    expect(result.current.str[0]).toBe('foo')\n    rerender({ defaultValue: 'b' })\n    const { str, obj, arr } = result.current\n    expect(str[0]).toBe('b')\n    expect(obj[0]).toBe(defaults.obj)\n    expect(arr[0]).toBe(defaults.arr)\n    expect(arr[0][0]).toBe(defaults.arr[0])\n  })\n\n  it('should have referential equality on the state updater function', async () => {\n    const { result, rerender, act } = await renderHook(\n      () => useQueryState('test'),\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    const [, setState1] = result.current\n    rerender()\n    const [, setState2] = result.current\n    expect(setState1).toBe(setState2)\n    await act(() => setState1('pass'))\n    const [, setState3] = result.current\n    expect(setState1).toBe(setState3)\n  })\n})\n\ndescribe('useQueryState: clearOnDefault', () => {\n  it('honors clearOnDefault: true by default', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => useQueryState('test', parseAsString.withDefault('default')),\n      {\n        wrapper: withNuqsTestingAdapter({\n          searchParams: '?test=init',\n          onUrlUpdate\n        })\n      }\n    )\n    await act(() => result.current[1]('default'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('')\n  })\n\n  it('supports clearOnDefault: false (hook level)', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryState(\n        'a',\n        parseAsString.withDefault('default').withOptions({\n          clearOnDefault: false\n        })\n      )\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]('default'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default')\n  })\n\n  it('supports clearOnDefault: false (call level)', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryState(\n        'a',\n        parseAsString.withDefault('default').withOptions({\n          clearOnDefault: true\n        })\n      )\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]('default', { clearOnDefault: false }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default')\n  })\n})\n\ndescribe('useQueryState: update sequencing', () => {\n  it('should combine updates for a single key made in the same event loop tick', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate\n      })\n    })\n    await act(() => {\n      result.current[1]('a')\n      return result.current[1]('b')\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=b')\n  })\n  it('should combine updtes for multiple keys made in the same event loop tick', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => ({\n        a: useQueryState('a', parseAsString),\n        b: useQueryState('b', parseAsString)\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate\n        })\n      }\n    )\n    await act(() => {\n      result.current.a[1]('a')\n      return result.current.b[1]('b')\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=a&b=b')\n  })\n  it('should return a stable Promise when pushing multiple updates in the same tick', async () => {\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter()\n    })\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(() => {\n      p1 = result.current[1]('a')\n      p2 = result.current[1]('b')\n      return p2\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).toBe(p2)\n    await expect(p1).resolves.toEqual(new URLSearchParams('?test=b'))\n  })\n  it('should return a stable Promise when pushing multiple updates in the same tick (multiple keys)', async () => {\n    const { result, act } = await renderHook(\n      () => ({\n        a: useQueryState('a', parseAsString),\n        b: useQueryState('b', parseAsString)\n      }),\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(() => {\n      p1 = result.current.a[1]('a')\n      p2 = result.current.b[1]('b')\n      return p2\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).toBe(p2)\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n  it('should return a stable Promise when pushing updates before the throttle period times out', async () => {\n    const { result, act } = await renderHook(\n      () => ({\n        a: useQueryState('a', parseAsString),\n        b: useQueryState('b', parseAsString)\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p0: Promise<URLSearchParams> | undefined = undefined\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    // prettier-ignore\n    await act(async () => {\n      // Flush the queue from previous tests\n      await new Promise(r => setTimeout(r, 60))\n      // First, push an update to a to be emitted \"immediately\"\n      p0 = result.current.a[1]('init')\n      // Then two updates before the end of the throttle timeout\n      setTimeout(() => { p1 = result.current.a[1]('a') }, 10)\n      setTimeout(() => { p2 = result.current.b[1]('b') }, 20)\n      return new Promise(resolve => setTimeout(resolve, 30))\n    })\n\n    expect(p0).toBeInstanceOf(Promise)\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p0).not.toBe(p1)\n    expect(p1).toBe(p2)\n    await expect(p0).resolves.toEqual(new URLSearchParams('?a=init'))\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n  it('should return a new Promise when using debounce', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => ({\n        a: useQueryState('a', { limitUrlUpdates: debounce(100) }),\n        b: useQueryState('b')\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate,\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p1 = result.current.a[1]('a')\n      p2 = result.current.b[1]('b')\n      return p1 // p1 will resolve last, so await it before moving on\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).not.toBe(p2)\n    // Note: our mock adapter does not save search params, so there is no merge\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a'))\n    await expect(p2).resolves.toEqual(new URLSearchParams('?b=b'))\n    expect(onUrlUpdate).toHaveBeenCalledTimes(2)\n    // b updates first, then a\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?b=b')\n    expect(onUrlUpdate.mock.calls[1]![0].queryString).toEqual('?a=a')\n  })\n  it('aborts a debounced update when pushing a throttled one', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate,\n        rateLimitFactor: 1\n      })\n    })\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p1 = result.current[1]('a', { limitUrlUpdates: debounce(100) })\n      p2 = result.current[1]('b')\n      return Promise.allSettled([p1, p2])\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).not.toBe(p2)\n    await expect(p1).resolves.toEqual(new URLSearchParams('?test=b'))\n    await expect(p2).resolves.toEqual(new URLSearchParams('?test=b'))\n    expect(onUrlUpdate).toHaveBeenCalledTimes(1)\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=b')\n  })\n\n  it('does flush when pushing throttled updates', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate,\n        autoResetQueueOnUpdate: false\n      })\n    })\n    let p: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p = result.current[1]('pass', { limitUrlUpdates: throttle(100) })\n      await waitForNextTick()\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=pass')\n    await expect(p).resolves.toEqual(new URLSearchParams('?test=pass'))\n  })\n\n  it('does not flush when pushing debounced updates', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate,\n        autoResetQueueOnUpdate: false\n      })\n    })\n    // Flush a first time without resetting the queue to keep pending items\n    // in the global throttle queue.\n    await act(() => result.current[1]('init'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=init')\n    onUrlUpdate.mockClear()\n    // Now push a debounced update, which should not flush immediately\n    let p: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p = result.current[1]('pass', { limitUrlUpdates: debounce(100) })\n      await waitForNextTick()\n    })\n    expect(onUrlUpdate).not.toHaveBeenCalled()\n    await expect(p).resolves.toEqual(new URLSearchParams('?test=pass'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=pass')\n  })\n})\n\ndescribe('useQueryState: adapter defaults', () => {\n  it('should use adapter default value for `shallow` when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter({\n        defaultOptions: {\n          shallow: false\n        },\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]('update'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].options.shallow).toBe(false)\n  })\n  it('should use adapter default value for `scroll` when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(() => useQueryState('test'), {\n      wrapper: withNuqsTestingAdapter({\n        defaultOptions: {\n          scroll: true\n        },\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]('update'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].options.scroll).toBe(true)\n  })\n  it('should use adapter default value for `clearOnDefault` when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => useQueryState('test', { defaultValue: 'pass' }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          defaultOptions: {\n            clearOnDefault: false\n          },\n          onUrlUpdate\n        })\n      }\n    )\n    await act(() => result.current[1]('pass'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toBe('?test=pass')\n  })\n})\n\ndescribe('useQueryState: edge cases & repros', () => {\n  it('should not go through transient old state when combined with another state update (#1099)', async () => {\n    function TestComponent() {\n      const [state, setState] = useQueryState('test')\n      const [isNullDetectorEnabled, setIsNullDetectorEnabled] = useState(false)\n      const isLoading = useFakeLoadingState(state)\n      return (\n        <>\n          <button\n            onClick={() => {\n              setIsNullDetectorEnabled(true)\n              setState('pass')\n            }}\n          >\n            Start\n          </button>\n          <NullDetector\n            state={state}\n            enabled={isNullDetectorEnabled}\n            data-testid=\"null-detector\"\n          />\n          <p>isLoading: {String(isLoading)}</p>\n        </>\n      )\n    }\n    const user = userEvent.setup()\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    render(<TestComponent />, {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate,\n        hasMemory: true // needs memory for the test to pass\n      })\n    })\n    await expect\n      .element(page.getByTestId('null-detector'))\n      .toHaveTextContent('pass')\n    await expect.element(page.getByText('isLoading: false')).toBeInTheDocument()\n    await user.click(page.getByRole('button', { name: 'Start' }))\n    await expect.element(page.getByText('isLoading: true')).toBeInTheDocument()\n    await expect.element(page.getByText('isLoading: false')).toBeInTheDocument()\n    await expect\n      .element(page.getByTestId('null-detector'))\n      .toHaveTextContent('pass')\n  })\n})\n\ndescribe('useQueryState: multi-parsers', () => {\n  it('should clear the url when defaults are set', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () =>\n        useQueryState(\n          'test',\n          parseAsNativeArrayOf(parseAsInteger).withDefault([42])\n        ),\n      {\n        wrapper: withNuqsTestingAdapter({\n          searchParams: '?test=1&test=2&test=3',\n          onUrlUpdate\n        })\n      }\n    )\n    expect(result.current[0]).toEqual([1, 2, 3])\n    await act(() => result.current[1]([42]))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('')\n  })\n\n  it('should add an empty param when set to empty array and there is a different default', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () =>\n        useQueryState(\n          'test',\n          parseAsNativeArrayOf(parseAsInteger).withDefault([42])\n        ),\n      {\n        wrapper: withNuqsTestingAdapter({\n          searchParams: '?test=1&test=2&test=3',\n          onUrlUpdate\n        })\n      }\n    )\n    expect(result.current[0]).toEqual([1, 2, 3])\n    await act(() => result.current[1]([]))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=')\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/useQueryState.ts",
    "content": "import { useCallback } from 'react'\nimport type { Options } from './defs'\nimport type { GenericParser } from './parsers'\nimport { useQueryStates } from './useQueryStates'\n\nexport type UseQueryStateOptions<T> = GenericParser<T> & Options\n\nexport type UseQueryStateReturn<Parsed, Default> = [\n  Default extends undefined\n    ? Parsed | null // value can't be null if default is specified\n    : Parsed,\n  (\n    value:\n      | null\n      | Parsed\n      | ((\n          old: Default extends Parsed ? Parsed : Parsed | null\n        ) => Parsed | null),\n    options?: Options\n  ) => Promise<URLSearchParams>\n]\n\n// Overload type signatures ----------------------------------------------------\n// Note: the order of declaration matters (from the most specific to the least).\n\n/**\n * React state hook synchronized with a URL query string in Next.js\n *\n * This variant is used when providing a default value. This will make\n * the returned state non-nullable when the query is not present in the URL.\n * (the default value will be returned instead).\n *\n * _Note: the URL will **not** be updated with the default value if the query\n * is missing._\n *\n * Setting the value to `null` will clear the query in the URL, and return\n * the default value as state.\n *\n * Example usage:\n * ```ts\n *   const [count, setCount] = useQueryState(\n *     'count',\n *     parseAsInteger.defaultValue(0)\n *   )\n *\n *   const increment = () => setCount(oldCount => oldCount + 1)\n *   const decrement = () => setCount(oldCount => oldCount - 1)\n *   // Clears the query key from the URL and `count` equals 0\n *   const clearCountQuery = () => setCount(null)\n * ```\n * @param key The URL query string key to bind to\n * @param options - Parser (defines the state data type), default value and optional history mode.\n */\nexport function useQueryState<T>(\n  key: string,\n  options: UseQueryStateOptions<T> & { defaultValue: T }\n): UseQueryStateReturn<\n  NonNullable<ReturnType<typeof options.parse>>,\n  typeof options.defaultValue\n>\n\n/**\n * React state hook synchronized with a URL query string in Next.js\n *\n * If the query is missing in the URL, the state will be `null`.\n *\n * Example usage:\n * ```ts\n *   // Blog posts filtering by tag\n *   const [tag, selectTag] = useQueryState('tag')\n *   const filteredPosts = posts.filter(post => tag ? post.tag === tag : true)\n *   const clearTag = () => selectTag(null)\n * ```\n * @param key The URL query string key to bind to\n * @param options - Parser (defines the state data type), and optional history mode.\n */\nexport function useQueryState<T>(\n  key: string,\n  options: UseQueryStateOptions<T>\n): UseQueryStateReturn<NonNullable<ReturnType<typeof options.parse>>, undefined>\n\n/**\n * Default type string, limited options & default value\n */\nexport function useQueryState(\n  key: string,\n  options: Options & {\n    defaultValue: string\n  } & {\n    // Note: Ensure this overload isn't picked when specifying a default\n    // value and spreading a parser for which the default would be invalid.\n    // See https://github.com/47ng/nuqs/pull/1057\n    [K in keyof GenericParser<unknown>]?: never\n  }\n): UseQueryStateReturn<string, typeof options.defaultValue>\n\n/**\n * React state hook synchronized with a URL query string in Next.js\n *\n * If the query is missing in the URL, the state will be `null`.\n *\n * Note: by default the state type is a `string`. To use different types,\n * check out the `parseAsXYZ` helpers:\n * ```ts\n *   const [date, setDate] = useQueryState(\n *     'date',\n *     parseAsIsoDateTime.withDefault(new Date('2021-01-01'))\n *   )\n *\n *   const setToNow = () => setDate(new Date())\n *   const addOneHour = () => {\n *     setDate(oldDate => new Date(oldDate.valueOf() + 3600_000))\n *   }\n * ```\n * @param key The URL query string key to bind to\n * @param options - Parser (defines the state data type), and optional history mode.\n */\nexport function useQueryState(\n  key: string,\n  options: Pick<UseQueryStateOptions<string>, keyof Options>\n): UseQueryStateReturn<string, undefined>\n\n/**\n * React state hook synchronized with a URL query string in Next.js\n *\n * If the query is missing in the URL, the state will be `null`.\n *\n * Note: by default the state type is a `string`. To use different types,\n * check out the `parseAsXYZ` helpers:\n * ```ts\n *   const [date, setDate] = useQueryState(\n *     'date',\n *     parseAsIsoDateTime.withDefault(new Date('2021-01-01'))\n *   )\n *\n *   const setToNow = () => setDate(new Date())\n *   const addOneHour = () => {\n *     setDate(oldDate => new Date(oldDate.valueOf() + 3600_000))\n *   }\n * ```\n * @param key The URL query string key to bind to\n */\nexport function useQueryState(\n  key: string\n): UseQueryStateReturn<string, undefined>\n\n/**\n * React state hook synchronized with a URL query string in Next.js\n *\n * If used without a `defaultValue` supplied in the options, and the query is\n * missing in the URL, the state will be `null`.\n *\n * ### Behaviour with default values:\n *\n * _Note: the URL will **not** be updated with the default value if the query\n * is missing._\n *\n * Setting the value to `null` will clear the query in the URL, and return\n * the default value as state.\n *\n * Example usage:\n * ```ts\n *   // Blog posts filtering by tag\n *   const [tag, selectTag] = useQueryState('tag')\n *   const filteredPosts = posts.filter(post => tag ? post.tag === tag : true)\n *   const clearTag = () => selectTag(null)\n *\n *   // With default values\n *\n *   const [count, setCount] = useQueryState(\n *     'count',\n *     parseAsInteger.defaultValue(0)\n *   )\n *\n *   const increment = () => setCount(oldCount => oldCount + 1)\n *   const decrement = () => setCount(oldCount => oldCount - 1)\n *   const clearCountQuery = () => setCount(null)\n *\n *   // --\n *\n *   const [date, setDate] = useQueryState(\n *     'date',\n *     parseAsIsoDateTime.withDefault(new Date('2021-01-01'))\n *   )\n *\n *   const setToNow = () => setDate(new Date())\n *   const addOneHour = () => {\n *     setDate(oldDate => new Date(oldDate.valueOf() + 3600_000))\n *   }\n * ```\n * @param key The URL query string key to bind to\n * @param options - Parser (defines the state data type), optional default value and history mode.\n */\nexport function useQueryState<T = string>(\n  key: string,\n  options: Partial<UseQueryStateOptions<T>> & {\n    defaultValue?: T\n  } = {}\n) {\n  const { parse, type, serialize, eq, defaultValue, ...hookOptions } = options\n  const [{ [key]: state }, setState] = useQueryStates(\n    {\n      [key]: {\n        parse: parse ?? ((x: any) => x as unknown as T),\n        type,\n        serialize,\n        eq,\n        defaultValue\n      } as GenericParser<T>\n    },\n    hookOptions\n  )\n  const update = useCallback(\n    (stateUpdater: React.SetStateAction<T | null>, callOptions: Options = {}) =>\n      setState(\n        old => ({\n          [key]:\n            typeof stateUpdater === 'function'\n              ? // @ts-expect-error somehow stateUpdater is not narrowed correctly\n                // and useQueryStates' key type is not inferred\n                stateUpdater(old[key])\n              : stateUpdater\n        }),\n        callOptions\n      ),\n    [key, setState]\n  )\n  return [state, update]\n}\n"
  },
  {
    "path": "packages/nuqs/src/useQueryStates.browser.test.tsx",
    "content": "import React, {\n  createElement,\n  useEffect,\n  useState,\n  type ReactNode\n} from 'react'\nimport { describe, expect, it, vi } from 'vitest'\nimport { render, renderHook } from 'vitest-browser-react'\nimport { page, userEvent } from 'vitest/browser'\nimport {\n  NullDetector,\n  useFakeLoadingState\n} from '../tests/components/repro-1099'\nimport {\n  NuqsTestingAdapter,\n  withNuqsTestingAdapter,\n  type OnUrlUpdateFunction\n} from './adapters/testing'\nimport { debounce, throttle } from './lib/queues/rate-limiting'\nimport {\n  parseAsArrayOf,\n  parseAsInteger,\n  parseAsJson,\n  parseAsNativeArrayOf,\n  parseAsString\n} from './parsers'\nimport { useQueryState } from './useQueryState'\nimport { useQueryStates } from './useQueryStates'\n\nconst waitForNextTick = () =>\n  new Promise<void>(resolve => {\n    setTimeout(resolve, 0)\n  })\n\ndescribe('useQueryStates', () => {\n  it('allows setting a single value', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString,\n        b: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate\n      })\n    })\n    expect(result.current[0].a).toBeNull()\n    expect(result.current[0].b).toBeNull()\n    await act(() => result.current[1]({ a: 'pass' }))\n    expect(result.current[0].a).toEqual('pass')\n    expect(result.current[0].b).toBeNull()\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=pass')\n  })\n\n  it('allows clearing a single key by setting it to null', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString,\n        b: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    expect(result.current[0].a).toEqual('init')\n    expect(result.current[0].b).toEqual('init')\n    await act(() => result.current[1]({ a: null }))\n    expect(result.current[0].a).toBeNull()\n    expect(result.current[0].b).toEqual('init')\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?b=init')\n  })\n  it('allows clearing managed keys by passing null', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString,\n        b: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1](null))\n    expect(result.current[0].a).toBeNull()\n    expect(result.current[0].b).toBeNull()\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('')\n  })\n  it('allows clearing managed keys by passing a function that returns null', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString,\n        b: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1](() => null))\n    expect(result.current[0].a).toBeNull()\n    expect(result.current[0].b).toBeNull()\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('')\n  })\n  it('accepts undefined for keys to leave them unchanged', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString,\n        b: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    expect(result.current[0].a).toEqual('init')\n    expect(result.current[0].b).toEqual('init')\n    await act(() => result.current[1]({ a: undefined, b: 'changed' }))\n    expect(result.current[0].a).toEqual('init')\n    expect(result.current[0].b).toEqual('changed')\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual(\n      '?a=init&b=changed'\n    )\n  })\n  it('accepts undefined for keys to leave them unchanged (updater function version)', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString,\n        b: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    expect(result.current[0].a).toEqual('init')\n    expect(result.current[0].b).toEqual('init')\n    await act(() => result.current[1](() => ({ a: undefined, b: 'changed' })))\n    expect(result.current[0].a).toEqual('init')\n    expect(result.current[0].b).toEqual('changed')\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual(\n      '?a=init&b=changed'\n    )\n  })\n})\n\ndescribe('useQueryStates: referential equality', () => {\n  const defaults = {\n    str: 'foo',\n    obj: { initial: 'state' },\n    arr: [\n      {\n        initial: 'state'\n      }\n    ],\n    multi: [\n      {\n        initial: 'state'\n      }\n    ]\n  }\n\n  const useTestHookWithDefaults = (\n    { defaultValue } = { defaultValue: defaults.str }\n  ) => {\n    return useQueryStates({\n      str: parseAsString.withDefault(defaultValue),\n      obj: parseAsJson<any>(x => x).withDefault(defaults.obj),\n      arr: parseAsArrayOf(parseAsJson<any>(x => x)).withDefault(defaults.arr),\n      multi: parseAsNativeArrayOf(parseAsJson<any>(x => x)).withDefault(\n        defaults.multi\n      )\n    })\n  }\n\n  it('should have referential equality on default values', async () => {\n    const { result } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter()\n    })\n    const [state] = result.current\n    expect(state.str).toBe(defaults.str)\n    expect(state.obj).toBe(defaults.obj)\n    expect(state.arr).toBe(defaults.arr)\n    expect(state.arr[0]).toBe(defaults.arr[0])\n    expect(state.multi[0]).toBe(defaults.multi[0])\n  })\n\n  it('should keep referential equality when resetting to defaults', async () => {\n    const { result, act } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: {\n          str: 'foo',\n          obj: '{\"hello\":\"world\"}',\n          arr: '{\"obj\":true},{\"arr\":true}',\n          multi: '{\"obj\":true},{\"arr\":true}'\n        }\n      })\n    })\n    await act(() => result.current[1](null))\n    const [state] = result.current\n    expect(state.str).toBe(defaults.str)\n    expect(state.obj).toBe(defaults.obj)\n    expect(state.arr).toBe(defaults.arr)\n    expect(state.arr[0]).toBe(defaults.arr[0])\n    expect(state.multi).toBe(defaults.multi)\n    expect(state.multi[0]).toBe(defaults.multi[0])\n  })\n\n  it('should keep referential equality when unrelated keys change', async () => {\n    const { result, act } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: {\n          str: 'foo',\n          obj: '{\"hello\":\"world\"}'\n          // Keep arr as default\n        }\n      })\n    })\n    const [{ obj: initialObj, arr: initialArr }] = result.current\n    await act(() => result.current[1]({ str: 'bar' }))\n    const [{ str, obj, arr }] = result.current\n    expect(str).toBe('bar')\n    expect(obj).toBe(initialObj)\n    expect(arr).toBe(initialArr)\n  })\n\n  it('should keep referential equality when default changes for another key', async () => {\n    const { result, rerender } = await renderHook(useTestHookWithDefaults, {\n      wrapper: withNuqsTestingAdapter()\n    })\n    expect(result.current[0].str).toBe('foo')\n    rerender({ defaultValue: 'b' })\n    const [state] = result.current\n    expect(state.str).toBe('b')\n    expect(state.obj).toBe(defaults.obj)\n    expect(state.arr).toBe(defaults.arr)\n    expect(state.arr[0]).toBe(defaults.arr[0])\n    expect(state.multi).toBe(defaults.multi)\n    expect(state.multi[0]).toBe(defaults.multi[0])\n  })\n})\n\ndescribe('useQueryStates: rendering & bail-out', () => {\n  it('should bail out of rendering the same component when setting to the same value', async () => {\n    let renderCount = 0\n    function TestComponent() {\n      const [{ test }, setSearchParams] = useQueryStates({\n        test: parseAsString\n      })\n      useEffect(() => {\n        renderCount++\n      })\n      return (\n        <>\n          <button\n            onClick={() => {\n              setSearchParams(v => v)\n            }}\n          >\n            Start\n          </button>\n          <div>value: {test}</div>\n        </>\n      )\n    }\n    const user = userEvent.setup()\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    await render(<TestComponent />, {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate,\n        searchParams: '?test=init'\n      })\n    })\n    await expect.element(page.getByText('value: init')).toBeInTheDocument()\n    expect(renderCount).toBe(1)\n    expect(onUrlUpdate).toHaveBeenCalledTimes(0)\n\n    await user.click(page.getByRole('button', { name: 'Start' }))\n\n    expect(renderCount).toBe(1) // same render count as before\n    expect(onUrlUpdate).toHaveBeenCalledTimes(1) // url update is still called\n  })\n})\n\ndescribe('useQueryStates: urlKeys remapping', () => {\n  it('uses the object key names by default', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        foo: parseAsString,\n        bar: parseAsString\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?foo=init&bar=init',\n        onUrlUpdate\n      })\n    })\n    expect(result.current[0].foo).toEqual('init')\n    expect(result.current[0].bar).toEqual('init')\n    await act(() => result.current[1]({ foo: 'a', bar: 'b' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?foo=a&bar=b')\n  })\n\n  it('allows remapping keys partially', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates(\n        {\n          foo: parseAsString,\n          bar: parseAsString\n        },\n        {\n          urlKeys: {\n            foo: 'f'\n          }\n        }\n      )\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?f=foo&bar=bar',\n        onUrlUpdate\n      })\n    })\n    expect(result.current[0].foo).toEqual('foo')\n    expect(result.current[0].bar).toEqual('bar')\n    await act(() => result.current[1]({ foo: 'a', bar: 'b' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?f=a&bar=b')\n  })\n\n  it('should have referential equality on the state updater function', async () => {\n    const { result, rerender, act } = await renderHook(\n      () => useQueryStates({ test: parseAsString }),\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    const [, setState1] = result.current\n    rerender()\n    const [, setState2] = result.current\n    expect(setState1).toBe(setState2)\n    await act(() => setState2({ test: 'pass' }))\n    const [, setState3] = result.current\n    expect(setState1).toBe(setState3)\n  })\n})\n\ndescribe('useQueryStates: clearOnDefault', () => {\n  it('honors clearOnDefault: true by default', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        test: parseAsString.withDefault('default')\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?test=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ test: 'default' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('')\n  })\n\n  it('supports clearOnDefault: false (parser level)', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({\n        a: parseAsString.withDefault('default').withOptions({\n          clearOnDefault: false\n        }),\n        b: parseAsString.withDefault('default')\n      })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ a: 'default', b: 'default' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default')\n  })\n\n  it('supports clearOnDefault: false (hook level)', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates(\n        {\n          a: parseAsString.withDefault('default'),\n          b: parseAsString.withDefault('default').withOptions({\n            clearOnDefault: true // overrides hook options\n          })\n        },\n        {\n          clearOnDefault: false\n        }\n      )\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ a: 'default', b: 'default' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=default')\n  })\n\n  it('supports clearOnDefault: false (call level)', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates(\n        {\n          a: parseAsString.withDefault('default'),\n          b: parseAsString.withDefault('default').withOptions({\n            clearOnDefault: true // overrides hook options\n          })\n        },\n        {\n          clearOnDefault: false\n        }\n      )\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=init&b=init',\n        onUrlUpdate\n      })\n    })\n    await act(() =>\n      result.current[1](\n        { a: 'default', b: 'default' },\n        {\n          clearOnDefault: true\n        }\n      )\n    )\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('')\n  })\n})\n\ndescribe('useQueryStates: dynamic keys', () => {\n  it('supports dynamic keys', async () => {\n    const useTestHook = (keys: [string, string] = ['a', 'b']) =>\n      useQueryStates({\n        [keys[0]]: parseAsInteger,\n        [keys[1]]: parseAsInteger\n      })\n    const { result, rerender } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?a=1&b=2&c=3&d=4'\n      })\n    })\n    expect(result.current[0].a).toEqual(1)\n    expect(result.current[0].b).toEqual(2)\n    expect(result.current[0].c).toBeUndefined()\n    expect(result.current[0].d).toBeUndefined()\n    rerender(['c', 'd'])\n    expect(result.current[0].a).toBeUndefined()\n    expect(result.current[0].b).toBeUndefined()\n    expect(result.current[0].c).toEqual(3)\n    expect(result.current[0].d).toEqual(4)\n  })\n\n  it('updating keys also updates the result structure', async () => {\n    const useTestHook = (keys: string[] = ['a', 'b']) =>\n      useQueryStates(\n        keys.reduce((acc, key) => ({ ...acc, [key]: parseAsInteger }), {})\n      )\n    const { result, rerender } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: ''\n      })\n    })\n    expect(result.current[0]).toStrictEqual({ a: null, b: null })\n    rerender(['a']) // remove b\n    expect(result.current[0]).toStrictEqual({ a: null })\n    rerender(['a', 'b', 'c']) // add c\n    expect(result.current[0]).toStrictEqual({ a: null, b: null, c: null })\n    rerender(['a', 'b', 'd']) // remove c, add d\n    expect(result.current[0]).toStrictEqual({ a: null, b: null, d: null })\n  })\n\n  it('supports dynamic keys with remapping', async () => {\n    const useTestHook = (keys: [string, string] = ['a', 'b']) =>\n      useQueryStates(\n        {\n          [keys[0]]: parseAsInteger,\n          [keys[1]]: parseAsInteger\n        },\n        {\n          urlKeys: {\n            a: 'x',\n            b: 'y',\n            c: 'z'\n          }\n        }\n      )\n    const { result, rerender } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        searchParams: '?x=1&y=2&z=3'\n      })\n    })\n    expect(result.current[0].a).toEqual(1)\n    expect(result.current[0].b).toEqual(2)\n    expect(result.current[0].c).toBeUndefined()\n    expect(result.current[0].d).toBeUndefined()\n    expect(result.current[0].x).toBeUndefined()\n    expect(result.current[0].y).toBeUndefined()\n    expect(result.current[0].z).toBeUndefined()\n    rerender(['c', 'd'])\n    expect(result.current[0].a).toBeUndefined()\n    expect(result.current[0].b).toBeUndefined()\n    expect(result.current[0].c).toEqual(3)\n    expect(result.current[0].d).toBeNull()\n    expect(result.current[0].x).toBeUndefined()\n    expect(result.current[0].y).toBeUndefined()\n    expect(result.current[0].z).toBeUndefined()\n  })\n})\n\ndescribe('useQueryStates: update sequencing', () => {\n  it('should combine updates for a single key made in the same event loop tick', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => useQueryStates({ test: parseAsString }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate\n        })\n      }\n    )\n    await act(() => {\n      result.current[1]({ test: 'a' })\n      return result.current[1]({ test: 'b' })\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=b')\n  })\n\n  it('should combine updates for multiple keys in the same hook made in the same event loop tick', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => useQueryStates({ a: parseAsString, b: parseAsString }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate\n        })\n      }\n    )\n    await act(() => {\n      result.current[1]({ a: 'a' })\n      return result.current[1](() => ({ b: 'b' }))\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=a&b=b')\n  })\n\n  it('should combine updates for multiple keys in different hook made in the same event loop tick', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => ({\n        a: useQueryStates({ a: parseAsString }),\n        b: useQueryStates({ b: parseAsString })\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate\n        })\n      }\n    )\n    await act(() => {\n      result.current.a[1]({ a: 'a' })\n      return result.current.b[1](() => ({ b: 'b' }))\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?a=a&b=b')\n  })\n\n  it('should return a stable Promise when pushing multiple updates in the same tick', async () => {\n    const { result, act } = await renderHook(\n      () =>\n        useQueryStates({\n          a: parseAsString,\n          b: parseAsString\n        }),\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(() => {\n      p1 = result.current[1]({ a: 'a' })\n      p2 = result.current[1]({ b: 'b' })\n      return p2\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).toBe(p2)\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n\n  it('should return a stable Promise when pushing multiple updates in the same tick (multiple useQueryStates)', async () => {\n    const { result, act } = await renderHook(\n      () => ({\n        foo: useQueryStates({ a: parseAsString }),\n        bar: useQueryStates({ b: parseAsString })\n      }),\n      {\n        wrapper: withNuqsTestingAdapter()\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(() => {\n      p1 = result.current.foo[1]({ a: 'a' })\n      p2 = result.current.bar[1]({ b: 'b' })\n      return p2\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).toBe(p2)\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n\n  it('should return a stable Promise when pushing updates before the throttle period times out', async () => {\n    const { result, act } = await renderHook(\n      () => ({\n        foo: useQueryStates({ a: parseAsString }),\n        bar: useQueryStates({ b: parseAsString })\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p0: Promise<URLSearchParams> | undefined = undefined\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    // prettier-ignore\n    await act(async () => {\n        // Flush the queue from previous tests\n        await new Promise(r => setTimeout(r, 60))\n        // First, push an update to a to be emitted \"immediately\"\n        p0 = result.current.foo[1]({ a: 'init' })\n        // Then two updates before the end of the throttle timeout\n        setTimeout(() => { p1 = result.current.foo[1]({a:'a'}) }, 10)\n        setTimeout(() => { p2 = result.current.bar[1]({b:'b'}) }, 20)\n        return new Promise((resolve) => setTimeout(resolve, 30))\n      })\n    expect(p0).toBeInstanceOf(Promise)\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p0).not.toBe(p1)\n    expect(p1).toBe(p2)\n    await expect(p0).resolves.toEqual(new URLSearchParams('?a=init'))\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n\n  it('should return the same Promise as useQueryState', async () => {\n    const { result, act } = await renderHook(\n      () => ({\n        foo: useQueryStates({ a: parseAsString }),\n        bar: useQueryState('b')\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p0: Promise<URLSearchParams> | undefined = undefined\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    // prettier-ignore\n    await act(async () => {\n        // Flush the queue from previous tests\n        await new Promise(r => setTimeout(r, 60))\n        // First, push an update to a to be emitted \"immediately\"\n        p0 = result.current.foo[1]({ a: 'init' })\n        // Then two updates before the end of the throttle timeout\n        setTimeout(() => { p1 = result.current.foo[1]({a:'a'}) }, 10)\n        setTimeout(() => { p2 = result.current.bar[1]('b') }, 20)\n        return new Promise((resolve) => setTimeout(resolve, 30))\n      })\n    expect(p0).toBeInstanceOf(Promise)\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p0).not.toBe(p1)\n    expect(p1).toBe(p2)\n    await expect(p0).resolves.toEqual(new URLSearchParams('?a=init'))\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a&b=b'))\n  })\n\n  it('should return a new Promise when using debounce', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => ({\n        foo: useQueryStates({\n          a: parseAsString.withOptions({ limitUrlUpdates: debounce(100) })\n        }),\n        bar: useQueryStates({\n          b: parseAsString\n        })\n      }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate,\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p1 = result.current.foo[1]({ a: 'a' })\n      p2 = result.current.bar[1]({ b: 'b' })\n      return p1 // p1 will resolve last, so await it before moving on\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).not.toBe(p2)\n    // Note: our mock adapter does not save search params, so there is no merge\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=a'))\n    await expect(p2).resolves.toEqual(new URLSearchParams('?b=b'))\n    expect(onUrlUpdate).toHaveBeenCalledTimes(2)\n    // b updates first, then a\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?b=b')\n    expect(onUrlUpdate.mock.calls[1]![0].queryString).toEqual('?a=a')\n  })\n\n  it('aborts a debounced update when pushing a throttled one', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () =>\n        useQueryStates({\n          test: parseAsString\n        }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate,\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p1 = result.current[1](\n        { test: 'init' },\n        { limitUrlUpdates: debounce(100) }\n      )\n      p2 = result.current[1]({ test: 'pass' })\n      return Promise.allSettled([p1, p2])\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).not.toBe(p2)\n    // Note: our mock adapter does not save search params, so there is no merge\n    await expect(p1).resolves.toEqual(new URLSearchParams('?test=pass'))\n    await expect(p2).resolves.toEqual(new URLSearchParams('?test=pass'))\n    expect(onUrlUpdate).toHaveBeenCalledTimes(1)\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=pass')\n  })\n\n  it('does not abort when pushing another key', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () =>\n        useQueryStates({\n          a: parseAsString.withOptions({ limitUrlUpdates: debounce(100) }),\n          b: parseAsString\n        }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate,\n          rateLimitFactor: 1\n        })\n      }\n    )\n    let p1: Promise<URLSearchParams> | undefined = undefined\n    let p2: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p1 = result.current[1]({ a: 'debounced' })\n      p2 = result.current[1]({ b: 'pass' })\n      return Promise.allSettled([p1, p2])\n    })\n    expect(p1).toBeInstanceOf(Promise)\n    expect(p2).toBeInstanceOf(Promise)\n    expect(p1).not.toBe(p2)\n    // Note: our mock adapter does not save search params, so there is no merge\n    await expect(p1).resolves.toEqual(new URLSearchParams('?a=debounced'))\n    await expect(p2).resolves.toEqual(new URLSearchParams('?b=pass'))\n    expect(onUrlUpdate).toHaveBeenCalledTimes(2)\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?b=pass')\n    expect(onUrlUpdate.mock.calls[1]![0].queryString).toEqual('?a=debounced')\n  })\n\n  it('does flush when pushing throttled updates', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => useQueryStates({ test: parseAsString }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate,\n          autoResetQueueOnUpdate: false\n        })\n      }\n    )\n    let p: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p = result.current[1](\n        { test: 'pass' },\n        { limitUrlUpdates: throttle(100) }\n      )\n      await waitForNextTick()\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=pass')\n    await expect(p).resolves.toEqual(new URLSearchParams('?test=pass'))\n  })\n\n  it('does not flush when pushing debounced updates', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const { result, act } = await renderHook(\n      () => useQueryStates({ test: parseAsString }),\n      {\n        wrapper: withNuqsTestingAdapter({\n          onUrlUpdate,\n          autoResetQueueOnUpdate: false\n        })\n      }\n    )\n    // Flush a first time without resetting the queue to keep pending items\n    // in the global throttle queue.\n    await act(() => result.current[1]({ test: 'init' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=init')\n    onUrlUpdate.mockClear()\n    // Now push a debounced update, which should not flush immediately\n    let p: Promise<URLSearchParams> | undefined = undefined\n    await act(async () => {\n      p = result.current[1](\n        { test: 'pass' },\n        { limitUrlUpdates: debounce(100) }\n      )\n      await waitForNextTick()\n    })\n    expect(onUrlUpdate).not.toHaveBeenCalled()\n    await expect(p).resolves.toEqual(new URLSearchParams('?test=pass'))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toEqual('?test=pass')\n  })\n})\n\ndescribe('useQueryStates: adapter defaults', () => {\n  it('should use adapter default value for `shallow` when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () => useQueryStates({ test: parseAsString })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        defaultOptions: {\n          shallow: false\n        },\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ test: 'update' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].options.shallow).toBe(false)\n  })\n  it('should use adapter default value for `scroll` when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () => useQueryStates({ test: parseAsString })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        defaultOptions: {\n          scroll: true\n        },\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ test: 'update' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].options.scroll).toBe(true)\n  })\n  it('should use adapter default value for `clearOnDefault` when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () =>\n      useQueryStates({ test: parseAsString.withDefault('pass') })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        defaultOptions: {\n          clearOnDefault: false\n        },\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ test: 'pass' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toBe('?test=pass')\n  })\n})\n\ndescribe('useQueryStates: process url search params', () => {\n  it('should use adapter processUrlSearchParams when provided', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () => useQueryStates({ test: parseAsString })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        processUrlSearchParams: search => {\n          const params = new URLSearchParams(search)\n          params.set('test', 'processed')\n          return params\n        },\n        onUrlUpdate\n      })\n    })\n    await act(() => result.current[1]({ test: 'update' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toBe('?test=processed')\n  })\n  it('should follow changes in the processUrlSearchParams callback', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    function DynamicWrapper({ children }: { children: ReactNode }) {\n      const [process, setProcess] = useState(false)\n      // Who needs JSX in tests anyway?\n      return createElement(NuqsTestingAdapter, {\n        onUrlUpdate,\n        processUrlSearchParams: process\n          ? search => {\n              search.set('test', 'processed')\n              return search\n            }\n          : undefined,\n        children: [\n          createElement('button', {\n            key: 'btn',\n            onClick: () => setProcess(p => !p),\n            'data-testid': 'btn',\n            'data-state': process ? 'on' : 'off'\n          }),\n          children\n        ]\n      })\n    }\n    const useTestHook = () => useQueryStates({ test: parseAsString })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: DynamicWrapper\n    })\n    const button = page.getByTestId('btn')\n    expect(button.element().getAttribute('data-state')).toBe('off')\n    await act(() => result.current[1]({ test: 'pass' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toBe('?test=pass')\n    onUrlUpdate.mockReset()\n    await act(() => button.click())\n    expect(button.element().getAttribute('data-state')).toBe('on')\n    await act(() => result.current[1]({ test: 'fail-if-kept' }))\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toBe('?test=processed')\n  })\n  it('should call processUrlSearchParams after a debounced update', async () => {\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    const useTestHook = () => useQueryStates({ test: parseAsString })\n    const { result, act } = await renderHook(useTestHook, {\n      wrapper: withNuqsTestingAdapter({\n        processUrlSearchParams: search => {\n          expect(search.get('test')).toBe('fail')\n          search.set('test', 'pass')\n          return search\n        },\n        onUrlUpdate\n      })\n    })\n    await act(async () => {\n      await result.current[1](\n        { test: 'fail' },\n        { limitUrlUpdates: debounce(50) }\n      )\n    })\n    expect(onUrlUpdate).toHaveBeenCalledOnce()\n    expect(onUrlUpdate.mock.calls[0]![0].queryString).toBe('?test=pass')\n  })\n})\n\ndescribe('useQueryStates: edge cases & repros', () => {\n  it('should not go through transient old state when combined with another state update (#1099)', async () => {\n    function TestComponent() {\n      const [{ test }, setSearchParams] = useQueryStates({\n        test: parseAsString\n      })\n      const [isNullDetectorEnabled, setIsNullDetectorEnabled] = useState(false)\n      const isLoading = useFakeLoadingState(test)\n      return (\n        <>\n          <button\n            onClick={() => {\n              setIsNullDetectorEnabled(true)\n              setSearchParams({ test: 'pass' })\n            }}\n          >\n            Start\n          </button>\n          <NullDetector\n            state={test}\n            enabled={isNullDetectorEnabled}\n            data-testid=\"null-detector\"\n          />\n          <p>isLoading: {String(isLoading)}</p>\n        </>\n      )\n    }\n    const user = userEvent.setup()\n    const onUrlUpdate = vi.fn<OnUrlUpdateFunction>()\n    render(<TestComponent />, {\n      wrapper: withNuqsTestingAdapter({\n        onUrlUpdate,\n        hasMemory: true // needs memory for the test to pass\n      })\n    })\n    await expect\n      .element(page.getByTestId('null-detector'))\n      .toHaveTextContent('pass')\n    await expect.element(page.getByText('isLoading: false')).toBeInTheDocument()\n    await user.click(page.getByRole('button', { name: 'Start' }))\n    await expect.element(page.getByText('isLoading: true')).toBeInTheDocument()\n    await expect.element(page.getByText('isLoading: false')).toBeInTheDocument()\n    await expect\n      .element(page.getByTestId('null-detector'))\n      .toHaveTextContent('pass')\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/src/useQueryStates.ts",
    "content": "import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport {\n  useAdapter,\n  useAdapterDefaultOptions,\n  useAdapterProcessUrlSearchParams\n} from './adapters/lib/context'\nimport type { Nullable, Options, UrlKeys } from './defs'\nimport { compareQuery } from './lib/compare'\nimport { debug } from './lib/debug'\nimport { error } from './lib/errors'\nimport { debounceController } from './lib/queues/debounce'\nimport { defaultRateLimit } from './lib/queues/rate-limiting'\nimport {\n  globalThrottleQueue,\n  type UpdateQueuePushArgs\n} from './lib/queues/throttle'\nimport { safeParse } from './lib/safe-parse'\nimport { isAbsentFromUrl, type Query } from './lib/search-params'\nimport { emitter, type CrossHookSyncPayload } from './lib/sync'\nimport { type GenericParser } from './parsers'\n\ntype KeyMapValue<Type> = GenericParser<Type> &\n  Options & {\n    defaultValue?: Type\n  }\n\nexport type UseQueryStatesKeysMap<Map = any> = {\n  [Key in keyof Map]: KeyMapValue<Map[Key]>\n} & {}\n\nexport type UseQueryStatesOptions<KeyMap extends UseQueryStatesKeysMap> =\n  Options & {\n    urlKeys: UrlKeys<KeyMap>\n  }\n\nexport type Values<T extends UseQueryStatesKeysMap> = {\n  [K in keyof T]: T[K]['defaultValue'] extends NonNullable<\n    ReturnType<T[K]['parse']>\n  >\n    ? NonNullable<ReturnType<T[K]['parse']>>\n    : ReturnType<T[K]['parse']> | null\n}\ntype NullableValues<T extends UseQueryStatesKeysMap> = Nullable<Values<T>>\n\ntype UpdaterFn<T extends UseQueryStatesKeysMap> = (\n  old: Values<T>\n) => Partial<Nullable<Values<T>>> | null\n\nexport type SetValues<T extends UseQueryStatesKeysMap> = (\n  values: Partial<Nullable<Values<T>>> | UpdaterFn<T> | null,\n  options?: Options\n) => Promise<URLSearchParams>\n\nexport type UseQueryStatesReturn<T extends UseQueryStatesKeysMap> = [\n  Values<T>,\n  SetValues<T>\n]\n\n// Ensure referential consistency for the default value of urlKeys\n// by hoisting it out of the function scope.\n// Otherwise useEffect loops go brrrr\nconst defaultUrlKeys = {}\n\n/**\n * Synchronise multiple query string arguments to React state in Next.js\n *\n * @param keys - An object describing the keys to synchronise and how to\n *               serialise and parse them.\n *               Use `parseAs(String|Integer|Float|...)` for quick shorthands.\n * @param options - Optional history mode, shallow routing and scroll restoration options.\n */\nexport function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(\n  keyMap: KeyMap,\n  options: Partial<UseQueryStatesOptions<KeyMap>> = {}\n): UseQueryStatesReturn<KeyMap> {\n  const hookId = useId()\n  const defaultOptions = useAdapterDefaultOptions()\n  const processUrlSearchParams = useAdapterProcessUrlSearchParams()\n\n  const {\n    history = 'replace',\n    scroll = defaultOptions?.scroll ?? false,\n    shallow = defaultOptions?.shallow ?? true,\n    throttleMs = defaultRateLimit.timeMs,\n    limitUrlUpdates = defaultOptions?.limitUrlUpdates,\n    clearOnDefault = defaultOptions?.clearOnDefault ?? true,\n    startTransition,\n    urlKeys = defaultUrlKeys as UrlKeys<KeyMap>\n  } = options\n\n  type V = NullableValues<KeyMap>\n  const stateKeys = Object.keys(keyMap).join(',')\n  const resolvedUrlKeys = useMemo(\n    () =>\n      Object.fromEntries(\n        Object.keys(keyMap).map(key => [key, urlKeys[key] ?? key])\n      ),\n    [stateKeys, JSON.stringify(urlKeys)]\n  )\n  const adapter = useAdapter(Object.values(resolvedUrlKeys))\n  const initialSearchParams = adapter.searchParams\n  const queryRef = useRef<Record<string, Query | null>>({})\n  const defaultValues = useMemo(\n    () =>\n      Object.fromEntries(\n        Object.keys(keyMap).map(key => [key, keyMap[key]!.defaultValue ?? null])\n      ) as Values<KeyMap>,\n    [\n      Object.values(keyMap)\n        .map(({ defaultValue }) => defaultValue)\n        .join(',')\n    ]\n  )\n  const queuedQueries = debounceController.useQueuedQueries(\n    Object.values(resolvedUrlKeys)\n  )\n  const [internalState, setInternalState] = useState<V>(() => {\n    const source = initialSearchParams ?? new URLSearchParams()\n    return parseMap(keyMap, urlKeys, source, queuedQueries).state\n  })\n\n  const stateRef = useRef(internalState)\n  debug(\n    '[nuq+ %s `%s`] render - state: %O, iSP: %s',\n    hookId,\n    stateKeys,\n    internalState,\n    initialSearchParams\n  )\n\n  // Initialise the refs with the initial values\n  if (\n    Object.keys(queryRef.current).join('&') !==\n    Object.values(resolvedUrlKeys).join('&')\n  ) {\n    const { state, hasChanged } = parseMap(\n      keyMap,\n      urlKeys,\n      initialSearchParams,\n      queuedQueries,\n      queryRef.current,\n      stateRef.current\n    )\n    if (hasChanged) {\n      debug('[nuq+ %s `%s`] State changed: %O', hookId, stateKeys, {\n        state,\n        initialSearchParams,\n        queuedQueries,\n        queryRef: queryRef.current,\n        stateRef: stateRef.current\n      })\n      stateRef.current = state\n      setInternalState(state)\n    }\n    queryRef.current = Object.fromEntries(\n      Object.entries(resolvedUrlKeys).map(([key, urlKey]) => {\n        const parser = keyMap[key]\n        return [\n          urlKey,\n          parser?.type === 'multi'\n            ? initialSearchParams?.getAll(urlKey)\n            : (initialSearchParams?.get(urlKey) ?? null)\n        ]\n      })\n    )\n  }\n\n  useEffect(() => {\n    const { state, hasChanged } = parseMap(\n      keyMap,\n      urlKeys,\n      initialSearchParams,\n      queuedQueries,\n      queryRef.current,\n      stateRef.current\n    )\n    if (hasChanged) {\n      debug('[nuq+ %s `%s`] State changed: %O', hookId, stateKeys, {\n        state,\n        initialSearchParams,\n        queuedQueries,\n        queryRef: queryRef.current,\n        stateRef: stateRef.current\n      })\n      stateRef.current = state\n      setInternalState(state)\n    }\n  }, [\n    Object.values(resolvedUrlKeys)\n      .map(key => `${key}=${initialSearchParams?.getAll(key)}`)\n      .join('&'),\n    JSON.stringify(queuedQueries)\n  ])\n\n  // Sync all hooks together & with external URL changes\n  useEffect(() => {\n    const handlers = Object.keys(keyMap).reduce(\n      (handlers, stateKey) => {\n        handlers[stateKey as keyof KeyMap] = ({\n          state,\n          query\n        }: CrossHookSyncPayload) => {\n          setInternalState(currentState => {\n            const { defaultValue } = keyMap[stateKey]!\n            const urlKey = resolvedUrlKeys[stateKey]!\n            const nextValue = state ?? defaultValue ?? null\n            const currentValue = currentState[stateKey] ?? defaultValue ?? null\n\n            if (Object.is(currentValue, nextValue)) {\n              debug(\n                '[nuq+ %s `%s`] Cross-hook key sync %s: %O (default: %O). no change, skipping, resolved: %O',\n                hookId,\n                stateKeys,\n                urlKey,\n                state,\n                defaultValue,\n                stateRef.current\n              )\n              // bail out by returning the current state\n              return currentState\n            }\n            // Note: cannot mutate in-place, the object ref must change\n            // for the subsequent setState to pick it up.\n            stateRef.current = {\n              ...stateRef.current,\n              [stateKey as keyof KeyMap]: nextValue\n            }\n            queryRef.current[urlKey] = query\n            debug(\n              '[nuq+ %s `%s`] Cross-hook key sync %s: %O (default: %O). updateInternalState, resolved: %O',\n              hookId,\n              stateKeys,\n              urlKey,\n              state,\n              defaultValue,\n              stateRef.current\n            )\n            return stateRef.current\n          })\n        }\n        return handlers\n      },\n      {} as Record<keyof KeyMap, (payload: CrossHookSyncPayload) => void>\n    )\n\n    for (const stateKey of Object.keys(keyMap)) {\n      const urlKey = resolvedUrlKeys[stateKey]!\n      debug(\n        '[nuq+ %s `%s`] Subscribing to sync for `%s`',\n        hookId,\n        urlKey,\n        stateKeys\n      )\n      emitter.on(urlKey, handlers[stateKey]!)\n    }\n    return () => {\n      for (const stateKey of Object.keys(keyMap)) {\n        const urlKey = resolvedUrlKeys[stateKey]!\n        debug(\n          '[nuq+ %s `%s`] Unsubscribing to sync for `%s`',\n          hookId,\n          urlKey,\n          stateKeys\n        )\n        emitter.off(urlKey, handlers[stateKey])\n      }\n    }\n  }, [stateKeys, resolvedUrlKeys])\n\n  const update = useCallback<SetValues<KeyMap>>(\n    (stateUpdater, callOptions = {}) => {\n      const nullMap = Object.fromEntries(\n        Object.keys(keyMap).map(key => [key, null])\n      ) as Nullable<KeyMap>\n      const newState: Partial<Nullable<KeyMap>> =\n        typeof stateUpdater === 'function'\n          ? (stateUpdater(\n              applyDefaultValues(stateRef.current, defaultValues)\n            ) ?? nullMap)\n          : (stateUpdater ?? nullMap)\n      debug('[nuq+ %s `%s`] setState: %O', hookId, stateKeys, newState)\n      let returnedPromise: Promise<URLSearchParams> | undefined = undefined\n      let maxDebounceTime = 0\n      let doFlush = false\n      const debounceAborts: Array<\n        (p: Promise<URLSearchParams>) => Promise<URLSearchParams>\n      > = []\n      for (let [stateKey, value] of Object.entries(newState)) {\n        const parser = keyMap[stateKey]\n        const urlKey = resolvedUrlKeys[stateKey]!\n        if (!parser || value === undefined) {\n          continue\n        }\n        if (\n          (callOptions.clearOnDefault ??\n            parser.clearOnDefault ??\n            clearOnDefault) &&\n          value !== null &&\n          parser.defaultValue !== undefined &&\n          (parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue)\n        ) {\n          value = null\n        }\n        const query =\n          value === null ? null : (parser.serialize ?? String)(value)\n        emitter.emit(urlKey, { state: value, query })\n        const update: UpdateQueuePushArgs = {\n          key: urlKey,\n          query,\n          options: {\n            // Call-level options take precedence over individual parser options\n            // which take precedence over global options\n            history: callOptions.history ?? parser.history ?? history,\n            shallow: callOptions.shallow ?? parser.shallow ?? shallow,\n            scroll: callOptions.scroll ?? parser.scroll ?? scroll,\n            startTransition:\n              callOptions.startTransition ??\n              parser.startTransition ??\n              startTransition\n          }\n        }\n        if (\n          callOptions?.limitUrlUpdates?.method === 'debounce' ||\n          limitUrlUpdates?.method === 'debounce' ||\n          parser.limitUrlUpdates?.method === 'debounce'\n        ) {\n          if (update.options.shallow === true) {\n            console.warn(error(422))\n          }\n          const timeMs =\n            callOptions?.limitUrlUpdates?.timeMs ??\n            limitUrlUpdates?.timeMs ??\n            parser.limitUrlUpdates?.timeMs ??\n            defaultRateLimit.timeMs\n          const debouncedPromise = debounceController.push(\n            update,\n            timeMs,\n            adapter,\n            processUrlSearchParams\n          )\n          if (maxDebounceTime < timeMs) {\n            // The largest debounce is likely to be the last URL update,\n            // so we keep that Promise to return it.\n            returnedPromise = debouncedPromise\n            maxDebounceTime = timeMs\n          }\n        } else {\n          const timeMs =\n            callOptions?.limitUrlUpdates?.timeMs ??\n            parser?.limitUrlUpdates?.timeMs ??\n            limitUrlUpdates?.timeMs ??\n            callOptions.throttleMs ??\n            parser.throttleMs ??\n            throttleMs\n          debounceAborts.push(debounceController.abort(urlKey))\n          globalThrottleQueue.push(update, timeMs)\n          doFlush = true\n        }\n      }\n      // We need to flush the throttle queue, but we may have a pending\n      // debounced update that will resolve afterwards.\n      const globalPromise = debounceAborts.reduce(\n        (previous, fn) => fn(previous),\n        doFlush\n          ? globalThrottleQueue.flush(adapter, processUrlSearchParams)\n          : globalThrottleQueue.getPendingPromise(adapter)\n      )\n      return returnedPromise ?? globalPromise\n    },\n    [\n      stateKeys,\n      history,\n      shallow,\n      scroll,\n      throttleMs,\n      limitUrlUpdates?.method,\n      limitUrlUpdates?.timeMs,\n      startTransition,\n      resolvedUrlKeys,\n      adapter.updateUrl,\n      adapter.getSearchParamsSnapshot,\n      adapter.rateLimitFactor,\n      processUrlSearchParams,\n      defaultValues\n    ]\n  )\n\n  const outputState = useMemo(\n    () => applyDefaultValues(internalState, defaultValues),\n    [internalState, defaultValues]\n  )\n  return [outputState, update]\n}\n\n// --\n\nfunction parseMap<KeyMap extends UseQueryStatesKeysMap>(\n  keyMap: KeyMap,\n  urlKeys: Partial<Record<keyof KeyMap, string>>,\n  searchParams: URLSearchParams,\n  queuedQueries: Record<string, Query | null | undefined>,\n  cachedQuery?: Record<string, Query | null>,\n  cachedState?: NullableValues<KeyMap>\n): {\n  state: NullableValues<KeyMap>\n  hasChanged: boolean\n} {\n  let hasChanged = false\n  const state = Object.entries(keyMap).reduce((out, [stateKey, parser]) => {\n    const urlKey = urlKeys?.[stateKey] ?? stateKey\n    const queuedQuery = queuedQueries[urlKey]\n    const fallbackValue = parser.type === 'multi' ? [] : null\n    const query =\n      queuedQuery === undefined\n        ? ((parser.type === 'multi'\n            ? searchParams?.getAll(urlKey)\n            : searchParams?.get(urlKey)) ?? fallbackValue)\n        : queuedQuery\n    if (\n      cachedQuery &&\n      cachedState &&\n      compareQuery(cachedQuery[urlKey] ?? fallbackValue, query)\n    ) {\n      // Cache hit\n      out[stateKey as keyof KeyMap] = cachedState[stateKey] ?? null\n      return out\n    }\n    // Cache miss\n    hasChanged = true\n    const value = isAbsentFromUrl(query)\n      ? null\n      : // we have properly narrowed `query` here, but TS doesn't keep track of that\n        safeParse(parser.parse, query as string & Array<string>, urlKey)\n\n    out[stateKey as keyof KeyMap] = value ?? null\n    if (cachedQuery) {\n      cachedQuery[urlKey] = query\n    }\n    return out\n  }, {} as NullableValues<KeyMap>)\n\n  if (!hasChanged) {\n    // check that keyMap keys have not changed\n    const keyMapKeys = Object.keys(keyMap)\n    const cachedStateKeys = Object.keys(cachedState ?? {})\n    hasChanged =\n      keyMapKeys.length !== cachedStateKeys.length ||\n      keyMapKeys.some(key => !cachedStateKeys.includes(key))\n  }\n\n  return { state, hasChanged }\n}\n\nfunction applyDefaultValues<KeyMap extends UseQueryStatesKeysMap>(\n  state: NullableValues<KeyMap>,\n  defaults: Partial<Values<KeyMap>>\n) {\n  return Object.fromEntries(\n    Object.keys(state).map(key => [key, state[key] ?? defaults[key] ?? null])\n  ) as Values<KeyMap>\n}\n"
  },
  {
    "path": "packages/nuqs/testing.d.ts",
    "content": "// This file is needed for projects that have `moduleResolution` set to `node`\n// in their tsconfig.json to be able to `import {} from 'nuqs/testing'`.\n// Other module resolutions strategies will look for the `exports` in `package.json`,\n// but with `node`, TypeScript will look for a .d.ts file with that name at the\n// root of the package.\n\nexport * from './dist/testing'\n"
  },
  {
    "path": "packages/nuqs/tests/cache.test-d.ts",
    "content": "import { assertType, describe, expectTypeOf, it } from 'vitest'\nimport {\n  createSearchParamsCache,\n  parseAsBoolean,\n  parseAsInteger,\n  parseAsString\n} from '../dist/server'\n\ndescribe('types/cache', () => {\n  const cache = createSearchParamsCache({\n    foo: parseAsString,\n    bar: parseAsInteger,\n    egg: parseAsBoolean.withDefault(false)\n  })\n  type All = Readonly<{ foo: string | null; bar: number | null; egg: boolean }>\n\n  it('has a type-safe `parse` method that returns all entries', () => {\n    assertType<All>(cache.parse({}))\n  })\n\n  it('has a type-safe `all` method that returns all entries', () => {\n    assertType<All>(cache.all())\n  })\n\n  it('has a type-safe `get` method that returns a single entry', () => {\n    const cache = createSearchParamsCache({\n      foo: parseAsString,\n      bar: parseAsInteger,\n      egg: parseAsBoolean.withDefault(false)\n    })\n    assertType<string | null>(cache.get('foo'))\n    assertType<number | null>(cache.get('bar'))\n    assertType<boolean>(cache.get('egg'))\n    expectTypeOf(cache.get('egg')).not.toBeNull()\n    expectTypeOf(cache.get('foo')).not.toBeUndefined()\n    expectTypeOf(cache.get('bar')).not.toBeUndefined()\n    expectTypeOf(cache.get('egg')).not.toBeUndefined()\n\n    // @ts-expect-error\n    assertType(cache.get('spam'))\n  })\n\n  it('supports async search params (Next.js 15+)', () => {\n    const cache = createSearchParamsCache({\n      foo: parseAsString,\n      bar: parseAsInteger,\n      egg: parseAsBoolean.withDefault(false)\n    })\n    // Only `parse` is async, getters are unwrapped\n    assertType<Promise<All>>(cache.parse(Promise.resolve({})))\n    assertType<All>(cache.all())\n    assertType<string | null>(cache.get('foo'))\n    assertType<number | null>(cache.get('bar'))\n    assertType<boolean>(cache.get('egg'))\n  })\n\n  it('supports urlKeys', () => {\n    createSearchParamsCache(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          foo: 'f'\n          // It accepts partial inputs\n        }\n      }\n    )\n    createSearchParamsCache(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          foo: 'f'\n          // It accepts partial inputs\n        }\n      }\n    )\n    createSearchParamsCache(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          foo: 'f',\n          bar: 'b'\n        }\n      }\n    )\n    createSearchParamsCache(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          // @ts-expect-error\n          nope: 'n' // Doesn't accept extra properties\n        }\n      }\n    )\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/tests/components/repro-1099.tsx",
    "content": "import React, {\n  type ComponentProps,\n  type ReactElement,\n  type ReactNode,\n  useEffect,\n  useState\n} from 'react'\n\ntype NullDetectorProps = ComponentProps<'pre'> & {\n  state: ReactNode\n  enabled?: boolean\n}\n\nexport function NullDetector({\n  state,\n  enabled = true,\n  ...props\n}: NullDetectorProps): ReactElement {\n  const [hasBeenNullAtSomePoint, set] = useState(() =>\n    enabled ? state === null : false\n  )\n  useEffect(() => {\n    if (!enabled || state !== null) {\n      return\n    }\n    set(true)\n  }, [state, enabled])\n  return <pre {...props}>{hasBeenNullAtSomePoint ? 'fail' : 'pass'}</pre>\n}\n\nexport function useFakeLoadingState(trigger: unknown): boolean {\n  const [isLoading, setIsLoading] = useState(false)\n  useEffect(() => {\n    if (!trigger) {\n      return\n    }\n    setIsLoading(true)\n    const timeout = setTimeout(() => {\n      setIsLoading(false)\n    }, 100)\n    return () => clearTimeout(timeout)\n  }, [trigger])\n  return isLoading\n}\n"
  },
  {
    "path": "packages/nuqs/tests/parsers.test-d.ts",
    "content": "import { assertType, describe, expectTypeOf, it, test } from 'vitest'\nimport {\n  parseAsArrayOf,\n  parseAsBoolean,\n  parseAsFloat,\n  parseAsHex,\n  parseAsInteger,\n  parseAsIsoDate,\n  parseAsIsoDateTime,\n  parseAsJson,\n  parseAsNumberLiteral,\n  parseAsString,\n  parseAsStringEnum,\n  parseAsStringLiteral,\n  parseAsTimestamp,\n  type inferParserType\n} from '../dist'\n\ndescribe('types/parsers', () => {\n  test('parseAsString', () => {\n    const p = parseAsString\n    assertType<string | null>(p.parse('foo'))\n    assertType<string>(p.serialize('foo'))\n    assertType<string | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsInteger', () => {\n    const p = parseAsInteger\n    assertType<number | null>(p.parse('42'))\n    assertType<string>(p.serialize(42))\n    assertType<number | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsHex', () => {\n    const p = parseAsHex\n    assertType<number | null>(p.parse('42'))\n    assertType<string>(p.serialize(42))\n    assertType<number | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsFloat', () => {\n    const p = parseAsFloat\n    assertType<number | null>(p.parse('42'))\n    assertType<string>(p.serialize(42))\n    assertType<number | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsBoolean', () => {\n    const p = parseAsBoolean\n    assertType<boolean | null>(p.parse('true'))\n    assertType<string>(p.serialize(true))\n    assertType<boolean | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsTimestamp', () => {\n    const p = parseAsTimestamp\n    assertType<Date | null>(p.parse('2020-01-01T00:00:00Z'))\n    assertType<string>(p.serialize(new Date()))\n    assertType<Date | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsIsoDateTime', () => {\n    const p = parseAsIsoDateTime\n    assertType<Date | null>(p.parse('2020-01-01T00:00:00Z'))\n    assertType<string>(p.serialize(new Date()))\n    assertType<Date | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsIsoDate', () => {\n    const p = parseAsIsoDate\n    assertType<Date | null>(p.parse('2020-01-01T00:00:00Z'))\n    assertType<string>(p.serialize(new Date()))\n    assertType<Date | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsStringEnum', () => {\n    enum Test {\n      A = 'a',\n      B = 'b'\n    }\n    const p = parseAsStringEnum<Test>(Object.values(Test))\n    assertType<Test | null>(p.parse('a'))\n    assertType<string>(p.serialize(Test.A))\n    assertType<Test | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsStringLiteral', () => {\n    const p = parseAsStringLiteral(['a', 'b'])\n    assertType<'a' | 'b' | null>(p.parse('a'))\n    assertType<string>(p.serialize('a'))\n    assertType<'a' | 'b' | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsNumberLiteral', () => {\n    const p = parseAsNumberLiteral([1, 2, 3])\n    assertType<1 | 2 | 3 | null>(p.parse('42'))\n    assertType<string>(p.serialize(1))\n    assertType<1 | 2 | 3 | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsJson returns the return type of the validator', () => {\n    type T = { test: string }\n    const p = parseAsJson(value => value as T)\n    assertType<T | null>(p.parse('foo'))\n    assertType<string>(p.serialize({ test: 'foo' }))\n    assertType<T | null>(p.parseServerSide(undefined))\n  })\n  test('parseAsArrayOf composes existing item parsers', () => {\n    const p = parseAsArrayOf(parseAsInteger)\n    assertType<number[] | null>(p.parse('42'))\n    assertType<string>(p.serialize([42]))\n    assertType<number[] | null>(p.parseServerSide(undefined))\n  })\n\n  it('removes null from the type when the parser has a default value', () => {\n    const p = parseAsString.withDefault('default')\n    assertType<string | null>(p.parse('foo')) // This one allows null (can fail)\n    assertType<string>(p.parseServerSide(undefined)) // This one doesn't (defaults)\n  })\n  it('keeps the default value type-safe when combining with options (builder pattern)', () => {\n    const a = parseAsString.withDefault('default').withOptions({ scroll: true })\n    assertType<string | null>(a.parse('foo'))\n    assertType<string>(a.parseServerSide(undefined))\n    const b = parseAsString.withOptions({ scroll: true }).withDefault('default')\n    assertType<string | null>(b.parse('foo'))\n    assertType<string>(b.parseServerSide(undefined))\n  })\n})\n\ndescribe('types/parsers: inferParserType', () => {\n  it('infers the type of a single parser', () => {\n    expectTypeOf<inferParserType<typeof parseAsString>>().toEqualTypeOf<\n      string | null\n    >()\n  })\n  it('infers the type of a parser with a default value', () => {\n    const withDefault = parseAsString.withDefault('')\n    expectTypeOf<inferParserType<typeof withDefault>>().toEqualTypeOf<string>()\n  })\n  it('infers the type of an object of parsers', () => {\n    const parsers = {\n      str: parseAsString,\n      int: parseAsInteger\n    }\n    expectTypeOf<inferParserType<typeof parsers>>().toEqualTypeOf<{\n      str: string | null\n      int: number | null\n    }>()\n  })\n  it('infers the type of an object of parsers with default values', () => {\n    const parsers = {\n      str: parseAsString.withDefault(''),\n      int: parseAsInteger.withDefault(0)\n    }\n    expectTypeOf<inferParserType<typeof parsers>>().toEqualTypeOf<{\n      str: string\n      int: number\n    }>()\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/tests/serializer.test-d.ts",
    "content": "import { assertType, describe, it } from 'vitest'\nimport { createSerializer, parseAsInteger, parseAsString } from '../dist'\n\ndescribe('types/serializer', () => {\n  const serialize = createSerializer({\n    foo: parseAsString,\n    bar: parseAsInteger\n  })\n  it('returns a string', () => {\n    assertType<string>(serialize({}))\n    assertType<string>(serialize({ foo: 'foo', bar: 42 }))\n    assertType<string>(serialize({ foo: null, bar: null }))\n  })\n  // prettier-ignore\n  it('accepts a base', () => {\n    assertType<string>(serialize('/', {}))\n    assertType<string>(serialize('/', { foo: 'foo', bar: 42 }))\n    assertType<string>(serialize('/', { foo: null, bar: null }))\n    assertType<string>(serialize(new URLSearchParams(), {}))\n    assertType<string>(serialize(new URLSearchParams(), { foo: 'foo', bar: 42 }))\n    assertType<string>(serialize(new URLSearchParams(), { foo: null, bar: null }))\n    assertType<string>(serialize(new URL('https://example.com'), {}))\n    assertType<string>(serialize(new URL('https://example.com'), { foo: 'foo', bar: 42 }))\n    assertType<string>(serialize(new URL('https://example.com'), { foo: null, bar: null }))\n  })\n  it('allows clearing from the base', () => {\n    assertType<string>(serialize('/', null))\n    assertType<string>(serialize(new URLSearchParams(), null))\n    assertType<string>(serialize(new URL('https://example.com'), null))\n  })\n  it('accepts partial inputs', () => {\n    assertType<string>(serialize({ foo: 'foo' }))\n    assertType<string>(serialize({ bar: 42 }))\n  })\n  it(\"doesn't accept extra properties\", () => {\n    // @ts-expect-error\n    assertType(serialize({ nope: null }))\n  })\n  it('accepts null | undefined for values', () => {\n    assertType<string>(serialize({ foo: null }))\n    assertType<string>(serialize({ foo: undefined }))\n    assertType<string>(serialize({ bar: null }))\n    assertType<string>(serialize({ bar: undefined }))\n  })\n  it('supports urlKeys', () => {\n    createSerializer(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          foo: 'f'\n          // It accepts partial inputs\n        }\n      }\n    )\n    createSerializer(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          foo: 'f',\n          bar: 'b'\n        }\n      }\n    )\n    createSerializer(\n      {\n        foo: parseAsString,\n        bar: parseAsInteger\n      },\n      {\n        urlKeys: {\n          // @ts-expect-error\n          nope: 'n' // Doesn't accept extra properties\n        }\n      }\n    )\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/tests/useQueryState.test-d.ts",
    "content": "import { describe, expectTypeOf, it } from 'vitest'\nimport {\n  debounce,\n  defaultRateLimit,\n  parseAsString,\n  throttle,\n  useQueryState,\n  parseAsStringLiteral\n} from '../dist'\n\ndescribe('types/useQueryState', () => {\n  it('has a nullable string state by default', () => {\n    const [state, setState] = useQueryState('foo')\n    expectTypeOf(state).toEqualTypeOf<string | null>()\n    setState('bar')\n    setState(old => old?.toUpperCase() ?? null)\n    expectTypeOf(setState('bar')).toEqualTypeOf<Promise<URLSearchParams>>()\n  })\n  it('accepts options as a second argument', () => {\n    const [state, setState] = useQueryState('foo', {\n      history: 'push',\n      scroll: false,\n      shallow: true,\n      throttleMs: 100,\n      clearOnDefault: true\n    })\n    expectTypeOf(state).toEqualTypeOf<string | null>()\n    setState('bar')\n    setState(old => old?.toUpperCase() ?? null)\n    expectTypeOf(setState('bar')).toEqualTypeOf<Promise<URLSearchParams>>()\n  })\n  it('accepts a default value as second argument, making the state non-nullable', () => {\n    const [state] = useQueryState('foo', { defaultValue: 'bar' })\n    expectTypeOf(state).toEqualTypeOf<string>()\n  })\n  it('accepts parsers as a second argument', () => {\n    const [nullable] = useQueryState('foo', parseAsString)\n    const [nonNullable] = useQueryState('foo', parseAsString.withDefault('bar'))\n    expectTypeOf(nullable).toEqualTypeOf<string | null>()\n    expectTypeOf(nonNullable).toEqualTypeOf<string>()\n  })\n  it('accepts spreading in the default value', () => {\n    const [state] = useQueryState('foo', {\n      ...parseAsString,\n      defaultValue: 'bar'\n    })\n    expectTypeOf(state).toEqualTypeOf<string>()\n  })\n  it('accepts passing in a parse function', () => {\n    const [state] = useQueryState('foo', {\n      parse(query) {\n        expectTypeOf(query).toEqualTypeOf<string>()\n        return 42\n      }\n    })\n    expectTypeOf(state).toEqualTypeOf<number | null>()\n  })\n  it('accepts passing in a serialize function', () => {\n    const [state] = useQueryState('foo', {\n      parse: parseInt,\n      serialize(value) {\n        expectTypeOf(value).toEqualTypeOf<number>()\n        return '42'\n      }\n    })\n    expectTypeOf(state).toEqualTypeOf<number | null>()\n  })\n  it('accepts passing in an equality function', () => {\n    const [state] = useQueryState('foo', {\n      parse: parseInt,\n      eq(a, b) {\n        expectTypeOf(a).toEqualTypeOf<number>()\n        expectTypeOf(b).toEqualTypeOf<number>()\n        return a === b\n      }\n    })\n    expectTypeOf(state).toEqualTypeOf<number | null>()\n  })\n  it('allows setting null to clear the query', () => {\n    const [, set] = useQueryState('foo')\n    set(null)\n    set(old => {\n      expectTypeOf(old).toEqualTypeOf<string | null>()\n      return null\n    })\n  })\n  it('allows setting null to clear the query (with default value)', () => {\n    const [, set] = useQueryState('foo', { defaultValue: 'bar' })\n    set(null)\n    set(old => {\n      expectTypeOf(old).toEqualTypeOf<string>()\n      return null\n    })\n  })\n  it('strongly binds parse & serialize', () => {\n    useQueryState('foo', {\n      parse: (str: string) => str.length,\n      // @ts-expect-error\n      serialize: value => value.toUpperCase() // value is number\n    })\n  })\n  it('strongly binds parse & eq', () => {\n    useQueryState('foo', {\n      parse: parseInt,\n      // @ts-expect-error\n      eq: (a: number, b: number) => a.toUpperCase() === b.toUpperCase()\n    })\n  })\n  it(\"accepts a type parameter, but overloads require passing a parser if it's not a string\", () => {\n    // @ts-expect-error - missing parser\n    useQueryState<number>('foo')\n    // @ts-expect-error - mismatched types\n    useQueryState<number>('foo', parseAsString)\n  })\n  it(\"doesn't allow passing undefined as value\", () => {\n    const [, setFoo] = useQueryState('foo')\n    const [, setBar] = useQueryState('bar', parseAsString.withDefault('egg'))\n    // @ts-expect-error\n    setFoo(undefined)\n    // @ts-expect-error\n    setFoo(() => undefined)\n    // @ts-expect-error\n    setBar(undefined)\n    // @ts-expect-error\n    setBar(() => undefined)\n  })\n  it('accepts a limitUrlUpdates option', () => {\n    useQueryState('foo', {\n      limitUrlUpdates: {\n        method: 'throttle',\n        timeMs: 100\n      }\n    })\n    useQueryState('foo', {\n      limitUrlUpdates: {\n        method: 'debounce',\n        timeMs: 100\n      }\n    })\n    useQueryState('foo', { limitUrlUpdates: throttle(100) })\n    useQueryState('foo', { limitUrlUpdates: debounce(100) })\n    useQueryState('foo', { limitUrlUpdates: defaultRateLimit })\n    // Using parsers options (builder pattern)\n    parseAsString.withOptions({\n      limitUrlUpdates: {\n        method: 'throttle',\n        timeMs: 100\n      }\n    })\n    parseAsString.withOptions({\n      limitUrlUpdates: {\n        method: 'debounce',\n        timeMs: 100\n      }\n    })\n    parseAsString.withOptions({ limitUrlUpdates: throttle(100) })\n    parseAsString.withOptions({ limitUrlUpdates: debounce(100) })\n    parseAsString.withOptions({ limitUrlUpdates: defaultRateLimit })\n    // State updater function\n    const [, setState] = useQueryState('foo')\n    setState(null, {\n      limitUrlUpdates: {\n        method: 'throttle',\n        timeMs: 100\n      }\n    })\n    setState(null, {\n      limitUrlUpdates: {\n        method: 'debounce',\n        timeMs: 100\n      }\n    })\n    setState(null, { limitUrlUpdates: throttle(100) })\n    setState(null, { limitUrlUpdates: debounce(100) })\n    setState(null, { limitUrlUpdates: defaultRateLimit })\n  })\n  it('accepts defaultValue depending on parser when used with object syntax', () => {\n    const issueTypes = ['open', 'closed'] as const\n    const [issueType] = useQueryState('issueType', {\n      ...parseAsStringLiteral(issueTypes),\n      defaultValue: 'open'\n    })\n\n    expectTypeOf(issueType).toEqualTypeOf<'open' | 'closed'>()\n\n    useQueryState('issueType', {\n      ...parseAsStringLiteral(issueTypes),\n      // @ts-expect-error - defaultValue must be one of the issueTypes\n      defaultValue: 'thisiswrong'\n    })\n\n    // let's check if order matters (it shouldn't)\n    useQueryState('issueType', {\n      // @ts-expect-error - defaultValue must be one of the issueTypes\n      defaultValue: 'thisiswrong',\n      ...parseAsStringLiteral(issueTypes)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/tests/useQueryStates.test-d.ts",
    "content": "import { describe, expectTypeOf, it } from 'vitest'\nimport {\n  debounce,\n  defaultRateLimit,\n  parseAsInteger,\n  parseAsString,\n  throttle,\n  useQueryStates\n} from '../dist'\n\ndescribe('types/useQueryStates', () => {\n  const parsers = {\n    a: parseAsString,\n    b: parseAsInteger\n  }\n  it('has nullable state by default', () => {\n    const [state, setState] = useQueryStates(parsers)\n    expectTypeOf(state).toEqualTypeOf<{ a: string | null; b: number | null }>()\n    setState({ a: 'foo', b: 42 })\n    setState(old => {\n      expectTypeOf(old).toEqualTypeOf<{ a: string | null; b: number | null }>()\n      return { a: 'bar' }\n    })\n  })\n  it('allows partial updates', () => {\n    const [, setState] = useQueryStates(parsers)\n    setState({ a: 'foo' })\n    setState(() => ({ b: 42 }))\n  })\n  it('allows setting null to clear the query', () => {\n    const [, setState] = useQueryStates(parsers)\n    setState({ a: null }) // Clear an individual key\n    setState(null) // Clear all managed keys\n    setState(() => ({ a: null }))\n    setState(() => null)\n  })\n  it('allows setting to undefined to leave keys as-is', () => {\n    const [, setState] = useQueryStates(parsers)\n    setState({ a: undefined }) // No change\n    setState(() => ({ a: undefined })) // No change\n  })\n  it(\"doesn't allow setting undefined globally\", () => {\n    const [, setState] = useQueryStates(parsers)\n    // @ts-expect-error\n    setState(undefined)\n    // @ts-expect-error\n    setState(() => undefined)\n  })\n  it('makes state non-nullable when using default values', () => {\n    const [state, setState] = useQueryStates({\n      a: parseAsString.withDefault('foo'),\n      b: parseAsInteger.withDefault(42)\n    })\n    expectTypeOf(state).toEqualTypeOf<{ a: string; b: number }>()\n    setState({ a: 'bar', b: 42 })\n    setState({ a: null, b: null }) // Still allowed to clear it with null (state retuns to default)\n    setState(null)\n    setState(old => {\n      expectTypeOf(old).toEqualTypeOf<{ a: string; b: number }>()\n      return {}\n    })\n    setState(() => ({ a: null, b: null })) // Still allowed to clear it with null (state retuns to default)\n    setState(() => null)\n  })\n  it('supports inline custom parsers', () => {\n    const [state] = useQueryStates({\n      a: {\n        parse: parseInt,\n        serialize: value => value.toString()\n      },\n      b: {\n        parse: input => Uint8Array.from(input),\n        eq: (a: Uint8Array, b: Uint8Array) =>\n          a === b || (a.length === b.length && a.every((v, i) => v === b[i])),\n        defaultValue: Uint8Array.from('')\n      }\n    })\n    expectTypeOf(state).toEqualTypeOf<{\n      a: number | null\n      b: Uint8Array<ArrayBuffer>\n    }>()\n  })\n  it('supports urlKeys', () => {\n    const [state, setState] = useQueryStates(parsers, {\n      urlKeys: {\n        a: 'u',\n        b: 'v'\n      }\n    })\n    // State uses the original key names\n    expectTypeOf(state).toEqualTypeOf<{\n      a: string | null\n      b: number | null\n    }>()\n    setState({ a: 'baz', b: 42 })\n    useQueryStates(parsers, {\n      urlKeys: {\n        // @ts-expect-error\n        notInTheList: 'should-error'\n      }\n    })\n  })\n  it('accepts a limitUrlUpdates option', () => {\n    // Hook-level definition\n    const parsers = { foo: parseAsString }\n    useQueryStates(parsers, {\n      limitUrlUpdates: {\n        method: 'throttle',\n        timeMs: 100\n      }\n    })\n    useQueryStates(parsers, {\n      limitUrlUpdates: {\n        method: 'debounce',\n        timeMs: 100\n      }\n    })\n    useQueryStates(parsers, { limitUrlUpdates: throttle(100) })\n    useQueryStates(parsers, { limitUrlUpdates: debounce(100) })\n    useQueryStates(parsers, { limitUrlUpdates: defaultRateLimit })\n    // State updater function\n    const [, setState] = useQueryStates(parsers)\n    setState(null, {\n      limitUrlUpdates: {\n        method: 'throttle',\n        timeMs: 100\n      }\n    })\n    setState(null, {\n      limitUrlUpdates: {\n        method: 'debounce',\n        timeMs: 100\n      }\n    })\n    setState(null, { limitUrlUpdates: throttle(100) })\n    setState(null, { limitUrlUpdates: debounce(100) })\n    setState(null, { limitUrlUpdates: defaultRateLimit })\n  })\n})\n"
  },
  {
    "path": "packages/nuqs/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"src/**/*.test.ts\", \"src/**/*.test.tsx\"]\n}\n"
  },
  {
    "path": "packages/nuqs/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    \"jsx\": \"react\",\n    // Type checking\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"alwaysStrict\": false, // Don't emit \"use strict\" to avoid conflicts with \"use client\"\n    // Modules\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"resolveJsonModule\": true,\n    // Language & Environment\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"types\": [\"node\"],\n    // Emit\n    \"noEmit\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"verbatimModuleSyntax\": true,\n    \"isolatedDeclarations\": true,\n    \"moduleDetection\": \"force\",\n\n    \"downlevelIteration\": true,\n    // Interop\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    // Misc\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"incremental\": true,\n    \"tsBuildInfoFile\": \".tsbuildinfo\"\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "packages/nuqs/tsdown.config.ts",
    "content": "import { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { defineConfig, type UserConfig } from 'tsdown'\n\nconst commonConfig = {\n  clean: true,\n  format: ['esm'],\n  dts: true,\n  outDir: 'dist',\n  external: [\n    'next',\n    'react',\n    '@remix-run/react',\n    'react-router-dom',\n    'react-router',\n    '@tanstack/react-router'\n  ],\n  outExtensions() {\n    return {\n      js: '.js',\n      dts: '.d.ts'\n    }\n  },\n  treeshake: true,\n  tsconfig: 'tsconfig.build.json'\n} satisfies UserConfig\n\nconst entrypoints = {\n  client: {\n    index: 'src/index.ts',\n    'adapters/react': 'src/adapters/react.ts',\n    'adapters/next': 'src/adapters/next.ts',\n    'adapters/next/app': 'src/adapters/next/app.ts',\n    'adapters/next/pages': 'src/adapters/next/pages.ts',\n    'adapters/remix': 'src/adapters/remix.ts',\n    'adapters/react-router': 'src/adapters/react-router.ts',\n    'adapters/react-router/v6': 'src/adapters/react-router/v6.ts',\n    'adapters/react-router/v7': 'src/adapters/react-router/v7.ts',\n    'adapters/tanstack-router': 'src/adapters/tanstack-router.ts',\n    'adapters/custom': 'src/adapters/custom.ts',\n    'adapters/testing': 'src/adapters/testing.ts'\n  },\n  server: {\n    server: 'src/index.server.ts',\n    testing: 'src/testing.ts'\n  }\n}\n\nconst config: UserConfig = defineConfig([\n  // Client bundles\n  {\n    ...commonConfig,\n    entry: entrypoints.client,\n    outputOptions: {\n      intro: ({ isEntry, fileName }) =>\n        isEntry && !fileName.endsWith('.d.ts') ? \"'use client';\\n\" : ''\n    },\n    async onSuccess() {\n      // Mark the un-versionned React Router adapter as deprecated\n      // (will be removed in nuqs@3.0.0).\n      const filePath = resolve(\n        import.meta.dirname,\n        'dist',\n        'adapters',\n        'react-router.d.ts'\n      )\n      try {\n        const fileContents = await readFile(filePath, 'utf-8')\n        const updatedContents = fileContents.replace(\n          'export { NuqsAdapter, useOptimisticSearchParams };',\n          `export {\n  /**\n   * @deprecated This import will be removed in nuqs@3.0.0.\n   *\n   * Please pin your version of React Router in the import:\n   * - \\`nuqs/adapters/react-router/v6\\`\n   * - \\`nuqs/adapters/react-router/v7\\`.\n   *\n   * Note: this deprecated import (\\`nuqs/adapters/react-router\\`) is for React Router v6 only.\n   */\n  NuqsAdapter,\n  /**\n   * @deprecated This import will be removed in nuqs@3.0.0.\n   *\n   * Please pin your version of React Router in the import:\n   * - \\`nuqs/adapters/react-router/v6\\`\n   * - \\`nuqs/adapters/react-router/v7\\`.\n   *\n   * Note: this deprecated import (\\`nuqs/adapters/react-router\\`) is for React Router v6 only.\n   */\n  useOptimisticSearchParams\n};`\n        )\n        await writeFile(filePath, updatedContents, 'utf-8')\n      } catch (error) {\n        console.error('Error updating react-router barrel adapter:', error)\n        return\n      }\n    }\n  },\n  // Server bundle\n  {\n    ...commonConfig,\n    entry: entrypoints.server\n  }\n]) as UserConfig\n\nexport default config\n"
  },
  {
    "path": "packages/nuqs/turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"extends\": [\"//\"],\n  \"tasks\": {\n    \"build\": {\n      \"outputs\": [\"dist/**\", \".tsup/**\", \".tsbuildinfo\"]\n    },\n    \"build:size-json\": {\n      \"outputs\": [\"size.json\"],\n      \"dependsOn\": [\"build\"]\n    },\n    \"test\": {\n      \"outputs\": [\"coverage/**\"],\n      \"dependsOn\": [\"build\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/nuqs/vitest.browser.setup.ts",
    "content": "import { cleanup } from 'vitest-browser-react'\nimport { afterEach } from 'vitest'\n\n// https://testing-library.com/docs/react-testing-library/api/#cleanup\nafterEach(cleanup)\n"
  },
  {
    "path": "packages/nuqs/vitest.config.ts",
    "content": "import { playwright } from '@vitest/browser-playwright'\nimport { defineConfig, type ViteUserConfig } from 'vitest/config'\n\nconst config: ViteUserConfig = defineConfig({\n  define: {\n    /**\n     * We need to polyfill process.env because it is not meant to exist by default in a browser.\n     * @see https://github.com/vitest-dev/vitest/issues/6872\n     */\n    'process.env': JSON.stringify({})\n  },\n  test: {\n    setupFiles: ['vitest.setup.ts'],\n    exclude: ['node_modules/**'],\n    coverage: {\n      include: ['src/**/*.{ts,tsx}'],\n      exclude: ['src/adapters/**'] // Covered by e2e tests\n    },\n    env: {\n      IS_REACT_ACT_ENVIRONMENT: 'true'\n    },\n    server: {\n      deps: {\n        inline: ['vitest-package-exports']\n      }\n    },\n    projects: [\n      {\n        extends: true,\n        test: {\n          setupFiles: ['vitest.setup.ts', 'vitest.browser.setup.ts'],\n          name: 'browser',\n          include: ['**/*.browser.test.?(c|m)[jt]s?(x)'],\n          browser: {\n            enabled: true,\n            provider: playwright(),\n            instances: [{ browser: 'chromium' }]\n          }\n        }\n      },\n      {\n        // Tests that are meant to work in a non-browser environment.\n        extends: true,\n        test: {\n          environment: 'node',\n          name: 'unit',\n          include: ['**/*.test.?(c|m)[jt]s?(x)'],\n          exclude: ['**/*.browser.test.?(c|m)[jt]s?(x)']\n        }\n      },\n      {\n        extends: true,\n        test: {\n          name: 'types',\n          typecheck: {\n            enabled: true,\n            only: true\n          }\n        }\n      }\n    ]\n  }\n})\n\nexport default config\n"
  },
  {
    "path": "packages/nuqs/vitest.setup.ts",
    "content": "import fc, { VerbosityLevel } from 'fast-check'\n\nfc.configureGlobal({\n  numRuns: 1000,\n  verbose: process.env.CI ? VerbosityLevel.None : VerbosityLevel.VeryVerbose,\n  interruptAfterTimeLimit: 4000\n})\n"
  },
  {
    "path": "packages/res/package.json",
    "content": "{\n  \"name\": \"res\",\n  \"version\": \"0.0.0-internal\",\n  \"private\": true,\n  \"files\": [\n    \"./logo.light.svg\",\n    \"./logo.dark.svg\",\n    \"./wordmark.svg\",\n    \"./wordmark.light.svg\",\n    \"./wordmark.dark.svg\"\n  ]\n}\n"
  },
  {
    "path": "packages/scripts/next-release-analyser.ts",
    "content": "#!/usr/bin/env node\n\nimport MailPace from '@mailpace/mailpace.js'\nimport { createEnv } from '@t3-oss/env-core'\nimport minimist from 'minimist'\nimport { z } from 'zod'\n\nconst gaRegexp = /^\\d+\\.\\d+\\.\\d+$/\nconst canaryRegexp = /^(\\d+)\\.(\\d+)\\.(\\d+)-canary\\.(\\d+)$/\n\nconst env = createEnv({\n  server: {\n    CI: z.stringbool().optional(),\n    MAILPACE_API_TOKEN: z.string(),\n    EMAIL_ADDRESS_FROM: z.email(),\n    EMAIL_ADDRESS_TO: z.email()\n  },\n  isServer: true,\n  runtimeEnv: process.env\n})\n\nmain()\n\nconst fileSchema = z.object({\n  filename: z.string(),\n  patch: z.string().optional()\n})\n\ntype File = z.infer<typeof fileSchema>\n\nasync function main() {\n  const argv = minimist(process.argv.slice(2))\n  const thisVersion = argv.version\n  if (gaRegexp.test(thisVersion)) {\n    await sendGAEmail(thisVersion)\n    return\n  }\n\n  const previousVersion = getPreviousVersion(argv.version)\n\n  if (!previousVersion) {\n    console.log('No previous version to compare with')\n    process.exit(0)\n  }\n  const compareURL = `https://api.github.com/repos/vercel/next.js/compare/v${previousVersion}...v${thisVersion}`\n  const compare = await fetch(compareURL).then(res => res.json())\n  const files = z.array(fileSchema).parse(compare.files)\n  const relevantFiles = files.filter(file =>\n    [\n      'packages/next/src/client/components/app-router.tsx',\n      'packages/next/src/client/components/search-params.ts',\n      'packages/next/src/client/components/navigation.ts'\n    ].includes(file.filename)\n  )\n  if (relevantFiles.length === 0) {\n    console.log('No relevant changes')\n    process.exit(0)\n  }\n  await sendNotificationEmail(thisVersion, relevantFiles)\n}\n\n// --\n\nfunction getPreviousVersion(version: string) {\n  const match = canaryRegexp.exec(version)\n  if (!match || !match[4]) {\n    return null\n  }\n  const canary = parseInt(match[4])\n  if (canary === 0) {\n    return null\n  }\n  return `${match[1]}.${match[2]}.${match[3]}-canary.${canary - 1}`\n}\n\nasync function sendNotificationEmail(thisVersion: string, files: File[]) {\n  const client = new MailPace.DomainClient(env.MAILPACE_API_TOKEN)\n  const release = await fetch(\n    `https://api.github.com/repos/vercel/next.js/releases/tags/v${thisVersion}`\n  ).then(res => res.json())\n  const subject = `[nuqs] Next.js ${thisVersion} has relevant core changes`\n  const patchSection = files\n    .map(file => {\n      if (!file.patch) {\n        return `${file.filename}: no patch available`\n      }\n      return `${file.filename}:\n\\`\\`\\`diff\n${file.patch}\n\\`\\`\\`\n`\n    })\n    .join('\\n')\n\n  const body = `Release: ${release.html_url}\n\n${release.body}\n---\n\n${patchSection}\n`\n  console.info('Sending email:', subject)\n  console.info(body)\n  if (!env.CI) {\n    return\n  }\n  return client.sendEmail({\n    from: env.EMAIL_ADDRESS_FROM,\n    to: env.EMAIL_ADDRESS_TO,\n    subject,\n    textbody: body,\n    tags: ['nuqs']\n  })\n}\n\nfunction sendGAEmail(thisVersion: string) {\n  const client = new MailPace.DomainClient(env.MAILPACE_API_TOKEN)\n  const subject = `[nuqs] Next.js ${thisVersion} was published to GA`\n  const body = `https://github.com/vercel/next.js/releases/tag/v${thisVersion}`\n  console.info('Sending email:', subject)\n  console.info(body)\n  if (!env.CI) {\n    return\n  }\n  return client.sendEmail({\n    from: env.EMAIL_ADDRESS_FROM,\n    to: env.EMAIL_ADDRESS_TO,\n    subject,\n    textbody: body,\n    tags: ['nuqs']\n  })\n}\n"
  },
  {
    "path": "packages/scripts/package.json",
    "content": "{\n  \"name\": \"scripts\",\n  \"private\": true,\n  \"version\": \"0.0.0-internal\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"@mailpace/mailpace.js\": \"^0.1.3\",\n    \"@t3-oss/env-core\": \"^0.13.10\",\n    \"minimist\": \"^1.2.8\",\n    \"zod\": \"^4.3.6\"\n  },\n  \"devDependencies\": {\n    \"@types/minimist\": \"^1.2.5\",\n    \"@types/node\": \"^24.10.10\",\n    \"typescript\": \"^5.9.3\",\n    \"vitest\": \"catalog:vitest\"\n  }\n}\n"
  },
  {
    "path": "packages/scripts/release-notes-automation.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport {\n  collectContributors,\n  formatClosingIssues,\n  formatThanksSection,\n  formatTitle,\n  groupPRsByCategory,\n  splitCategoryTitle,\n  type PR\n} from './release-notes-automation'\n\n// Helper to create a minimal PR object for testing\nfunction createPR(\n  overrides: Partial<PR> & { number: number; title: string }\n): PR {\n  return {\n    author: null,\n    participants: { nodes: [] },\n    closingIssuesReferences: { edges: [] },\n    ...overrides\n  }\n}\n\ndescribe('splitCategoryTitle', () => {\n  it('parses feat: prefix as Features', () => {\n    const [category, title] = splitCategoryTitle('feat: add new feature')\n    expect(category).toBe('Features')\n    expect(title).toBe('add new feature')\n  })\n\n  it('parses feat(scope): prefix as Features', () => {\n    const [category, title] = splitCategoryTitle('feat(core): add new feature')\n    expect(category).toBe('Features')\n    expect(title).toBe('add new feature')\n  })\n\n  it('parses fix: prefix as Bug fixes', () => {\n    const [category, title] = splitCategoryTitle('fix: resolve issue')\n    expect(category).toBe('Bug fixes')\n    expect(title).toBe('resolve issue')\n  })\n\n  it('parses fix(scope): prefix as Bug fixes', () => {\n    const [category, title] = splitCategoryTitle('fix(parser): resolve issue')\n    expect(category).toBe('Bug fixes')\n    expect(title).toBe('resolve issue')\n  })\n\n  it('parses doc: prefix as Documentation', () => {\n    const [category, title] = splitCategoryTitle('doc: update readme')\n    expect(category).toBe('Documentation')\n    expect(title).toBe('update readme')\n  })\n\n  it('parses docs: prefix as Documentation', () => {\n    const [category, title] = splitCategoryTitle('docs: update readme')\n    expect(category).toBe('Documentation')\n    expect(title).toBe('update readme')\n  })\n\n  it('parses chore: prefix as Other changes', () => {\n    const [category, title] = splitCategoryTitle('chore: update deps')\n    expect(category).toBe('Other changes')\n    expect(title).toBe('update deps')\n  })\n\n  it('parses refactor: prefix as Other changes', () => {\n    const [category, title] = splitCategoryTitle('refactor: clean up code')\n    expect(category).toBe('Other changes')\n    expect(title).toBe('clean up code')\n  })\n\n  it('handles titles without conventional commit prefix', () => {\n    const [category, title] = splitCategoryTitle('Some random title')\n    expect(category).toBe('Other changes')\n    expect(title).toBe('Some random title')\n  })\n\n  it('is case insensitive for type prefix', () => {\n    const [category1] = splitCategoryTitle('FEAT: uppercase')\n    const [category2] = splitCategoryTitle('Feat: titlecase')\n    expect(category1).toBe('Features')\n    expect(category2).toBe('Features')\n  })\n})\n\ndescribe('formatTitle', () => {\n  it('converts single backtick code to <code> tag', () => {\n    expect(formatTitle('Add `useQueryState` hook')).toBe(\n      'Add <code>useQueryState</code> hook'\n    )\n  })\n\n  it('converts multiple backtick codes to <code> tags', () => {\n    expect(formatTitle('Replace `foo` with `bar`')).toBe(\n      'Replace <code>foo</code> with <code>bar</code>'\n    )\n  })\n\n  it('leaves titles without backticks unchanged', () => {\n    expect(formatTitle('Simple title without code')).toBe(\n      'Simple title without code'\n    )\n  })\n\n  it('handles empty title', () => {\n    expect(formatTitle('')).toBe('')\n  })\n\n  it('handles code at the start of title', () => {\n    expect(formatTitle('`nuqs` is great')).toBe('<code>nuqs</code> is great')\n  })\n\n  it('handles code at the end of title', () => {\n    expect(formatTitle('Check out `nuqs`')).toBe('Check out <code>nuqs</code>')\n  })\n})\n\ndescribe('formatClosingIssues', () => {\n  it('returns empty string for no issues', () => {\n    expect(formatClosingIssues([])).toBe('')\n  })\n\n  it('formats single issue', () => {\n    expect(formatClosingIssues([{ number: 123 }])).toBe(' (closes #123)')\n  })\n\n  it('formats multiple issues', () => {\n    expect(formatClosingIssues([{ number: 123 }, { number: 456 }])).toBe(\n      ' (closes #123, #456)'\n    )\n  })\n})\n\ndescribe('collectContributors', () => {\n  it('returns empty array when no PRs', () => {\n    expect(collectContributors([])).toEqual([])\n  })\n\n  it('excludes franky47 from contributors', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'test: should ignore @franky47',\n        participants: { nodes: [{ login: 'franky47' }] }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual([])\n  })\n\n  it('excludes bot accounts from contributors', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'chore: update deps',\n        participants: { nodes: [{ login: 'dependabot[bot]' }] }\n      }),\n      createPR({\n        number: 2,\n        title: 'chore: ci',\n        participants: { nodes: [{ login: 'github-actions[bot]' }] }\n      }),\n      createPR({\n        number: 3,\n        title: 'chore: renovate',\n        participants: { nodes: [{ login: 'renovate[bot]' }] }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual([])\n  })\n\n  it('collects external contributors', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'feat: test',\n        participants: { nodes: [{ login: 'contributor1' }] }\n      }),\n      createPR({\n        number: 2,\n        title: 'fix: bug',\n        participants: { nodes: [{ login: 'contributor2' }] }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual(['contributor1', 'contributor2'])\n  })\n\n  it('deduplicates contributors', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'feat: test',\n        participants: { nodes: [{ login: 'contributor1' }] }\n      }),\n      createPR({\n        number: 2,\n        title: 'fix: bug',\n        participants: { nodes: [{ login: 'contributor1' }] }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual(['contributor1'])\n  })\n\n  it('includes issue participants as contributors', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'fix: bug',\n        participants: { nodes: [{ login: 'franky47' }] },\n        closingIssuesReferences: {\n          edges: [\n            {\n              node: {\n                number: 100,\n                participants: { nodes: [{ login: 'issueReporter' }] }\n              }\n            }\n          ]\n        }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual(['issueReporter'])\n  })\n\n  it('includes PR discussion participants as contributors', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'feat: test',\n        author: { login: 'prAuthor' },\n        participants: {\n          nodes: [\n            { login: 'prAuthor' },\n            { login: 'commenter1' },\n            { login: 'commenter2' }\n          ]\n        }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual([\n      'commenter1',\n      'commenter2',\n      'prAuthor'\n    ])\n  })\n\n  it('deduplicates across PR author, PR participants, and issue participants', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'fix: bug',\n        author: { login: 'contributor1' },\n        participants: {\n          nodes: [{ login: 'contributor1' }, { login: 'contributor2' }]\n        },\n        closingIssuesReferences: {\n          edges: [\n            {\n              node: {\n                number: 100,\n                participants: {\n                  nodes: [{ login: 'contributor2' }, { login: 'contributor3' }]\n                }\n              }\n            }\n          ]\n        }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual([\n      'contributor1',\n      'contributor2',\n      'contributor3'\n    ])\n  })\n\n  it('sorts contributors alphabetically (case insensitive)', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'feat: test',\n        participants: { nodes: [{ login: 'Zara' }] }\n      }),\n      createPR({\n        number: 2,\n        title: 'fix: bug',\n        participants: { nodes: [{ login: 'alice' }] }\n      }),\n      createPR({\n        number: 3,\n        title: 'doc: readme',\n        participants: { nodes: [{ login: 'Bob' }] }\n      })\n    ]\n    expect(collectContributors(prs)).toEqual(['alice', 'Bob', 'Zara'])\n  })\n})\n\ndescribe('formatThanksSection', () => {\n  it('returns null for empty contributors', () => {\n    expect(formatThanksSection([])).toBeNull()\n  })\n\n  it('formats single contributor', () => {\n    expect(formatThanksSection(['alice'])).toBe(\n      'Huge thanks to @alice for helping!'\n    )\n  })\n\n  it('formats two contributors with \"and\"', () => {\n    expect(formatThanksSection(['alice', 'bob'])).toBe(\n      'Huge thanks to @alice and @bob for helping!'\n    )\n  })\n\n  it('formats three+ contributors with oxford comma', () => {\n    expect(formatThanksSection(['alice', 'bob', 'charlie'])).toBe(\n      'Huge thanks to @alice, @bob, and @charlie for helping!'\n    )\n  })\n})\n\ndescribe('groupPRsByCategory', () => {\n  it('groups PRs by their category', () => {\n    const prs: PR[] = [\n      createPR({ number: 1, title: 'feat: new feature' }),\n      createPR({ number: 2, title: 'fix: bug fix' }),\n      createPR({ number: 3, title: 'docs: update docs' }),\n      createPR({ number: 4, title: 'chore: maintenance' })\n    ]\n\n    const result = groupPRsByCategory(prs)\n\n    expect(result.Features).toHaveLength(1)\n    expect(result.Features[0]!.number).toBe(1)\n    expect(result.Features[0]!.title).toBe('new feature')\n\n    expect(result['Bug fixes']).toHaveLength(1)\n    expect(result['Bug fixes'][0]!.number).toBe(2)\n    expect(result['Bug fixes'][0]!.title).toBe('bug fix')\n\n    expect(result.Documentation).toHaveLength(1)\n    expect(result.Documentation[0]!.number).toBe(3)\n    expect(result.Documentation[0]!.title).toBe('update docs')\n    expect(result['Other changes']).toHaveLength(1)\n    expect(result['Other changes'][0]!.number).toBe(4)\n    expect(result['Other changes'][0]!.title).toBe('maintenance')\n  })\n\n  it('sorts PRs by number within each category', () => {\n    const prs: PR[] = [\n      createPR({ number: 30, title: 'feat: third' }),\n      createPR({ number: 10, title: 'feat: first' }),\n      createPR({ number: 20, title: 'feat: second' })\n    ]\n\n    const result = groupPRsByCategory(prs)\n\n    expect(result.Features.map(pr => pr.number)).toEqual([10, 20, 30])\n  })\n\n  it('includes author information', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'feat: test',\n        author: { login: 'testuser' }\n      })\n    ]\n\n    const result = groupPRsByCategory(prs)\n    expect(result.Features[0]!.author).toBe('testuser')\n  })\n\n  it('includes closing issues information', () => {\n    const prs: PR[] = [\n      createPR({\n        number: 1,\n        title: 'fix: resolve bug',\n        closingIssuesReferences: {\n          edges: [\n            { node: { number: 100, participants: { nodes: [] } } },\n            { node: { number: 101, participants: { nodes: [] } } }\n          ]\n        }\n      })\n    ]\n\n    const result = groupPRsByCategory(prs)\n    expect(result['Bug fixes'][0]!.closingIssues).toEqual([\n      { number: 100 },\n      { number: 101 }\n    ])\n  })\n})\n"
  },
  {
    "path": "packages/scripts/release-notes-automation.ts",
    "content": "#!/usr/bin/env node\n\nimport { z } from 'zod'\n\n// Schema for the GraphQL response\nconst participantsSchema = z.object({\n  nodes: z.array(z.object({ login: z.string() }))\n})\n\nconst issueReferenceSchema = z.object({\n  number: z.number(),\n  participants: participantsSchema\n})\n\nexport const prSchema = z.object({\n  number: z.number(),\n  title: z.string(),\n  author: z\n    .object({\n      login: z.string()\n    })\n    .nullable(),\n  participants: participantsSchema,\n  closingIssuesReferences: z.object({\n    edges: z.array(\n      z.object({\n        node: issueReferenceSchema\n      })\n    )\n  })\n})\n\nconst responseSchema = z.object({\n  data: z.object({\n    repository: z.object({\n      milestone: z\n        .object({\n          pullRequests: z.object({\n            nodes: z.array(prSchema)\n          })\n        })\n        .nullable()\n    })\n  })\n})\n\nexport type PR = z.infer<typeof prSchema>\n\nexport const CATEGORIES = [\n  'Features',\n  'Bug fixes',\n  'Documentation',\n  'Other changes'\n] as const\nexport type Category = (typeof CATEGORIES)[number]\n\nasync function fetchMilestonePRs(): Promise<PR[]> {\n  const token = process.env.GITHUB_TOKEN\n  if (!token) {\n    throw new Error('GITHUB_TOKEN environment variable is required')\n  }\n\n  // GraphQL query to fetch PRs with milestone ID 2 and their closing issues\n  const query = `\n    query {\n      repository(owner: \"47ng\", name: \"nuqs\") {\n        milestone(number: 2) {\n          pullRequests(first: 100) {\n            nodes {\n              number\n              title\n              author {\n                login\n              }\n              participants(first: 20) {\n                nodes {\n                  login\n                }\n              }\n              closingIssuesReferences(first: 10) {\n                edges {\n                  node {\n                    number\n                    participants(first: 20) {\n                      nodes {\n                        login\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  `.replace(/\\s+/g, ' ')\n\n  const response = await fetch(\n    'https://api.github.com/graphql?fn=fetchMilestonesPRs',\n    {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${token}`,\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({ query })\n    }\n  )\n\n  if (!response.ok) {\n    throw new Error(\n      `GitHub API error: ${response.status} ${response.statusText}`\n    )\n  }\n\n  const json = await response.json()\n  const parsed = responseSchema.parse(json)\n\n  if (!parsed.data.repository.milestone) {\n    throw new Error('Milestone not found')\n  }\n\n  return parsed.data.repository.milestone.pullRequests.nodes\n}\n\nexport function splitCategoryTitle(title: string): [Category, string] {\n  // Regex to match conventional commit prefix with optional scope\n  // Matches: feat:, feat(scope):, fix:, docs:, doc:, etc.\n  const conventionalCommitRegex = /^(\\w+)(?:\\([^)]+\\))?:\\s*(.+)$/\n  const match = title.match(conventionalCommitRegex)\n\n  let category: Category\n  let cleanTitle: string\n\n  if (match) {\n    const [, type = '', titleWithoutPrefix = ''] = match\n    cleanTitle = titleWithoutPrefix.trim()\n\n    const typeLower = type.toLowerCase()\n\n    if (typeLower === 'feat') {\n      category = 'Features'\n    } else if (typeLower === 'fix') {\n      category = 'Bug fixes'\n    } else if (typeLower === 'doc' || typeLower === 'docs') {\n      category = 'Documentation'\n    } else {\n      category = 'Other changes'\n    }\n  } else {\n    // No conventional commit prefix found\n    category = 'Other changes'\n    cleanTitle = title\n  }\n  return [category, cleanTitle] as const\n}\n\nexport type CategorizedPR = {\n  category: Category\n  number: number\n  title: string\n  author: string | null\n  closingIssues: Array<{ number: number }>\n}\n\nexport function groupPRsByCategory(\n  prs: PR[]\n): Record<Category, CategorizedPR[]> {\n  const categories: Record<Category, CategorizedPR[]> = {\n    Features: [],\n    'Bug fixes': [],\n    Documentation: [],\n    'Other changes': []\n  }\n\n  for (const pr of prs) {\n    const [category, cleanTitle] = splitCategoryTitle(pr.title)\n    const closingIssues = pr.closingIssuesReferences.edges.map(edge => ({\n      number: edge.node.number\n    }))\n    categories[category].push({\n      category,\n      number: pr.number,\n      title: cleanTitle,\n      author: pr.author?.login ?? null,\n      closingIssues\n    })\n  }\n\n  for (const category of CATEGORIES) {\n    categories[category].sort((a, b) => a.number - b.number)\n  }\n\n  return categories\n}\n\n// Known bot accounts to exclude\nconst botAccounts = new Set([\n  'copilot',\n  'dependabot',\n  'github-actions',\n  'pkg-pr-new',\n  'renovate',\n  'vercel'\n])\n\nfunction isBot(login: string) {\n  return login.endsWith('[bot]') || botAccounts.has(login.toLowerCase())\n}\n\nexport function collectContributors(prs: PR[]): string[] {\n  const contributors = new Set<string>()\n\n  for (const pr of prs) {\n    // Add all PR discussion participants (includes the PR author)\n    for (const { login } of pr.participants.nodes) {\n      if (!isBot(login)) {\n        contributors.add(login)\n      }\n    }\n\n    // Add participants of closing issues\n    for (const { node } of pr.closingIssuesReferences.edges) {\n      for (const { login } of node.participants.nodes) {\n        if (!isBot(login)) {\n          contributors.add(login)\n        }\n      }\n    }\n  }\n\n  // Remove myself from the list\n  contributors.delete('franky47')\n\n  return Array.from(contributors).sort((a, b) =>\n    a.toLowerCase().localeCompare(b.toLowerCase())\n  )\n}\n\nexport function formatClosingIssues(\n  issues: CategorizedPR['closingIssues']\n): string {\n  if (issues.length === 0) return ''\n  const issueNumbers = issues.map(i => `#${i.number}`).join(', ')\n  return ` (closes ${issueNumbers})`\n}\n\nexport function formatTitle(title: string): string {\n  // Convert backtick code blocks to <code> tags for better rendering in GitHub release notes\n  return title.replace(/`([^`]+)`/g, '<code>$1</code>')\n}\n\nexport function formatThanksSection(contributors: string[]): string | null {\n  if (contributors.length === 0) {\n    return null\n  }\n  // Such travesty will not go unpunished! 🇬🇧\n  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/ListFormat#oxford_comma\n  const oxfordComma = new Intl.ListFormat('en-US', { type: 'conjunction' })\n  const allContributors = oxfordComma.format(contributors.map(c => `@${c}`))\n  return `Huge thanks to ${allContributors} for helping!`\n}\n\n// Main execution\nasync function main() {\n  try {\n    const prs = await fetchMilestonePRs()\n\n    // Group by category\n    const categories = groupPRsByCategory(prs)\n\n    // Display results\n    for (const category of CATEGORIES) {\n      const prsInCategory = categories[category]\n\n      if (prsInCategory.length === 0) {\n        continue // Skip empty categories\n      }\n\n      console.log(`## ${category}\\n`)\n\n      for (const pr of prsInCategory) {\n        const author = pr.author ? `, by @${pr.author}` : ''\n        const closingIssues = formatClosingIssues(pr.closingIssues)\n        console.log(\n          `- #${pr.number} - ${formatTitle(pr.title)}${author}${closingIssues}`\n        )\n      }\n\n      console.log() // Empty line between categories\n    }\n\n    // Collect and display contributors\n    const contributors = collectContributors(prs)\n    const thanksSection = formatThanksSection(contributors)\n    if (thanksSection) {\n      console.log('## Thanks\\n')\n      console.log(`${thanksSection}\\n`)\n    }\n  } catch (error) {\n    console.error('Error:', error)\n    process.exit(1)\n  }\n}\n\n// Only run main when executed directly (not when imported)\nconst isMainModule =\n  import.meta.url === `file://${process.argv[1]}` ||\n  process.argv[1]?.endsWith('release-notes-automation.ts')\n\nif (isMainModule) {\n  main()\n}\n"
  },
  {
    "path": "packages/scripts/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    // Type checking\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"alwaysStrict\": false, // Don't emit \"use strict\" to avoid conflicts with \"use client\"\n    // Modules\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"resolveJsonModule\": true,\n    // Language & Environment\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    // Emit\n    \"noEmit\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleDetection\": \"force\",\n\n    \"downlevelIteration\": true,\n    // Interop\n    \"allowJs\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    // Misc\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n    \"incremental\": true,\n    \"tsBuildInfoFile\": \".tsbuildinfo\"\n  },\n  \"include\": [\"./**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - packages/**\n\ncatalogs:\n  e2e:\n    playwright: ^1.58.1\n    '@playwright/test': ^1.58.1\n\n  vitest:\n    vitest: ^4.1.0\n    '@vitest/browser-playwright': ^4.1.0\n    '@vitest/coverage-v8': ^4.1.0\n\n  vite:\n    vite: ^8.0.0\n\n  next:\n    next: 16.2.1-canary.3\n\n  react19:\n    '@types/react': ^19.2.10\n    '@types/react-dom': ^19.2.3\n    react: ^19.2.4\n    react-dom: ^19.2.4\n\nenablePrePostScripts: true\n\nonlyBuiltDependencies:\n  - '@sentry/cli'\n  - '@swc/core'\n  - '@tailwindcss/oxide'\n  - playwright\n  - esbuild\n  - sharp\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.v2.json\",\n  \"concurrency\": \"11\",\n  \"tasks\": {\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"build\": {\n      \"dependsOn\": [\"^build\"]\n    },\n    \"test\": {}\n  }\n}\n"
  }
]