@jgamaraalv/ts-dev-kit 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/CHANGELOG.md +52 -0
  4. package/README.md +106 -50
  5. package/package.json +2 -2
  6. package/skills/codebase-adapter/SKILL.md +237 -0
  7. package/skills/codebase-adapter/template.md +25 -0
  8. package/skills/conventional-commits/SKILL.md +12 -0
  9. package/skills/core-web-vitals/SKILL.md +102 -0
  10. package/skills/core-web-vitals/references/cls.md +154 -0
  11. package/skills/core-web-vitals/references/inp.md +140 -0
  12. package/skills/core-web-vitals/references/lcp.md +89 -0
  13. package/skills/core-web-vitals/references/tools.md +112 -0
  14. package/skills/core-web-vitals/scripts/visualize.py +222 -0
  15. package/skills/debug/SKILL.md +10 -27
  16. package/skills/debug/template.md +23 -0
  17. package/skills/{task → execute-task}/SKILL.md +78 -50
  18. package/skills/generate-prd/SKILL.md +56 -0
  19. package/skills/generate-prd/template.md +69 -0
  20. package/skills/generate-task/SKILL.md +136 -0
  21. package/skills/generate-task/template.md +71 -0
  22. package/skills/owasp-security-review/SKILL.md +1 -10
  23. package/skills/owasp-security-review/template.md +6 -0
  24. package/skills/tanstack-query/SKILL.md +348 -0
  25. package/skills/tanstack-query/references/advanced-patterns.md +376 -0
  26. package/skills/tanstack-query/references/api-reference.md +297 -0
  27. package/skills/tanstack-query/references/ssr-nextjs.md +272 -0
  28. package/skills/tanstack-query/references/testing.md +175 -0
  29. package/skills/ui-ux-guidelines/SKILL.md +1 -17
  30. package/skills/ui-ux-guidelines/template.md +15 -0
  31. /package/skills/{task → execute-task}/references/agent-dispatch.md +0 -0
  32. /package/skills/{task → execute-task}/references/verification-protocol.md +0 -0
  33. /package/skills/{task/references/output-templates.md → execute-task/template.md} +0 -0
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: generate-task
3
+ description: "Breaks a PRD into ordered, production-ready engineering tasks ready for execution by /execute-task. Use when: (1) converting a PRD document into executable engineering tasks, (2) planning feature delivery as a sequence of mergeable, self-contained pull requests, (3) the user says 'generate tasks', 'break down this PRD', 'create tasks from PRD', 'plan the implementation tasks', or 'task breakdown'. Each generated task document embeds its own success criteria, baseline checks, post-change tests, performance benchmarks, and non-functional requirements — making it directly executable by /execute-task."
4
+ argument-hint: "[prd-file-path | task-without-context | epic-task | big-task]"
5
+ ---
6
+
7
+ <prd>
8
+ $ARGUMENTS
9
+ </prd>
10
+
11
+ <workflow>
12
+ Follow each phase in order.
13
+
14
+ <phase_1_read_prd>
15
+ Read the PRD document fully. Extract and organize:
16
+ - Functional requirements — numbered, atomic conditions
17
+ - Acceptance criteria — how feature completion is verified
18
+ - Non-functional requirements — performance, security, accessibility, scalability targets
19
+ - Scope — what is included and what is explicitly excluded
20
+ - User journeys — key flows and their steps
21
+ - Success metrics and KPIs
22
+ </phase_1_read_prd>
23
+
24
+ <phase_2_analyze_codebase>
25
+ Before decomposing tasks, understand the target project:
26
+ 1. Read CLAUDE.md and root package.json — project structure, package manager, tech stack, key directories.
27
+ 2. Identify where implementation units live — backend routes, frontend pages, shared types, database schemas, tests.
28
+ 3. Search for patterns similar to the feature being built — use Grep/Glob to find related files and establish co-location conventions.
29
+ 4. List the domain areas the feature touches — database, API, shared, frontend, tests, config.
30
+ </phase_2_analyze_codebase>
31
+
32
+ <phase_3_identify_implementation_units>
33
+ Map every PRD requirement to concrete implementation units. An implementation unit is any atomic change: a new schema, a route, a component, a migration, a shared type, a test file, a config entry, an i18n key.
34
+
35
+ For each unit, identify:
36
+ - **File path** (exact, following codebase conventions)
37
+ - **Action**: create or modify
38
+ - **Domain**: database | api | shared | frontend | test | config | docs
39
+ - **Dependencies**: which other units must exist first
40
+
41
+ **Standard dependency ordering** (lower layers before higher):
42
+ 1. Shared types, constants, i18n keys, env variables
43
+ 2. Database migrations and schema updates
44
+ 3. API routes, handlers, validation schemas
45
+ 4. Shared hooks, utilities, helper functions
46
+ 5. UI components (atoms → molecules → organisms)
47
+ 6. Pages and routes composing components
48
+ 7. Tests (unit, integration, E2E)
49
+ 8. Config and infrastructure changes
50
+ 9. Documentation updates
51
+
52
+ **Orphan-free rule** — every consumer of a resource must be in the same task as its producer OR in a later task that explicitly depends on the producer's task:
53
+ - New i18n key + every component using that key → same task (or key in TASK_N, component in TASK_M where M > N and TASK_M depends on TASK_N)
54
+ - New database column + migration that adds it → same task
55
+ - New shared type + every immediate consumer → same task
56
+ - New component + the page that renders it → same task (unless page is intentionally deferred to a later task)
57
+ </phase_3_identify_implementation_units>
58
+
59
+ <phase_4_group_into_tasks>
60
+ Group implementation units into tasks. Apply these rules in order:
61
+
62
+ **Rule 1 — 30-file limit**: a task may create or modify at most 30 files. If a natural group exceeds this, split on domain boundaries (data layer, API layer, UI layer, test layer).
63
+
64
+ **Rule 2 — Production-ready delivery**: every task, when merged in order, must leave the application in a runnable state — no broken imports, unresolved references, orphaned i18n keys, or missing migrations.
65
+
66
+ **Rule 3 — Forward dependency only**: if TASK_N requires output from TASK_M, then M < N. No task may depend on a later task.
67
+
68
+ **Rule 4 — Mergeable without breaking**: use feature flags, graceful degradation, or empty-state handling so earlier tasks don't expose incomplete UX to end users.
69
+
70
+ **Rule 5 — Clear value delivery**: each task must deliver a demonstrable increment — a working endpoint, a rendered component, a passing test suite. Avoid tasks with no visible or testable outcome.
71
+
72
+ **Recommended grouping** (adapt per feature):
73
+ 1. **Foundation** — shared types, constants, i18n keys, env variables
74
+ 2. **Data layer** — database schema, migrations, ORM models
75
+ 3. **API layer** — routes, handlers, validation schemas, error codes
76
+ 4. **Core UI** — reusable components, hooks, state management
77
+ 5. **Feature pages** — pages and routes composing the core UI
78
+ 6. **Tests & polish** — comprehensive test suites, accessibility audit, performance tuning
79
+ 7. **Documentation** — CLAUDE.md updates, API docs, migration guides
80
+
81
+ Split tasks at domain boundaries when a group would exceed 30 files.
82
+ </phase_4_group_into_tasks>
83
+
84
+ <phase_5_define_verification_criteria>
85
+ For each task, derive its verification criteria from the PRD. These become binding requirements embedded in the task document and executed by /execute-task.
86
+
87
+ **Success criteria** — select PRD acceptance criteria that apply to this task's scope. Write them as testable assertions:
88
+ - "POST /api/resource returns 201 with the created resource payload" (not "API works")
89
+ - "Page renders the empty state at 1440px without console errors" (not "page looks right")
90
+ - "Migration runs cleanly on an empty database" (not "migration works")
91
+
92
+ **Baseline checks** — what to capture BEFORE making changes:
93
+ - Standard quality gates: tsc, lint, test, build (pass/fail and counts)
94
+ - Domain-specific: API endpoints (HTTP status + timing), pages (screenshot + LCP), schema state (table columns and types)
95
+
96
+ **Post-change checks** — what to verify AFTER changes, mapped 1:1 to each success criterion.
97
+
98
+ **Performance benchmarks** — from PRD NFRs or domain defaults:
99
+ - API endpoints: p95 response time target
100
+ - Frontend pages: LCP target, bundle size delta
101
+ - Database queries: execution time target
102
+
103
+ **Non-functional requirements** — scope PRD NFRs to this task's domain:
104
+ - Database task → data integrity, migration rollback safety, index strategy
105
+ - API task → input validation coverage, auth guard presence, rate limiting
106
+ - Frontend task → WCAG compliance level, responsive breakpoints, keyboard navigation
107
+ </phase_5_define_verification_criteria>
108
+
109
+ <phase_6_generate_task_documents>
110
+ Generate a document for each task using the template from [template.md](template.md).
111
+
112
+ Before saving, validate each document:
113
+ - [ ] File count ≤ 30
114
+ - [ ] No file path appears in more than one task
115
+ - [ ] Every success criterion is testable (specific, measurable outcome)
116
+ - [ ] Every "create" file has its consumer in the same or a later task
117
+ - [ ] Task N's dependencies all have numbers < N
118
+ - [ ] Baseline checks include at minimum: tsc, lint, test, build
119
+
120
+ Fix any violation before saving.
121
+ </phase_6_generate_task_documents>
122
+ </workflow>
123
+
124
+ <output>
125
+ Save each task document to:
126
+ ```
127
+ [project-root]/docs/features/[FEATURE_NAME]/TASK_[TASK_NUMBER].md
128
+ ```
129
+
130
+ After saving all tasks, print a summary table:
131
+
132
+ | Task | Title | Files | Depends on | Key deliverable |
133
+ |------|-------|-------|------------|-----------------|
134
+ | TASK_01 | ... | N files | none | ... |
135
+ | TASK_02 | ... | N files | TASK_01 | ... |
136
+ </output>
@@ -0,0 +1,71 @@
1
+ # TASK_[N]: [Title]
2
+
3
+ ## Overview
4
+ [2-3 sentences describing what this task delivers and why it is a cohesive unit
5
+ of work. State the concrete value delivered when this task is merged.]
6
+
7
+ ## Dependencies
8
+ - Requires merged: [list of TASK_X titles, or "none"]
9
+
10
+ ## Scope — Files (max 30)
11
+
12
+ | File | Action | Purpose |
13
+ |------|--------|---------|
14
+ | `path/to/file.ts` | create | What this file contains |
15
+ | `path/to/other.ts` | modify | What changes and why |
16
+
17
+ **Total: N files**
18
+
19
+ ## Success Criteria
20
+
21
+ - [ ] 1. [Testable, specific condition — maps to a PRD acceptance criterion]
22
+ - [ ] 2. [...]
23
+
24
+ ## Non-functional Requirements
25
+
26
+ - **Performance**: [specific targets, e.g., "p95 API response < 200ms", "LCP < 2.5s"]
27
+ - **Security**: [e.g., "all inputs validated with zod schema", "auth guard on all routes"]
28
+ - **Accessibility**: [for frontend tasks: WCAG level and specific requirements]
29
+ - **Scalability**: [relevant requirements, or "n/a for this task"]
30
+
31
+ ## Verification Plan
32
+
33
+ ### Baseline Checks (run before making changes)
34
+
35
+ **Quality gates:**
36
+ - `tsc` — record pass/fail
37
+ - `lint` — record pass/fail
38
+ - `test` — record pass/fail + test count
39
+ - `build` — record pass/fail + bundle size (if frontend)
40
+
41
+ **Domain-specific baseline:**
42
+ - [Example — API: `GET /api/resource` — record HTTP status and response time]
43
+ - [Example — Frontend: navigate to `/feature-page` — capture screenshot and LCP]
44
+ - [Example — Database: record current schema of table `users` (columns + types)]
45
+
46
+ ### Post-change Checks
47
+
48
+ **Quality gates:** [same commands as baseline — all must pass]
49
+
50
+ **Feature verification:**
51
+ - [ ] [Check mapped 1:1 to success criterion 1 — describe exact verification steps]
52
+ - [ ] [Check mapped 1:1 to success criterion 2]
53
+
54
+ **Performance benchmarks:**
55
+ - [metric]: target [value], acceptable range [range]
56
+
57
+ ### MCP Checks
58
+
59
+ **If browser MCPs available (playwright / chrome-devtools):**
60
+ - [Specific pages to navigate and screenshot]
61
+ - [Specific user interactions to exercise]
62
+ - [Specific performance traces to capture]
63
+
64
+ **If no browser MCPs available (shell-only fallback):**
65
+ - [What to verify via build output, curl, or CLI commands]
66
+
67
+ ## Implementation Notes
68
+
69
+ [Optional: key constraints, patterns to follow, architectural decisions the
70
+ executing agent should know. Reference specific file paths or conventions
71
+ discovered from the codebase analysis.]
@@ -86,13 +86,4 @@ Use these severity levels when reporting findings:
86
86
 
87
87
  ## Output format
88
88
 
89
- When reporting security findings, use this structure:
90
-
91
- ```
92
- ### [SEVERITY] A0X: Category Name — Brief title
93
-
94
- **Location**: `file:line`
95
- **Risk**: What can go wrong and the impact.
96
- **Finding**: What the code does wrong.
97
- **Fix**: Specific remediation with code example.
98
- ```
89
+ When reporting security findings, use the template in [template.md](template.md) for each finding.
@@ -0,0 +1,6 @@
1
+ ### [SEVERITY] A0X: Category Name — Brief title
2
+
3
+ **Location**: `file:line`
4
+ **Risk**: What can go wrong and the impact.
5
+ **Finding**: What the code does wrong.
6
+ **Fix**: Specific remediation with code example.
@@ -0,0 +1,348 @@
1
+ ---
2
+ name: tanstack-query
3
+ description: |
4
+ TanStack Query v5 (React Query) reference for data fetching, caching,
5
+ and server state management in React. Use when: (1) writing useQuery,
6
+ useMutation, or useInfiniteQuery hooks, (2) setting up QueryClient
7
+ and queryOptions, (3) implementing optimistic updates or cache
8
+ invalidation, (4) configuring SSR/hydration with Next.js App Router
9
+ or Pages Router, (5) testing React Query hooks, (6) working with
10
+ TypeScript types, Suspense, or advanced patterns like dependent
11
+ queries and infinite scroll.
12
+ ---
13
+
14
+ # TanStack Query v5 (React)
15
+
16
+ ## Setup
17
+
18
+ ```tsx
19
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
20
+
21
+ const queryClient = new QueryClient({
22
+ defaultOptions: {
23
+ queries: {
24
+ staleTime: 60 * 1000, // 1 min (default is 0)
25
+ gcTime: 5 * 60 * 1000, // 5 min (default)
26
+ retry: 3, // 3 retries with exponential backoff (default)
27
+ refetchOnWindowFocus: true, // default
28
+ },
29
+ },
30
+ })
31
+
32
+ function App() {
33
+ return (
34
+ <QueryClientProvider client={queryClient}>
35
+ <YourApp />
36
+ </QueryClientProvider>
37
+ )
38
+ }
39
+ ```
40
+
41
+ ## Important Defaults
42
+
43
+ | Default | Value | Notes |
44
+ |---------|-------|-------|
45
+ | `staleTime` | `0` | Cached data is immediately stale; triggers background refetch on mount/focus/reconnect |
46
+ | `gcTime` | `5 min` | Inactive queries garbage collected after 5 minutes |
47
+ | `retry` | `3` (queries) / `0` (mutations) | Queries retry 3x with exponential backoff; mutations do NOT retry |
48
+ | `refetchOnWindowFocus` | `true` | Stale queries refetch when tab regains focus |
49
+ | `refetchOnReconnect` | `true` | Stale queries refetch when network reconnects |
50
+ | `refetchOnMount` | `true` | Stale queries refetch when new instance mounts |
51
+ | `structuralSharing` | `true` | Preserves referential identity if data is structurally equal |
52
+
53
+ **Key recommendation:** Set `staleTime` above 0 to control refetch frequency rather than disabling individual refetch triggers.
54
+
55
+ ## queryOptions — co-locate key + fn
56
+
57
+ Always use `queryOptions` to define query configurations. It enables type inference across `useQuery`, `prefetchQuery`, `getQueryData`, and `setQueryData`.
58
+
59
+ ```tsx
60
+ import { queryOptions, infiniteQueryOptions } from '@tanstack/react-query'
61
+
62
+ export function todosOptions(filters: TodoFilters) {
63
+ return queryOptions({
64
+ queryKey: ['todos', filters],
65
+ queryFn: () => fetchTodos(filters),
66
+ staleTime: 5 * 1000,
67
+ })
68
+ }
69
+
70
+ // Works everywhere with full type inference:
71
+ useQuery(todosOptions({ status: 'done' }))
72
+ useSuspenseQuery(todosOptions({ status: 'done' }))
73
+ queryClient.prefetchQuery(todosOptions({ status: 'done' }))
74
+ queryClient.setQueryData(todosOptions({ status: 'done' }).queryKey, newData)
75
+ const cached = queryClient.getQueryData(todosOptions({ status: 'done' }).queryKey)
76
+ // ^? TodoItem[] | undefined
77
+ ```
78
+
79
+ For infinite queries, use `infiniteQueryOptions` (same pattern, adds `initialPageParam` and `getNextPageParam`).
80
+
81
+ ## useQuery
82
+
83
+ ```tsx
84
+ const {
85
+ data, // TData | undefined
86
+ error, // TError | null
87
+ status, // 'pending' | 'error' | 'success'
88
+ isPending, // no cached data yet
89
+ isError,
90
+ isSuccess,
91
+ isFetching, // queryFn is running (including background refetch)
92
+ isLoading, // isPending && isFetching (first load only)
93
+ isPlaceholderData, // showing placeholder, not real data
94
+ isStale,
95
+ refetch,
96
+ fetchStatus, // 'fetching' | 'paused' | 'idle'
97
+ } = useQuery({
98
+ queryKey: ['todos', userId], // unique cache key (Array)
99
+ queryFn: () => fetchTodos(userId),
100
+ enabled: !!userId, // disable until userId exists
101
+ staleTime: 60_000,
102
+ select: (data) => data.filter(t => !t.done), // transform/filter
103
+ placeholderData: keepPreviousData, // smooth pagination
104
+ })
105
+ ```
106
+
107
+ **Query states:** `status` tells you "do we have data?"; `fetchStatus` tells you "is the queryFn running?". They are orthogonal — a query can be `pending` + `paused` (no data, no network).
108
+
109
+ ## Query Keys
110
+
111
+ Keys must be Arrays. They are hashed deterministically.
112
+
113
+ ```tsx
114
+ // Object key order does NOT matter — these are equivalent:
115
+ useQuery({ queryKey: ['todos', { status, page }] })
116
+ useQuery({ queryKey: ['todos', { page, status }] })
117
+
118
+ // Array item order DOES matter — these are different:
119
+ useQuery({ queryKey: ['todos', status, page] })
120
+ useQuery({ queryKey: ['todos', page, status] })
121
+ ```
122
+
123
+ **Rule:** If your queryFn depends on a variable, include it in the queryKey. The key acts as a dependency array.
124
+
125
+ ## useMutation
126
+
127
+ ```tsx
128
+ const queryClient = useQueryClient()
129
+
130
+ const mutation = useMutation({
131
+ mutationFn: (newTodo: CreateTodoInput) => api.post('/todos', newTodo),
132
+ onSuccess: (data, variables) => {
133
+ // Invalidate related queries to trigger refetch
134
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
135
+ // Or update cache directly with response data
136
+ queryClient.setQueryData(['todos', data.id], data)
137
+ },
138
+ onError: (error, variables, onMutateResult) => {},
139
+ onSettled: (data, error, variables, onMutateResult) => {},
140
+ })
141
+
142
+ // Trigger:
143
+ mutation.mutate({ title: 'New todo' })
144
+
145
+ // Or with per-call callbacks:
146
+ mutation.mutate(input, { onSuccess: () => navigate('/todos') })
147
+
148
+ // Async variant (returns Promise):
149
+ const data = await mutation.mutateAsync(input)
150
+ ```
151
+
152
+ **Lifecycle:** `onMutate` → `mutationFn` → `onSuccess`/`onError` → `onSettled`. Callbacks returning promises are awaited.
153
+
154
+ **Gotcha:** Per-call `mutate()` callbacks only fire for the *latest* call if mutations overlap. Use `useMutation`-level callbacks for reliable logic.
155
+
156
+ ## Query Invalidation
157
+
158
+ ```tsx
159
+ // Prefix match (default) — invalidates ['todos'] and ['todos', { page: 1 }]
160
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
161
+
162
+ // Exact match only
163
+ queryClient.invalidateQueries({ queryKey: ['todos'], exact: true })
164
+
165
+ // Predicate for fine-grained control
166
+ queryClient.invalidateQueries({
167
+ predicate: (query) => query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
168
+ })
169
+
170
+ // ALL queries
171
+ queryClient.invalidateQueries()
172
+ ```
173
+
174
+ Invalidation marks queries as stale and triggers background refetch for active (rendered) queries.
175
+
176
+ ## Optimistic Updates
177
+
178
+ ### Approach 1: Via the UI (simpler, recommended)
179
+
180
+ Render optimistic state from `variables` directly in JSX:
181
+
182
+ ```tsx
183
+ const { mutate, variables, isPending, isError } = useMutation({
184
+ mutationFn: (text: string) => api.post('/todos', { text }),
185
+ onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
186
+ })
187
+
188
+ // In JSX:
189
+ {todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
190
+ {isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
191
+ ```
192
+
193
+ Access pending mutations from other components with `useMutationState`:
194
+
195
+ ```tsx
196
+ const pendingTodos = useMutationState<string>({
197
+ filters: { mutationKey: ['addTodo'], status: 'pending' },
198
+ select: (mutation) => mutation.state.variables,
199
+ })
200
+ ```
201
+
202
+ ### Approach 2: Via cache (with rollback)
203
+
204
+ ```tsx
205
+ useMutation({
206
+ mutationFn: updateTodo,
207
+ onMutate: async (newTodo, context) => {
208
+ await context.client.cancelQueries({ queryKey: ['todos'] })
209
+ const previous = context.client.getQueryData(['todos'])
210
+ context.client.setQueryData(['todos'], (old) => [...old, newTodo])
211
+ return { previous }
212
+ },
213
+ onError: (err, newTodo, onMutateResult, context) => {
214
+ context.client.setQueryData(['todos'], onMutateResult.previous)
215
+ },
216
+ onSettled: (data, error, variables, onMutateResult, context) => {
217
+ context.client.invalidateQueries({ queryKey: ['todos'] })
218
+ },
219
+ })
220
+ ```
221
+
222
+ **Always** `cancelQueries` before optimistic update to prevent background refetches from overwriting.
223
+
224
+ ## Infinite Queries
225
+
226
+ ```tsx
227
+ const {
228
+ data, // { pages: T[], pageParams: unknown[] }
229
+ fetchNextPage,
230
+ fetchPreviousPage,
231
+ hasNextPage, // true when getNextPageParam returns non-null/undefined
232
+ hasPreviousPage,
233
+ isFetchingNextPage,
234
+ isFetchingPreviousPage,
235
+ } = useInfiniteQuery({
236
+ queryKey: ['projects'],
237
+ queryFn: ({ pageParam }) => fetchProjects(pageParam),
238
+ initialPageParam: 0, // REQUIRED
239
+ getNextPageParam: (lastPage, allPages) => lastPage.nextCursor ?? undefined,
240
+ maxPages: 3, // optional: cap cached pages
241
+ })
242
+
243
+ // Render all pages:
244
+ {data.pages.map((page, i) => (
245
+ <Fragment key={i}>
246
+ {page.items.map(item => <div key={item.id}>{item.name}</div>)}
247
+ </Fragment>
248
+ ))}
249
+
250
+ // Load more:
251
+ <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
252
+ {isFetchingNextPage ? 'Loading...' : hasNextPage ? 'Load More' : 'No more'}
253
+ </button>
254
+ ```
255
+
256
+ **Gotcha:** `data` is `{ pages, pageParams }`, not flat data. `initialData` and `placeholderData` must match this shape.
257
+
258
+ ## Paginated Queries (keep previous data)
259
+
260
+ ```tsx
261
+ import { keepPreviousData, useQuery } from '@tanstack/react-query'
262
+
263
+ const { data, isPlaceholderData } = useQuery({
264
+ queryKey: ['projects', page],
265
+ queryFn: () => fetchProjects(page),
266
+ placeholderData: keepPreviousData,
267
+ })
268
+
269
+ // Prefetch next page for instant transitions:
270
+ queryClient.prefetchQuery({
271
+ queryKey: ['projects', page + 1],
272
+ queryFn: () => fetchProjects(page + 1),
273
+ })
274
+ ```
275
+
276
+ ## Prefetching
277
+
278
+ ```tsx
279
+ // In event handlers (hover/focus):
280
+ const prefetch = () => queryClient.prefetchQuery(todosOptions())
281
+ <button onMouseEnter={prefetch} onFocus={prefetch} onClick={handleClick}>Show</button>
282
+
283
+ // In components (avoid Suspense waterfalls):
284
+ function Layout({ id }: { id: string }) {
285
+ usePrefetchQuery(commentsOptions(id)) // starts fetch immediately
286
+ return (
287
+ <Suspense fallback="Loading...">
288
+ <Article id={id} />
289
+ </Suspense>
290
+ )
291
+ }
292
+
293
+ // Prefetch infinite queries:
294
+ queryClient.prefetchInfiniteQuery({
295
+ ...projectsInfiniteOptions(),
296
+ pages: 3, // prefetch first 3 pages
297
+ })
298
+ ```
299
+
300
+ ## Dependent Queries
301
+
302
+ ```tsx
303
+ const { data: user } = useQuery({
304
+ queryKey: ['user', email],
305
+ queryFn: () => getUserByEmail(email),
306
+ })
307
+
308
+ const { data: projects } = useQuery({
309
+ queryKey: ['projects', user?.id],
310
+ queryFn: () => getProjectsByUser(user!.id),
311
+ enabled: !!user?.id, // waits for user query
312
+ })
313
+ ```
314
+
315
+ **Type-safe disabling with skipToken:**
316
+
317
+ ```tsx
318
+ import { skipToken } from '@tanstack/react-query'
319
+
320
+ const { data } = useQuery({
321
+ queryKey: ['projects', userId],
322
+ queryFn: userId ? () => getProjects(userId) : skipToken,
323
+ })
324
+ ```
325
+
326
+ `skipToken` prevents `refetch()` from working — use `enabled: false` if you need manual refetch.
327
+
328
+ ## setQueryData — immutability
329
+
330
+ ```tsx
331
+ // WRONG — mutating cache in place
332
+ queryClient.setQueryData(['todo', id], (old) => {
333
+ if (old) old.title = 'new' // DO NOT DO THIS
334
+ return old
335
+ })
336
+
337
+ // CORRECT — return new object
338
+ queryClient.setQueryData(['todo', id], (old) =>
339
+ old ? { ...old, title: 'new' } : old
340
+ )
341
+ ```
342
+
343
+ ## Further Reference
344
+
345
+ - **Full API signatures** (useQuery, useMutation, useInfiniteQuery, QueryClient): See [references/api-reference.md](references/api-reference.md)
346
+ - **SSR & Next.js** (hydration, App Router, streaming): See [references/ssr-nextjs.md](references/ssr-nextjs.md)
347
+ - **Testing** (renderHook, mocking, setup): See [references/testing.md](references/testing.md)
348
+ - **Advanced patterns** (TypeScript, Suspense, waterfalls, network modes): See [references/advanced-patterns.md](references/advanced-patterns.md)