You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .agents/skills/react-query-best-practices/SKILL.md
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,6 +37,10 @@ Read these before analyzing:
37
37
- Use `enabled` to prevent queries from running without required params
38
38
- Warm data for hover/focus intent with `queryClient.prefetchQuery` and shared `queryOptions`; never temporarily enable a mounted hidden observer, which can remain active after focus restoration and refetch data for closed UI
39
39
- When gating a query by view or modal state, move every consumer to the active query too: imperative refresh/pagination, loading and error feedback, and data-derived controls must never read a disabled query or placeholder data from a previous key
40
+
- Compose caller-controlled `enabled` options with required-param guards (`Boolean(id) && (options?.enabled ?? true)`). Never spread options after an internal guard, because `{ enabled: true }` can silently re-enable an invalid request.
41
+
- A disabled query can still report `isPending: true`. Aggregate loading state only for queries that are applicable/enabled, or an optional query can hold the whole surface in a permanent loading state.
42
+
- Deferred authorization or policy queries must fail closed. Do not give pending/error data the same fallback as a successfully loaded unrestricted policy; disable guarded actions until the policy query succeeds.
43
+
- Server prefetches must call the authorized use case, apply the route presenter/response schema, and reuse the client's exact key, mapper, and stale time. Keep all fallible auth/read/parse work inside `queryFn` so an optional warm cannot fail the page, and never bypass a route that redacts fields.
40
44
41
45
### Mutations
42
46
- Use `onSettled` (not `onSuccess`) for cache reconciliation — it fires on both success and error
@@ -48,7 +52,7 @@ Read these before analyzing:
48
52
- Never copy query data into useState. Use query data directly in components.
49
53
- Never copy query data into Zustand stores (exception: mutation callbacks that coordinate cross-store state like temp ID replacement)
50
54
- The query cache is not a local state manager — `setQueryData` is for optimistic updates only
51
-
- Forms are the one deliberate exception: copy server data into local form state with `staleTime: Infinity`
55
+
- Forms are the one deliberate exception: once query data exists, initialize a keyed form subtree from it with lazy state initializers. Do not synchronize query data into draft state with an Effect; key the form by resource identity so switching resources resets every draft/modal/upload field together. Keep independent queries in the outer wrapper so they still start in parallel.
Copy file name to clipboardExpand all lines: .agents/skills/you-might-not-need-an-effect/SKILL.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,3 +16,7 @@ Steps:
16
16
1. Read https://react.dev/learn/you-might-not-need-an-effect to understand the guidelines
17
17
2. Analyze the specified scope for useEffect anti-patterns
18
18
3. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
19
+
20
+
## Query-backed forms
21
+
22
+
When query data supplies the initial values for an editable form, do not copy it into draft state in an Effect. Render loading chrome in an outer component, then mount a keyed form child once data exists and initialize its state lazily from props. Key by the resource identity so every related draft, dialog, and upload state resets together when the resource changes. Keep independent queries in the outer component to preserve parallel fetching.
Platform-only entries (desktop **Browser** and **Terminal**) trail the shared set rather than interleaving, so the common prefix is identical on every platform.
28
28
29
-
## Grouping: one rule, against the consequential group
29
+
## Grouping: a rule marks a change in what the action acts on
30
30
31
-
Order is governed above. **Separators are governed here** — and the answer is: use at most one.
31
+
Order is governed above. **Separators are governed here.**
32
32
33
-
Put a single `DropdownMenuSeparator` against the **consequential group** — the actions that
34
-
delete, detach, or change a run — and nowhere else. Everything on the other side of it runs
35
-
uninterrupted in toolbar-mirroring order.
33
+
A `DropdownMenuSeparator` earns its place when the next group stops acting on the thing the user
34
+
clicked. That is the whole test — one question, asked the same way in every menu:
36
35
37
-
That group trails in almost every menu, so in practice the rule reads "one rule immediately
38
-
before Delete / Leave / Close / Hide". It leads in exactly one place: the **logs row menu**,
39
-
where `Retry` and `Cancel Run` are the primary actions on a failed run and sit at the top, with
40
-
the rule beneath them. Ordering follows the surface (see "The rule" above); the separator simply
41
-
fences whichever end the consequential group occupies. A menu whose consequential actions are
42
-
merely *disabled* still gets no extra rule — `disabled` is not a group.
36
+
| The group | Gets a rule before it |
37
+
| --- | --- |
38
+
| Acts on the clicked item (open, rename, duplicate, export, copy, edit, pin, run) | no — this is the body of the menu |
39
+
| Acts on **something else** — the page's filters or view, or a newly created sibling | yes |
40
+
|**Destroys or detaches** it (delete, leave, close, hide, remove) | yes |
41
+
42
+
Most row menus only ever have the one transition, so they carry one rule, immediately before
43
+
`Delete`. A menu that also filters the page or inserts siblings carries two. Nothing carries
44
+
more, because there is no third thing a menu acts on.
45
+
46
+
Do **not** band by verb. "Navigation", "status", "edit", "copy" are categories of *what the verb
47
+
is*, not of *what it touches*, and the user meets no such taxonomy anywhere else — every toolbar
48
+
in the app is a flat `gap-1` chip row with no dividers. Menus banded that way put the same action
49
+
in different groups depending on which siblings happened to be visible.
50
+
51
+
The consequential group trails in almost every menu. It leads in exactly one: the **logs row
52
+
menu**, where `Retry` and `Cancel Run` act on the run itself and are the primary actions on a
53
+
failure, so they sit on top with the rule beneath them. Ordering follows the surface (see "The
54
+
rule" above); the separator fences whichever end that group occupies.
55
+
56
+
A group whose items are merely *disabled* still gets no extra rule — `disabled` is not a group.
43
57
44
58
```tsx
45
59
// ✗ Bad — four semantic bands the user meets nowhere else
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -388,7 +388,7 @@ A list orders itself the way the user already reads the same things somewhere el
388
388
389
389
Encode the order in ONE exported constant and sort by it — never a hand-maintained literal per menu (`RESOURCE_MENU_ORDER` / `byResourceMenuOrder` in `home/components/mothership-view/components/resource-registry`). Render mixed item kinds in a single ordered pass; emitting all submenu-backed families and then all flat ones silently pins every submenu to the top no matter what the constant says. Divergence is allowed only for search ranking, user-controlled ordering, and recency.
390
390
391
-
**Grouping**: at most ONE `DropdownMenuSeparator`per menu, fencing the consequential group — immediately before Delete/Leave/Close/Hide in almost every menu, and immediately after Retry/Cancel Run in the logs row menu, where those lead. No toolbar in the app renders a divider, so multi-band menus teach a taxonomy that exists on no other surface. Build each separator's guard from the EXACT render conditions of the items on both sides — a looser guard is what leaves a dangling rule when its group is conditional. Never add a prop to move a rule. Full rule in `.claude/rules/sim-list-ordering.md`.
391
+
**Grouping**: a `DropdownMenuSeparator`marks a change in WHAT the action acts on — the clicked item (no rule), something else like the page's filters or a new sibling (rule), or destroying it (rule). Most row menus have only the destructive transition and carry one rule before Delete/Leave/Close/Hide; menus that also filter the page or insert siblings carry two. Never band by verb (navigation/status/edit/copy) — the toolbars are flat, so that taxonomy exists nowhere else. No toolbar in the app renders a divider, so multi-band menus teach a taxonomy that exists on no other surface. Build each separator's guard from the EXACT render conditions of the items on both sides — a looser guard is what leaves a dangling rule when its group is conditional. Never add a prop to move a rule. Full rule in `.claude/rules/sim-list-ordering.md`.
0 commit comments