Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .claude/commands/cleanup.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Run all code quality skills in sequence — effects, memo, callbacks, state, React Query, and emcn design review
description: Run all code quality skills in sequence — effects, memo, callbacks, state, React Query, emcn design review, and url-state
argument-hint: [scope] [fix=true|false]
---

Expand All @@ -21,5 +21,6 @@ Run each of these skills in order on the specified scope, passing through the sc
4. `/you-might-not-need-state $ARGUMENTS`
5. `/react-query-best-practices $ARGUMENTS`
6. `/emcn-design-review $ARGUMENTS`
7. `/you-might-not-need-url-state $ARGUMENTS`

After all skills have run, output a summary of what was found and fixed (or proposed) across all six passes.
After all skills have run, output a summary of what was found and fixed (or proposed) across all seven passes.
45 changes: 45 additions & 0 deletions .claude/commands/you-might-not-need-url-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
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`](https://nuqs.dev) — 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:
1. `.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
2. https://nuqs.dev/docs/parsers — parsers (`parseAsString`/`parseAsInteger`/`parseAsBoolean`/`parseAsStringLiteral`/`parseAsArrayOf`/`createParser`)
3. https://nuqs.dev/docs/options — `withDefault`, `history`, `shallow`, `clearOnDefault`
4. https://nuqs.dev/docs/server-side — `createSearchParamsCache` for server reads

## Anti-patterns to detect

1. **Manual param reads for state**: `useSearchParams().get(...)` or `new URLSearchParams(window.location.search)` used to *read* view-state. Replace with `useQueryState`/`useQueryStates` bound to a `search-params.ts`. (Read-once auth/invite/redirect tokens — `token`, `callbackUrl`, `redirect`, `error`, `invite_flow`, `code` — are NOT view-state; leave them on `useSearchParams`.)
2. **Hand-built query mutation**: constructing a query string + `router.replace`/`router.push` to change a param on the current path. Use a nuqs setter. (A `router.push` that changes the route *path* is fine; an outbound `new URLSearchParams` building an `href`/`window.open`/download/API URL is fine.)
3. **`window.history.replaceState`/`pushState`** to mutate a param.
4. **URL state duplicated into a store/useState + synced with an effect** (or a `popstate` listener). The URL is the single source of truth; derive from it, don't mirror it.
5. **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)`).
6. **High-frequency / large state in the URL**: cursor, pan/zoom, un-debounced keystrokes, big JSON blobs. Debounce text search (local `useState` mirror + reconcile effect); keep canvas/presence/resize state in Zustand.
7. **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.
8. **Missing Suspense boundary**: a component newly calling `useQueryState`/`useQueryStates` whose page entry has no `<Suspense>` wrapper (Next.js requires it for `useSearchParams`). Add one with a real-chrome fallback.
9. **`import { z }` for param validation in client code**: use nuqs parsers instead.

## Steps

1. Read `.claude/rules/sim-url-state.md` and the nuqs docs above to understand the guidelines
2. Analyze the specified scope for the anti-patterns listed above
3. For each finding, decide the correct home using the decision table — do not force URL state onto ephemeral/high-frequency/socket-synced state
4. If fix=true, apply the fixes (co-locate a `search-params.ts`, wire `useQueryState(s)`, add the Suspense boundary, delete the replaced state + sync effects). If fix=false, propose the fixes without applying.
2 changes: 2 additions & 0 deletions .claude/rules/sim-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ paths:

All React Query hooks live in `hooks/queries/`. All server state must go through React Query — never use `useState` + `fetch` in components for data fetching or mutations.

For *client* view-state that belongs in a shareable link (tabs, filters, search, pagination, selected entity id), use URL query params via nuqs — see `.claude/rules/sim-url-state.md`. React Query owns remote data; nuqs owns shareable client view-state.

## Query Key Factory

Every query file defines a hierarchical keys factory with an `all` root key and intermediate plural keys for prefix-level invalidation:
Expand Down
Loading
Loading