dev-booster 1.18.0 → 1.18.2

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 (48) hide show
  1. package/README.md +5 -2
  2. package/package.json +1 -1
  3. package/template/.devbooster/MANIFEST.md +36 -2
  4. package/template/.devbooster/boosters/advisor.md +5 -0
  5. package/template/.devbooster/boosters/audit.md +20 -0
  6. package/template/.devbooster/boosters/auto-triage.md +5 -0
  7. package/template/.devbooster/boosters/backend.md +12 -0
  8. package/template/.devbooster/boosters/builder.md +5 -0
  9. package/template/.devbooster/boosters/code-audit.md +19 -0
  10. package/template/.devbooster/boosters/coder.md +5 -0
  11. package/template/.devbooster/boosters/commit.md +233 -0
  12. package/template/.devbooster/boosters/create.md +5 -0
  13. package/template/.devbooster/boosters/debug.md +19 -0
  14. package/template/.devbooster/boosters/deploy.md +12 -0
  15. package/template/.devbooster/boosters/discovery.md +5 -0
  16. package/template/.devbooster/boosters/frontend.md +12 -0
  17. package/template/.devbooster/boosters/implementation.md +5 -0
  18. package/template/.devbooster/boosters/investigation.md +5 -0
  19. package/template/.devbooster/boosters/performance.md +12 -0
  20. package/template/.devbooster/boosters/planning.md +5 -0
  21. package/template/.devbooster/boosters/refactor.md +12 -0
  22. package/template/.devbooster/boosters/review.md +12 -0
  23. package/template/.devbooster/boosters/security.md +14 -0
  24. package/template/.devbooster/boosters/smart-task.md +27 -16
  25. package/template/.devbooster/boosters/stack-refresh.md +20 -0
  26. package/template/.devbooster/boosters/testing.md +12 -0
  27. package/template/.devbooster/hub/knowledge/angular-patterns.md +185 -0
  28. package/template/.devbooster/hub/knowledge/dependency-guide.md +175 -0
  29. package/template/.devbooster/hub/knowledge/eslint-migration.md +206 -0
  30. package/template/.devbooster/hub/knowledge/index.md +91 -0
  31. package/template/.devbooster/hub/knowledge/migration-guides.md +137 -0
  32. package/template/.devbooster/hub/knowledge/monorepo-patterns.md +121 -0
  33. package/template/.devbooster/hub/knowledge/nestjs-patterns.md +185 -0
  34. package/template/.devbooster/hub/knowledge/nextjs-pitfalls.md +226 -0
  35. package/template/.devbooster/hub/knowledge/nodejs-patterns.md +148 -0
  36. package/template/.devbooster/hub/knowledge/package-manager-patterns.md +143 -0
  37. package/template/.devbooster/hub/knowledge/prisma-postgresql-patterns.md +188 -0
  38. package/template/.devbooster/hub/knowledge/react-patterns.md +500 -0
  39. package/template/.devbooster/hub/knowledge/tailwind-shadcn-patterns.md +146 -0
  40. package/template/.devbooster/hub/knowledge/tanstack-patterns.md +278 -0
  41. package/template/.devbooster/hub/knowledge/testing-patterns.md +164 -0
  42. package/template/.devbooster/hub/knowledge/trpc-patterns.md +212 -0
  43. package/template/.devbooster/hub/knowledge/typescript-patterns.md +219 -0
  44. package/template/.devbooster/hub/knowledge/upgrade-fallout.md +154 -0
  45. package/template/.devbooster/hub/knowledge/vite-patterns.md +177 -0
  46. package/template/.devbooster/rules/GUIDE.md +26 -0
  47. package/template/.devbooster/rules/PROTOCOL.md +3 -2
  48. package/template/.devbooster/rules/TRIGGERS.md +15 -0
@@ -0,0 +1,278 @@
1
+ # TanStack Query Patterns
2
+
3
+ > **Purpose:** Practical TanStack Query v5 patterns for correct caching, mutations, cancellation, SSR hydration, and `QueryClient` lifecycle.
4
+ > **Primary official sources:** [Query keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) · [Query invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation) · [Caching](https://tanstack.com/query/latest/docs/framework/react/guides/caching) · [SSR and hydration](https://tanstack.com/query/latest/docs/framework/react/guides/ssr) · [Optimistic updates](https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates)
5
+
6
+ ## Project Convention Decision Rule
7
+
8
+ Before applying this guidance, verify the installed versions, local rules, existing query and mutation hooks, query-key conventions, `QueryClient` lifecycle, configuration, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ 1. [Use stable, complete query keys](#use-stable-complete-query-keys)
15
+ 2. [Invalidate affected queries after mutations](#invalidate-affected-queries-after-mutations)
16
+ 3. [Choose `staleTime` and `gcTime` for different concerns](#choose-staletime-and-gctime-for-different-concerns)
17
+ 4. [Prefetch and hydrate SSR data deliberately](#prefetch-and-hydrate-ssr-data-deliberately)
18
+ 5. [Write query functions that consume `AbortSignal`](#write-query-functions-that-consume-abortsignal)
19
+ 6. [Make mutation errors and optimistic rollbacks explicit](#make-mutation-errors-and-optimistic-rollbacks-explicit)
20
+ 7. [Create a stable `QueryClient`](#create-a-stable-queryclient)
21
+ 8. [Prefer Existing Query Ownership Over a Parallel Effect](#prefer-existing-query-ownership-over-a-parallel-effect)
22
+ 9. [Render Query States Deliberately](#render-query-states-deliberately)
23
+
24
+ ---
25
+
26
+ ## Use stable, complete query keys
27
+
28
+ ### Problem / symptom
29
+ Different data is served from one cache entry, or a query fails to refetch when a parameter changes. Common causes are a missing parameter in `queryKey` or inconsistent key shapes across the codebase.
30
+
31
+ ### Fix
32
+ Use a serializable array key that uniquely describes the returned data. Include every changing variable read by the query function. Object property order does not affect hashing; array item order does.
33
+
34
+ ```tsx
35
+ const itemKeys = {
36
+ all: ['items'] as const,
37
+ list: (filters: { status?: string }) => ['items', { filters }] as const,
38
+ detail: (id: string) => ['items', id] as const,
39
+ }
40
+
41
+ useQuery({
42
+ queryKey: itemKeys.detail(id),
43
+ queryFn: () => fetchItem(id),
44
+ })
45
+ ```
46
+
47
+ A key factory is optional; the requirement is stable, complete, consistently shaped keys.
48
+
49
+ ### Verify first
50
+ - Compare all variables used by `queryFn` with the `queryKey`.
51
+ - Change each variable and confirm a distinct cache entry or expected refetch occurs.
52
+ - Confirm list and detail keys support the invalidation scope that mutations require.
53
+
54
+ *Source: [Query keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys)*
55
+
56
+ ---
57
+
58
+ ## Invalidate affected queries after mutations
59
+
60
+ ### Problem / symptom
61
+ A mutation succeeds but visible data remains stale, or invalidation refetches unrelated data unnecessarily.
62
+
63
+ ### Fix
64
+ On mutation success or settlement, invalidate the smallest key prefix that represents all data changed by the mutation. `invalidateQueries` marks matching queries stale and background-refetches matching active queries; invalidation overrides configured `staleTime`.
65
+
66
+ ```tsx
67
+ const queryClient = useQueryClient()
68
+
69
+ useMutation({
70
+ mutationFn: updateItem,
71
+ onSuccess: (_item, variables) =>
72
+ Promise.all([
73
+ queryClient.invalidateQueries({ queryKey: itemKeys.detail(variables.id) }),
74
+ queryClient.invalidateQueries({ queryKey: itemKeys.all }),
75
+ ]),
76
+ })
77
+ ```
78
+
79
+ Use `{ exact: true }` only when the mutation genuinely affects one exact key. If the mutation response completely and correctly represents a cached resource, `setQueryData` can update that specific entry instead of a refetch.
80
+
81
+ ### Verify first
82
+ - List every query view affected by create, update, and delete paths.
83
+ - Confirm the invalidation key matches each intended query and excludes unrelated ones.
84
+ - Confirm the mutation waits for any required invalidation promise when pending state should include the refetch.
85
+
86
+ *Source: [Query invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation) · [Invalidation from mutations](https://tanstack.com/query/latest/docs/framework/react/guides/invalidations-from-mutations)*
87
+
88
+ ---
89
+
90
+ ## Choose `staleTime` and `gcTime` for different concerns
91
+
92
+ ### Problem / symptom
93
+ `gcTime` is increased to stop refetching, or `staleTime` is increased to retain data for remounts. The result is an incorrect freshness policy or unexpected memory use.
94
+
95
+ ### Fix
96
+ `staleTime` controls how long fetched data is considered fresh. `gcTime` controls how long an **inactive** query remains in cache before garbage collection. They are independent:
97
+
98
+ | Setting | Controls | Default described by the docs |
99
+ | --- | --- | --- |
100
+ | `staleTime` | Freshness and eligibility for automatic refetch behavior | `0` (immediately stale) |
101
+ | `gcTime` | Retention after the final observer unmounts | 5 minutes in the browser |
102
+
103
+ Set `staleTime` from the acceptable age of the data, not from navigation frequency. Set `gcTime` from the value of retaining inactive data versus memory cost.
104
+
105
+ ### Verify first
106
+ - Determine the maximum acceptable data age for each query before choosing `staleTime`.
107
+ - Unmount and remount a query before and after `gcTime` to distinguish retention from freshness.
108
+ - For SSR, note that default server `gcTime` is `Infinity`; do not set it to `0`, which can remove hydrated data before rendering completes.
109
+
110
+ *Source: [Caching examples](https://tanstack.com/query/latest/docs/framework/react/guides/caching) · [SSR memory and staleness](https://tanstack.com/query/latest/docs/framework/react/guides/ssr#tips-tricks-and-caveats)*
111
+
112
+ ---
113
+
114
+ ## Prefetch and hydrate SSR data deliberately
115
+
116
+ ### Problem / symptom
117
+ SSR markup renders data but the browser immediately fetches it again, displays different initial output, or a server cache leaks data between requests.
118
+
119
+ ### Fix
120
+ For data required in initial markup, create a request-local `QueryClient` during the framework's preload/loader phase, `prefetchQuery` the required queries, `dehydrate` it, then render the client tree under `HydrationBoundary` with that state. The browser provider must create one stable client instance per application lifecycle. Set a nonzero `staleTime` when immediate background refetch after hydration is not desired.
121
+
122
+ ```tsx
123
+ // Server preload phase
124
+ const queryClient = new QueryClient()
125
+ await queryClient.prefetchQuery({ queryKey: ['items'], queryFn: fetchItems })
126
+ const dehydratedState = dehydrate(queryClient)
127
+
128
+ // Rendered client tree
129
+ <HydrationBoundary state={dehydratedState}>
130
+ <Items />
131
+ </HydrationBoundary>
132
+ ```
133
+
134
+ Use `fetchQuery` rather than `prefetchQuery` when server rendering must surface a failure for framework-level handling: `prefetchQuery` does not throw, and dehydration includes successful queries by default.
135
+
136
+ ### Verify first
137
+ - Confirm the server prefetch key exactly matches the browser query key.
138
+ - Confirm the dehydration payload is serialized safely by the framework and contains only data safe to deliver to the browser.
139
+ - Test a cold request and a client-side navigation; measure whether critical data refetches according to the selected `staleTime`.
140
+
141
+ *Source: [Server rendering and hydration](https://tanstack.com/query/latest/docs/framework/react/guides/ssr)*
142
+
143
+ ---
144
+
145
+ ## Write query functions that consume `AbortSignal`
146
+
147
+ ### Problem / symptom
148
+ Superseded or unused requests continue consuming network and server resources, or manual cancellation does not cancel the underlying request.
149
+
150
+ ### Fix
151
+ TanStack Query passes an `AbortSignal` to each query function. Pass it to `fetch` or a supported HTTP client. When the signal is consumed and aborted, the query promise is cancelled and the query state reverts to its previous state.
152
+
153
+ ```tsx
154
+ useQuery({
155
+ queryKey: itemKeys.detail(id),
156
+ queryFn: async ({ signal }) => {
157
+ const response = await fetch(`/api/items/${id}`, { signal })
158
+ if (!response.ok) throw new Error('Unable to load item')
159
+ return response.json()
160
+ },
161
+ })
162
+ ```
163
+
164
+ By default, an unused query is not cancelled merely because it unmounts; its resolved result can still populate the cache. Consuming the signal opts the request into cancellation behavior.
165
+
166
+ ### Verify first
167
+ - Confirm the HTTP library accepts and forwards `AbortSignal`.
168
+ - Start a request, unmount or call `cancelQueries`, and inspect whether the underlying request is aborted.
169
+ - Ensure UI and error reporting distinguish an intentional cancellation from an actionable failure when that distinction matters.
170
+
171
+ *Source: [Query cancellation](https://tanstack.com/query/latest/docs/framework/react/guides/query-cancellation)*
172
+
173
+ ---
174
+
175
+ ## Make mutation errors and optimistic rollbacks explicit
176
+
177
+ ### Problem / symptom
178
+ An optimistic cache update is overwritten by an in-flight refetch, or a failed mutation leaves incorrect optimistic data visible.
179
+
180
+ ### Fix
181
+ Use the simplest option first: render a temporary mutation result from `variables` when only one view needs it. For a shared optimistic cache update, cancel relevant queries, snapshot the previous value, update the cache in `onMutate`, restore the snapshot in `onError`, and invalidate in `onSettled`.
182
+
183
+ ```tsx
184
+ useMutation({
185
+ mutationFn: updateItem,
186
+ onMutate: async (nextItem, context) => {
187
+ await context.client.cancelQueries({ queryKey: itemKeys.detail(nextItem.id) })
188
+ const previous = context.client.getQueryData(itemKeys.detail(nextItem.id))
189
+ context.client.setQueryData(itemKeys.detail(nextItem.id), nextItem)
190
+ return { previous }
191
+ },
192
+ onError: (_error, item, snapshot, context) => {
193
+ context.client.setQueryData(itemKeys.detail(item.id), snapshot?.previous)
194
+ },
195
+ onSettled: (_data, _error, item, _snapshot, context) =>
196
+ context.client.invalidateQueries({ queryKey: itemKeys.detail(item.id) }),
197
+ })
198
+ ```
199
+
200
+ Return the snapshot from `onMutate`; TanStack Query passes it to `onError` and `onSettled`. Retain mutation error state for a visible recovery path when appropriate.
201
+
202
+ ### Verify first
203
+ - Force the mutation to fail after the optimistic update and confirm the precise previous value is restored.
204
+ - Test a refetch racing with the mutation; confirm `cancelQueries` prevents it from overwriting the optimistic state.
205
+ - Test concurrent mutations against the same key before assuming a single snapshot is sufficient.
206
+
207
+ *Source: [Optimistic updates](https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates)*
208
+
209
+ ---
210
+
211
+ ## Create a stable `QueryClient`
212
+
213
+ ### Problem / symptom
214
+ Cache data disappears on every render, subscriptions reset, or all requests share cache data during SSR.
215
+
216
+ ### Fix
217
+ Create one `QueryClient` for the application lifecycle, not during every component render. In a React client component, initialize it lazily in state. For SSR, create a request-local client in the preload phase; do not place a request-serving cache at module scope.
218
+
219
+ ```tsx
220
+ function App() {
221
+ const [queryClient] = React.useState(() => new QueryClient())
222
+
223
+ return (
224
+ <QueryClientProvider client={queryClient}>
225
+ <Routes />
226
+ </QueryClientProvider>
227
+ )
228
+ }
229
+ ```
230
+
231
+ An async Server Component may create a new client because it runs once on the server. The official ESLint rule can detect the render-time recreation pattern.
232
+
233
+ ### Verify first
234
+ - Re-render the provider component and confirm the `QueryClient` identity remains stable.
235
+ - For SSR, make two isolated requests and confirm their dehydration payloads cannot contain each other's data.
236
+ - Enable `@tanstack/query/stable-query-client` where the ESLint plugin is used.
237
+
238
+ *Source: [Stable Query Client ESLint rule](https://tanstack.com/query/latest/docs/eslint/stable-query-client) · [SSR setup](https://tanstack.com/query/latest/docs/framework/react/guides/ssr#initial-setup)*
239
+
240
+ ---
241
+
242
+ ## Prefer Existing Query Ownership Over a Parallel Effect
243
+
244
+ ### Decision
245
+ When a project already uses TanStack Query for a resource, the query hook owns that server state. Do not add a parallel `useEffect` fetch, duplicate local cache, or second query-key convention for the same resource.
246
+
247
+ ### Verify first
248
+ - Locate existing query-key factories, query hooks, API clients, and mutation invalidation rules.
249
+ - Reuse the closest existing hook when its contract matches the screen’s data need.
250
+ - Create a new hook only when the query has a distinct key, inputs, response contract, or reuse boundary.
251
+ - Keep local state for local UI concerns; do not mirror query data into local state without a specific interaction reason.
252
+
253
+ This preserves cache coherence, loading semantics, invalidation, and developer familiarity. A plain Effect remains appropriate only when the project does not use a query layer for that boundary or when the work is not server-state ownership.
254
+
255
+ *Sources: [TanStack Query — Important Defaults](https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults) · [React — Fetching data with Effects](https://react.dev/reference/react/useEffect#fetching-data-with-effects)*
256
+
257
+ ---
258
+
259
+ ## Render Query States Deliberately
260
+
261
+ ### Decision
262
+ A query result needs an intentional UI for pending, error, empty, and success states. Preserve any shared loading, retry, empty-state, and error components already used by comparable screens.
263
+
264
+ ### Example
265
+ ```tsx
266
+ if (query.isPending) return <ItemsSkeleton />
267
+ if (query.isError) return <ErrorState onRetry={() => query.refetch()} />
268
+ if (query.data.length === 0) return <EmptyState />
269
+ return <ItemList items={query.data} />
270
+ ```
271
+
272
+ ### Verify first
273
+ - Use the status names and behavior documented for the installed TanStack Query version.
274
+ - Keep stale data visible during a background refetch when that matches the existing experience; do not replace it with a full-screen spinner without intent.
275
+ - Distinguish an empty successful response from a failed request.
276
+ - Make retry behavior and user-facing error treatment match project conventions and API semantics.
277
+
278
+ *Sources: [TanStack Query — Queries](https://tanstack.com/query/latest/docs/framework/react/guides/queries) · [TanStack Query — Background Fetching Indicators](https://tanstack.com/query/latest/docs/framework/react/guides/background-fetching-indicators)*
@@ -0,0 +1,164 @@
1
+ # 🧪 Testing Patterns
2
+
3
+ > **Purpose:** Build reliable unit, integration, and browser test coverage with reproducible validation.
4
+ > **Primary official sources:** [Vitest Guide](https://vitest.dev/guide/) · [Jest Configuration](https://jestjs.io/docs/configuration) · [Playwright Best Practices](https://playwright.dev/docs/best-practices) · [Playwright Locators](https://playwright.dev/docs/locators) · [Testing Library Guiding Principles](https://testing-library.com/docs/guiding-principles/)
5
+
6
+ ## Project Convention Decision Rule
7
+
8
+ Before applying this guidance, verify the installed versions, local rules, test runner, existing fixtures and helpers, CI configuration, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ 1. [Choose the Smallest Test Scope That Proves the Behavior](#choose-the-smallest-test-scope-that-proves-the-behavior)
15
+ 2. [Test Environment Does Not Match the Code](#test-environment-does-not-match-the-code)
16
+ 3. [Tests Depend on Time, Randomness, or Live Network State](#tests-depend-on-time-randomness-or-live-network-state)
17
+ 4. [Browser Tests Use Fragile Locators or Share State](#browser-tests-use-fragile-locators-or-share-state)
18
+ 5. [CI and Local Browser or Runtime Environments Differ](#ci-and-local-browser-or-runtime-environments-differ)
19
+ 6. [Validation Matrix](#validation-matrix)
20
+ 7. [Test Async UI States Through Observable Behavior](#test-async-ui-states-through-observable-behavior)
21
+
22
+ ---
23
+
24
+ ## Choose the Smallest Test Scope That Proves the Behavior
25
+
26
+ ### Problem
27
+ Tests either duplicate implementation details in isolated units or use slow end-to-end flows for every behavior.
28
+
29
+ ### Verify first
30
+ State the behavior and boundary being proved: pure logic, a component interaction, an API integration, or an essential user journey. A test’s scope should match the risk it is intended to detect.
31
+
32
+ ### Fix
33
+ Use a practical test pyramid:
34
+
35
+ | Scope | Best for | Avoid using it for |
36
+ |---|---|---|
37
+ | Unit | Pure functions, validation, reducers, formatting | Browser integration details |
38
+ | Integration | Component behavior, module boundaries, request handling | Full deployment and browser compatibility |
39
+ | End-to-end | Critical user journeys across real browser boundaries | Exhaustive branch coverage |
40
+
41
+ Prefer assertions observable to users over internal implementation state. Keep a small set of high-value browser journeys and place most coverage below that layer.
42
+
43
+ *Sources: [Testing Library — Guiding Principles](https://testing-library.com/docs/guiding-principles/), [Playwright — Best Practices](https://playwright.dev/docs/best-practices)*
44
+
45
+ ---
46
+
47
+ ## Test Environment Does Not Match the Code
48
+
49
+ ### Symptom
50
+ Tests fail on `window`, `document`, storage, or layout APIs—or browser-oriented tests run in a slower environment without need.
51
+
52
+ ### Verify first
53
+ Identify the APIs exercised by the test. Node is appropriate for server and pure logic. A DOM environment is required for code that accesses browser globals, but simulated DOM behavior is not equivalent to a real browser.
54
+
55
+ ### Fix
56
+ Set the environment deliberately per project or test group. In Vitest, configure `environment` or an environment annotation; in Jest, select the appropriate `testEnvironment`. Use Playwright for behavior that depends on an actual browser engine, browser navigation, or real layout behavior.
57
+
58
+ ```ts
59
+ // vitest.config.ts
60
+ export default {
61
+ test: { environment: 'jsdom' },
62
+ }
63
+ ```
64
+
65
+ Verify the selected environment's version and supported APIs before adding arbitrary global mocks.
66
+
67
+ *Sources: [Vitest — Environment](https://vitest.dev/guide/environment.html), [Jest — testEnvironment](https://jestjs.io/docs/configuration#testenvironment-string)*
68
+
69
+ ---
70
+
71
+ ## Tests Depend on Time, Randomness, or Live Network State
72
+
73
+ ### Symptom
74
+ Tests are flaky, pass alone but fail in a suite, or fail only in CI.
75
+
76
+ ### Verify first
77
+ Look for wall-clock time, timers, random IDs, locale/time zone, shared mutable state, and outgoing network calls. Treat each as an uncontrolled input until explicitly fixed or mocked.
78
+
79
+ ### Fix
80
+ Control inputs at the test boundary:
81
+
82
+ - Use fake timers only when the behavior is timer-driven; advance time intentionally and restore real timers after the test.
83
+ - Inject or seed random values when identifiers affect assertions.
84
+ - Mock network requests at the client boundary and assert both success and failure behavior; do not call live external services from ordinary tests.
85
+ - Reset mocks, module state, storage, and environment changes between tests.
86
+
87
+ When using fake timers with asynchronous code, follow the test runner's documented async timer APIs and verify pending work has settled before asserting.
88
+
89
+ *Sources: [Vitest — Mocking Timers](https://vitest.dev/guide/mocking.html#timers), [Jest — Timer Mocks](https://jestjs.io/docs/timer-mocks), [Playwright — Network](https://playwright.dev/docs/network)*
90
+
91
+ ---
92
+
93
+ ## Browser Tests Use Fragile Locators or Share State
94
+
95
+ ### Symptom
96
+ A Playwright test breaks after markup or styling changes, or passes/fails depending on test order.
97
+
98
+ ### Verify first
99
+ Check whether the locator expresses user-visible meaning and whether the test starts with an independent account, storage state, data record, and browser context.
100
+
101
+ ### Fix
102
+ Prefer Playwright’s user-facing locators: `getByRole`, `getByLabel`, `getByText`, and `getByTestId` when a stable test contract is needed. Avoid long CSS/XPath selectors tied to DOM structure. Rely on Playwright’s locator auto-waiting rather than arbitrary sleeps.
103
+
104
+ Keep tests isolated: create unique data or reset it through supported setup/teardown, and avoid state carried from another test. Reuse authenticated storage state only when it is created and managed as a deliberate fixture.
105
+
106
+ *Sources: [Playwright — Locators](https://playwright.dev/docs/locators), [Playwright — Test Isolation](https://playwright.dev/docs/browser-contexts), [Playwright — Best Practices](https://playwright.dev/docs/best-practices)*
107
+
108
+ ---
109
+
110
+ ## CI and Local Browser or Runtime Environments Differ
111
+
112
+ ### Symptom
113
+ The suite passes locally but fails in CI, or only a particular browser fails.
114
+
115
+ ### Verify first
116
+ Compare Node version, package-manager lockfile mode, operating system, browser version, environment variables, time zone/locale, and test command. Confirm whether CI runs the same browser projects and build artifact as local validation.
117
+
118
+ ### Fix
119
+ Pin and declare the runtime expected by the toolchain, install from the lockfile in CI, and install the browser binaries required by the Playwright version in use. Run the same focused command locally before changing assertions. Treat a browser-specific failure as evidence to investigate, not as a reason to remove that browser project.
120
+
121
+ *Sources: [Playwright — Continuous Integration](https://playwright.dev/docs/ci), [Node.js — Releases](https://nodejs.org/en/about/previous-releases)*
122
+
123
+ ---
124
+
125
+ ## Validation Matrix
126
+
127
+ Run the narrowest relevant checks first, then the complete required matrix before merging:
128
+
129
+ | Change type | Required validation |
130
+ |---|---|
131
+ | Pure logic or utility | Targeted unit test, typecheck |
132
+ | Component behavior | Targeted component/integration test, typecheck, lint |
133
+ | Request or state boundary | Success/failure integration tests, lint, typecheck, build |
134
+ | User journey or routing | Targeted Playwright test against the intended build/runtime, lint, typecheck, build |
135
+ | Toolchain or dependency change | Full test suite, lint, typecheck, production build, relevant browser tests |
136
+
137
+ ### Verify first
138
+ Use the scripts and configuration that the repository actually defines. A passing test command does not imply that linting, type checking, or the production build passed, and a passing DOM simulation does not replace browser coverage.
139
+
140
+ ### Minimum final check
141
+ 1. Run affected tests.
142
+ 2. Run lint.
143
+ 3. Run typecheck.
144
+ 4. Run the production build.
145
+ 5. Run affected browser tests when user-visible flows or browser behavior changed.
146
+
147
+ *Sources: [Vitest — CLI](https://vitest.dev/guide/cli), [Jest — CLI](https://jestjs.io/docs/cli), [Playwright — Test CLI](https://playwright.dev/docs/test-cli)*
148
+
149
+ ---
150
+
151
+ ## Test Async UI States Through Observable Behavior
152
+
153
+ ### Decision
154
+ When a feature loads, mutates, retries, or fails, test the user-observable state transitions that the project exposes: pending feedback, success data, empty results, actionable errors, and recovery. Do not assert internal hook calls or implementation-only state unless that is the public contract of a dedicated unit.
155
+
156
+ ### Verify first
157
+ - Reuse the project’s test setup, request-mocking layer, render helpers, and query-client fixture.
158
+ - Cover the states the screen actually supports; do not invent loading/error behavior absent from the product requirement.
159
+ - Await stable UI transitions with the test runner’s documented async utilities rather than arbitrary sleeps.
160
+ - For mutation flows, test failure and recovery as well as success when the user can retry or when optimistic UI is used.
161
+
162
+ Use component/integration tests for most async UI behavior. Add browser coverage when routing, real browser behavior, or a critical end-to-end path is the risk being addressed.
163
+
164
+ *Sources: [Testing Library — Async Methods](https://testing-library.com/docs/dom-testing-library/api-async/) · [Testing Library — Guiding Principles](https://testing-library.com/docs/guiding-principles/) · [Playwright — Auto-waiting](https://playwright.dev/docs/actionability)*
@@ -0,0 +1,212 @@
1
+ # tRPC Patterns
2
+
3
+ > **Purpose:** Practical tRPC v11 patterns for secure procedure boundaries, end-to-end type integrity, and observable HTTP transport.
4
+ > **Primary official sources:** [Context](https://trpc.io/docs/server/context) · [Procedures](https://trpc.io/docs/server/procedures) · [Error handling](https://trpc.io/docs/server/error-handling) · [HTTP batch link](https://trpc.io/docs/client/links/httpBatchLink) · [Type inference](https://trpc.io/docs/client/vanilla/infer-types)
5
+
6
+ ## Project Convention Decision Rule
7
+
8
+ Before applying this guidance, verify the installed versions, local rules, existing router and procedure abstractions, transport configuration, error contracts, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ 1. [Keep authentication and authorization at procedure boundaries](#keep-authentication-and-authorization-at-procedure-boundaries)
15
+ 2. [Validate every procedure input on the server](#validate-every-procedure-input-on-the-server)
16
+ 3. [Map expected failures to `TRPCError`](#map-expected-failures-to-trpcerror)
17
+ 4. [Preserve server-to-client type inference](#preserve-server-to-client-type-inference)
18
+ 5. [Organize routers around reusable primitives and modules](#organize-routers-around-reusable-primitives-and-modules)
19
+ 6. [Make batching and transport observable](#make-batching-and-transport-observable)
20
+ 7. [Keep tRPC packages version-aligned](#keep-trpc-packages-version-aligned)
21
+
22
+ ---
23
+
24
+ ## Keep authentication and authorization at procedure boundaries
25
+
26
+ ### Problem / symptom
27
+ A resolver checks authentication inconsistently, or trusts a user or permission value supplied in procedure input. This makes a protected route easy to omit during future work and leaves the authenticated value nullable throughout protected resolvers.
28
+
29
+ ### Fix
30
+ Build request-derived context once per HTTP request. Export named base procedures: `publicProcedure`, an authenticated procedure that rejects missing identity, and narrower authorization procedures when a route needs a validated scope. Middleware can refine context, so `ctx.user` is non-null in downstream resolvers.
31
+
32
+ ```ts
33
+ import { initTRPC, TRPCError } from '@trpc/server'
34
+
35
+ const t = initTRPC.context<Context>().create()
36
+ export const router = t.router
37
+ export const publicProcedure = t.procedure
38
+
39
+ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
40
+ if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' })
41
+ return next({ ctx: { user: ctx.user } })
42
+ })
43
+ ```
44
+
45
+ Use input to identify the target resource, then authorize that target using trusted context and server-side data. Do not treat an input role, account identifier, or permission flag as proof of authorization.
46
+
47
+ ### Verify first
48
+ - Confirm `createContext` is attached to the HTTP handler; tRPC creates it once per request, including one shared context for a batched request.
49
+ - Confirm protected procedures cannot be called without an authenticated context.
50
+ - Test an authenticated identity that lacks access to the target resource and expect `FORBIDDEN`.
51
+
52
+ *Source: [Context](https://trpc.io/docs/server/context) · [Reusable base procedures](https://trpc.io/docs/server/procedures#reusable-base-procedures)*
53
+
54
+ ---
55
+
56
+ ## Validate every procedure input on the server
57
+
58
+ ### Problem / symptom
59
+ A TypeScript type is used as if it validates network input, or a resolver assumes the caller sent a valid ID, pagination value, or object shape.
60
+
61
+ ### Fix
62
+ Add `.input(...)` to every procedure that accepts input. The parser receives untrusted `unknown` input and must return a validated value or throw. Use a supported schema library or a custom parser; keep authorization separate from shape validation.
63
+
64
+ ```ts
65
+ import { z } from 'zod'
66
+
67
+ const byIdInput = z.object({ id: z.string().min(1) })
68
+
69
+ export const itemRouter = router({
70
+ byId: protectedProcedure
71
+ .input(byIdInput)
72
+ .query(({ input }) => findItem(input.id)),
73
+ })
74
+ ```
75
+
76
+ Validation types improve developer feedback; the runtime parser is the security boundary.
77
+
78
+ ### Verify first
79
+ - Send malformed input directly to the transport, not only through the typed client.
80
+ - Confirm the resolver is not entered when parsing fails.
81
+ - Confirm limits and domain constraints needed by downstream services are included in the schema.
82
+
83
+ *Source: [Input parsers](https://trpc.io/docs/quickstart#3-using-input-parser-to-validate-procedure-inputs)*
84
+
85
+ ---
86
+
87
+ ## Map expected failures to `TRPCError`
88
+
89
+ ### Problem / symptom
90
+ Expected conditions are thrown as generic errors, producing misleading `INTERNAL_SERVER_ERROR` responses; or detailed internal failures and stacks reach consumers.
91
+
92
+ ### Fix
93
+ Throw `TRPCError` for expected API outcomes and use its documented code to express the condition. Keep unexpected errors as internal failures, observe them in the adapter's `onError`, and expose a safe message. tRPC maps its error codes to HTTP statuses and omits stack traces by default in production.
94
+
95
+ ```ts
96
+ if (!item) {
97
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Item not found' })
98
+ }
99
+ if (!canEdit(ctx.user, item)) {
100
+ throw new TRPCError({ code: 'FORBIDDEN' })
101
+ }
102
+ ```
103
+
104
+ Use `errorFormatter` only when the client needs a deliberate, safe extension to the error shape. Avoid returning raw database, provider, or stack details.
105
+
106
+ ### Verify first
107
+ - Assert `UNAUTHORIZED`, `FORBIDDEN`, validation, and missing-resource paths separately.
108
+ - Check the production error response has no stack trace or confidential cause.
109
+ - Confirm `onError` records path, operation type, and a safe correlation identifier without logging secrets.
110
+
111
+ *Source: [Error codes and error handling](https://trpc.io/docs/server/error-handling)*
112
+
113
+ ---
114
+
115
+ ## Preserve server-to-client type inference
116
+
117
+ ### Problem / symptom
118
+ The client duplicates request/response interfaces, imports server runtime code, or loses autocomplete after routers are split.
119
+
120
+ ### Fix
121
+ Export the root router value and its type from the server boundary. Import `AppRouter` with `import type` on the client and give it to the tRPC client or integration. Derive any shared input/output type with `inferRouterInputs` and `inferRouterOutputs` rather than handwritten duplicates.
122
+
123
+ ```ts
124
+ // server/app-router.ts
125
+ export const appRouter = router({ item: itemRouter })
126
+ export type AppRouter = typeof appRouter
127
+
128
+ // client/trpc.ts
129
+ import { createTRPCClient } from '@trpc/client'
130
+ import type { AppRouter } from '../server/app-router'
131
+
132
+ export const trpc = createTRPCClient<AppRouter>({ links: [] })
133
+ ```
134
+
135
+ Treat the router type as a compile-time contract, not runtime data. The client must still handle transport, authorization, and server validation failures.
136
+
137
+ ### Verify first
138
+ - Type-check a valid and invalid procedure call at the client boundary.
139
+ - Confirm the router is exported as `typeof appRouter`, not a separately maintained interface.
140
+ - Confirm the cross-boundary import is type-only and does not bundle server-only runtime dependencies.
141
+
142
+ *Source: [Quickstart: client type inference](https://trpc.io/docs/quickstart#2-type-inference--autocomplete) · [Inferring types](https://trpc.io/docs/client/vanilla/infer-types)*
143
+
144
+ ---
145
+
146
+ ## Organize routers around reusable primitives and modules
147
+
148
+ ### Problem / symptom
149
+ One large router mixes initialization, transport setup, authorization logic, and unrelated procedures. Changes create circular imports or inconsistent procedure behavior.
150
+
151
+ ### Fix
152
+ Initialize tRPC once in a dedicated module and export small primitives such as `router`, `publicProcedure`, and `protectedProcedure`. Define feature routers in separate modules, compose them into an `appRouter`, and mount that root router in the transport adapter. This keeps common behavior in base procedures and procedure ownership in feature modules.
153
+
154
+ ```ts
155
+ // trpc.ts: initialization and reusable base procedures
156
+ // item-router.ts: item procedures
157
+ // app-router.ts: router({ item: itemRouter }) and AppRouter export
158
+ // http.ts: adapter, appRouter, createContext, onError
159
+ ```
160
+
161
+ ### Verify first
162
+ - Confirm there is a single `initTRPC...create()` initialization per server application.
163
+ - Confirm router composition does not import the HTTP adapter back into feature routers.
164
+ - Confirm common authentication behavior is inherited through base procedures, not copied per resolver.
165
+
166
+ *Source: [Quickstart: recommended separation](https://trpc.io/docs/quickstart#installation) · [Reusable base procedures](https://trpc.io/docs/server/procedures#reusable-base-procedures)*
167
+
168
+ ---
169
+
170
+ ## Make batching and transport observable
171
+
172
+ ### Problem / symptom
173
+ Requests appear slow or fail with URI/payload errors, but telemetry only shows an HTTP request and cannot identify the individual operations hidden inside a batch.
174
+
175
+ ### Fix
176
+ `httpBatchLink` batches individual operations into one HTTP request. Instrument both layers: record operation path/type/duration/errors through tRPC error handling or links, and record HTTP status, batch size, payload/URL limits, and request timing at the transport boundary. Set compatible limits deliberately.
177
+
178
+ ```ts
179
+ httpBatchLink({
180
+ url: '/trpc',
181
+ maxItems: 10,
182
+ maxURLLength: 2083,
183
+ })
184
+
185
+ // Configure the adapter with a matching or higher maxBatchSize.
186
+ ```
187
+
188
+ A client `maxItems` value at or below the server `maxBatchSize` splits large batches before the server rejects them. If per-operation transport isolation is required, use `httpLink` instead of batch transport.
189
+
190
+ ### Verify first
191
+ - Use concurrent calls and confirm the expected number of HTTP requests and operations per batch.
192
+ - Exercise a batch above the configured limit; verify the client splits it rather than receiving a server rejection.
193
+ - Inspect observability data for a single failed operation in a mixed batch.
194
+
195
+ *Source: [HTTP batch link](https://trpc.io/docs/client/links/httpBatchLink) · [Adapter `onError`](https://trpc.io/docs/server/error-handling#handling-errors)*
196
+
197
+ ---
198
+
199
+ ## Keep tRPC packages version-aligned
200
+
201
+ ### Problem / symptom
202
+ Client and server compile against different tRPC API generations, causing missing exports, incompatible types, or transport behavior that differs from the selected documentation.
203
+
204
+ ### Fix
205
+ Review every installed `@trpc/*` package used by the server and client together during an upgrade. Keep their versions compatible with the same tRPC documentation generation, update lockfiles in the same change, and run type-checking across both boundaries. The current official documentation cited here is v11.
206
+
207
+ ### Verify first
208
+ - List resolved versions of `@trpc/server`, `@trpc/client`, and any installed framework integration packages.
209
+ - Confirm no duplicate incompatible tRPC versions are resolved where a shared type contract is expected.
210
+ - Type-check server and client after dependency resolution, then exercise one query and one mutation over the configured transport.
211
+
212
+ *Source: [tRPC v11 Quickstart](https://trpc.io/docs/quickstart) · [tRPC documentation](https://trpc.io/docs)*