d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
3.9 KiB
3.9 KiB
description: Analyze and fix URL/query-param state anti-patterns — manual useSearchParams reads, hand-built query mutations, view-state trapped in useState, and objects in the URL argument-hint: [scope] [fix=true|false]
You Might Not Need URL State
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "app/workspace/[workspaceId]/tables/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
Context
Shareable client view-state (active tab/panel, filters, search query, sort, pagination, selected-entity id, an open "view" modal/drawer that is a destination) lives in the URL via nuqs — driven by a co-located search-params.ts, never read via useSearchParams().get(...) and never mutated by hand-built query strings. Remote data stays in React Query; high-frequency / large / ephemeral / socket-synced state stays in Zustand; purely local UI stays in useState.
.claude/rules/sim-url-state.md is the source of truth — read it first.
References
Read these before analyzing:
.claude/rules/sim-url-state.md— the decision framework, conventions, debounced-input pattern, sort convention, selected-entity deep-link pattern, and the workflow-editor carve-out- https://nuqs.dev/docs/parsers — parsers (
parseAsString/parseAsInteger/parseAsBoolean/parseAsStringLiteral/parseAsArrayOf/createParser) - https://nuqs.dev/docs/options —
withDefault,history,shallow,clearOnDefault - https://nuqs.dev/docs/server-side —
createSearchParamsCachefor server reads
Anti-patterns to detect
- Manual param reads for state:
useSearchParams().get(...)ornew URLSearchParams(window.location.search)used to read view-state. Replace withuseQueryState/useQueryStatesbound to asearch-params.ts. (Read-once auth/invite/redirect tokens —token,callbackUrl,redirect,error,invite_flow,code— are NOT view-state; leave them onuseSearchParams.) - Hand-built query mutation: constructing a query string +
router.replace/router.pushto change a param on the current path. Use a nuqs setter. (Arouter.pushthat changes the route path is fine; an outboundnew URLSearchParamsbuilding anhref/window.open/download/API URL is fine.) window.history.replaceState/pushStateto mutate a param.- URL state duplicated into a store/useState + synced with an effect (or a
popstatelistener). The URL is the single source of truth; derive from it, don't mirror it. - Objects in the URL: serializing a
TableDefinition/SkillDefinition/etc. Store the id and derive the object from the loaded list (items.find(i => i.id === id)). - High-frequency / large state in the URL: cursor, pan/zoom, un-debounced keystrokes, big JSON blobs. Debounce text search (local
useStatemirror + reconcile effect); keep canvas/presence/resize state in Zustand. - Shareable view-state trapped in
useState: a tab/filter/sort/pagination/selected-entity that should be a link but lives in local state. Migrate it to the URL. - Missing Suspense boundary: a component newly calling
useQueryState/useQueryStateswhose page entry has no<Suspense>wrapper (Next.js requires it foruseSearchParams). Add one with a real-chrome fallback. import { z }for param validation in client code: use nuqs parsers instead.
Steps
- Read
.claude/rules/sim-url-state.mdand the nuqs docs above to understand the guidelines - Analyze the specified scope for the anti-patterns listed above
- For each finding, decide the correct home using the decision table — do not force URL state onto ephemeral/high-frequency/socket-synced state
- If fix=true, apply the fixes (co-locate a
search-params.ts, wireuseQueryState(s), add the Suspense boundary, delete the replaced state + sync effects). If fix=false, propose the fixes without applying.