@jgamaraalv/ts-dev-kit 2.1.0 → 2.3.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.
@@ -0,0 +1,272 @@
1
+ # TanStack Query v5 — SSR & Next.js
2
+
3
+ ## Table of Contents
4
+ - [Core Concept](#core-concept)
5
+ - [Next.js Pages Router](#nextjs-pages-router)
6
+ - [Next.js App Router](#nextjs-app-router)
7
+ - [Streaming](#streaming)
8
+ - [Critical Gotchas](#critical-gotchas)
9
+
10
+ ## Core Concept
11
+
12
+ Server rendering with TanStack Query follows three steps:
13
+ 1. **Prefetch** data on the server with `queryClient.prefetchQuery()`
14
+ 2. **Dehydrate** the cache with `dehydrate(queryClient)`
15
+ 3. **Hydrate** on the client with `<HydrationBoundary state={dehydratedState}>`
16
+
17
+ ## Next.js Pages Router
18
+
19
+ ### getStaticProps / getServerSideProps
20
+
21
+ ```tsx
22
+ import { dehydrate, QueryClient } from '@tanstack/react-query'
23
+
24
+ export async function getStaticProps() {
25
+ const queryClient = new QueryClient()
26
+
27
+ await queryClient.prefetchQuery({
28
+ queryKey: ['posts'],
29
+ queryFn: getPosts,
30
+ })
31
+
32
+ return {
33
+ props: {
34
+ dehydratedState: dehydrate(queryClient),
35
+ },
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### _app.tsx setup
41
+
42
+ ```tsx
43
+ import { HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query'
44
+ import { useState } from 'react'
45
+
46
+ export default function MyApp({ Component, pageProps }) {
47
+ const [queryClient] = useState(() => new QueryClient({
48
+ defaultOptions: {
49
+ queries: { staleTime: 60 * 1000 },
50
+ },
51
+ }))
52
+
53
+ return (
54
+ <QueryClientProvider client={queryClient}>
55
+ <HydrationBoundary state={pageProps.dehydratedState}>
56
+ <Component {...pageProps} />
57
+ </HydrationBoundary>
58
+ </QueryClientProvider>
59
+ )
60
+ }
61
+ ```
62
+
63
+ ### Page component — uses useQuery normally
64
+
65
+ ```tsx
66
+ export default function PostsPage() {
67
+ const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })
68
+ // data is immediately available from the hydrated cache
69
+ }
70
+ ```
71
+
72
+ ## Next.js App Router
73
+
74
+ ### Provider setup (app/providers.tsx)
75
+
76
+ ```tsx
77
+ 'use client'
78
+
79
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
80
+ import { useState } from 'react'
81
+
82
+ function makeQueryClient() {
83
+ return new QueryClient({
84
+ defaultOptions: {
85
+ queries: {
86
+ staleTime: 60 * 1000, // must be > 0 for SSR
87
+ },
88
+ },
89
+ })
90
+ }
91
+
92
+ let browserQueryClient: QueryClient | undefined
93
+
94
+ function getQueryClient() {
95
+ if (typeof window === 'undefined') {
96
+ // Server: always make a new query client
97
+ return makeQueryClient()
98
+ }
99
+ // Browser: reuse singleton
100
+ if (!browserQueryClient) browserQueryClient = makeQueryClient()
101
+ return browserQueryClient
102
+ }
103
+
104
+ export default function Providers({ children }: { children: React.ReactNode }) {
105
+ const queryClient = getQueryClient()
106
+
107
+ return (
108
+ <QueryClientProvider client={queryClient}>
109
+ {children}
110
+ </QueryClientProvider>
111
+ )
112
+ }
113
+ ```
114
+
115
+ **Why not `useState`?** If there is no Suspense boundary between the provider and suspending code, React throws away state on initial suspend. The singleton pattern avoids losing the client.
116
+
117
+ ### Root layout (app/layout.tsx)
118
+
119
+ ```tsx
120
+ import Providers from './providers'
121
+
122
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
123
+ return (
124
+ <html>
125
+ <body>
126
+ <Providers>{children}</Providers>
127
+ </body>
128
+ </html>
129
+ )
130
+ }
131
+ ```
132
+
133
+ ### Prefetch in Server Components
134
+
135
+ ```tsx
136
+ // app/posts/page.tsx (Server Component)
137
+ import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
138
+ import Posts from './posts' // Client Component
139
+
140
+ export default async function PostsPage() {
141
+ const queryClient = new QueryClient()
142
+
143
+ await queryClient.prefetchQuery({
144
+ queryKey: ['posts'],
145
+ queryFn: getPosts,
146
+ })
147
+
148
+ return (
149
+ <HydrationBoundary state={dehydrate(queryClient)}>
150
+ <Posts />
151
+ </HydrationBoundary>
152
+ )
153
+ }
154
+ ```
155
+
156
+ ```tsx
157
+ // app/posts/posts.tsx (Client Component)
158
+ 'use client'
159
+
160
+ import { useQuery } from '@tanstack/react-query'
161
+
162
+ export default function Posts() {
163
+ const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })
164
+ // data is available immediately from hydration
165
+ }
166
+ ```
167
+
168
+ ### Nested prefetching
169
+
170
+ Multiple `<HydrationBoundary>` with separate queryClients at different levels is fine. Deeply nested Server Components can each prefetch their own data.
171
+
172
+ ### Shared queryClient with React.cache
173
+
174
+ ```tsx
175
+ import { cache } from 'react'
176
+
177
+ const getQueryClient = cache(() => new QueryClient())
178
+
179
+ // Any Server Component in the same request:
180
+ export default async function Page() {
181
+ const queryClient = getQueryClient()
182
+ await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: getPosts })
183
+ return (
184
+ <HydrationBoundary state={dehydrate(queryClient)}>
185
+ <Posts />
186
+ </HydrationBoundary>
187
+ )
188
+ }
189
+ ```
190
+
191
+ Downside: each `dehydrate()` serializes the entire cache, including previously serialized queries.
192
+
193
+ ## Streaming
194
+
195
+ Available since v5.40.0. Pending queries can be dehydrated and streamed without `await`.
196
+
197
+ ### Setup — configure dehydration to include pending queries
198
+
199
+ ```tsx
200
+ function makeQueryClient() {
201
+ return new QueryClient({
202
+ defaultOptions: {
203
+ queries: { staleTime: 60 * 1000 },
204
+ dehydrate: {
205
+ shouldDehydrateQuery: (query) =>
206
+ defaultShouldDehydrateQuery(query) || query.state.status === 'pending',
207
+ },
208
+ },
209
+ })
210
+ }
211
+ ```
212
+
213
+ ### Stream prefetch (no await)
214
+
215
+ ```tsx
216
+ // app/posts/page.tsx
217
+ export default function PostsPage() {
218
+ const queryClient = new QueryClient()
219
+
220
+ // No await — query starts but doesn't block rendering
221
+ queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: getPosts })
222
+
223
+ return (
224
+ <HydrationBoundary state={dehydrate(queryClient)}>
225
+ <Posts />
226
+ </HydrationBoundary>
227
+ )
228
+ }
229
+ ```
230
+
231
+ On the client, use `useSuspenseQuery` to consume the streamed promise:
232
+
233
+ ```tsx
234
+ 'use client'
235
+ export default function Posts() {
236
+ const { data } = useSuspenseQuery({ queryKey: ['posts'], queryFn: getPosts })
237
+ // Suspends until streamed data arrives, then renders
238
+ }
239
+ ```
240
+
241
+ ### Non-JSON serialization
242
+
243
+ For data with Dates, Maps, etc., configure custom serialization:
244
+
245
+ ```tsx
246
+ defaultOptions: {
247
+ dehydrate: { serializeData: superjson.serialize },
248
+ hydrate: { deserializeData: superjson.deserialize },
249
+ }
250
+ ```
251
+
252
+ ## Critical Gotchas
253
+
254
+ 1. **Never create QueryClient at module scope** — it leaks data between requests/users:
255
+ ```tsx
256
+ // WRONG
257
+ const queryClient = new QueryClient()
258
+ export default function App() { ... }
259
+
260
+ // CORRECT
261
+ const [queryClient] = useState(() => new QueryClient())
262
+ ```
263
+
264
+ 2. **Set `staleTime > 0` for SSR** — with default `staleTime: 0`, the client immediately refetches on mount, negating the prefetch.
265
+
266
+ 3. **Don't use `fetchQuery` in Server Components to render data** — if both a Server Component and a Client Component use the same data, they desync when the client revalidates. Use `prefetchQuery` (which only populates the cache) and let client components own the data rendering.
267
+
268
+ 4. **`hydrate` only overwrites if incoming data is newer** — checked by timestamp.
269
+
270
+ 5. **`Error` objects and `undefined` are not JSON-serializable** — handle serialization manually if dehydrating errors. By default, errors are redacted (replaced with `null`).
271
+
272
+ 6. **Data ownership rule:** The component that renders the data should own it. Server Components prefetch; Client Components consume via `useQuery`/`useSuspenseQuery`.
@@ -0,0 +1,175 @@
1
+ # TanStack Query v5 — Testing
2
+
3
+ ## Setup
4
+
5
+ Use `@testing-library/react` (v14+) directly — no need for `@testing-library/react-hooks`.
6
+
7
+ ### Create a wrapper with isolated QueryClient
8
+
9
+ ```tsx
10
+ import { renderHook, waitFor } from '@testing-library/react'
11
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
12
+ import type { ReactNode } from 'react'
13
+
14
+ function createTestQueryClient() {
15
+ return new QueryClient({
16
+ defaultOptions: {
17
+ queries: {
18
+ retry: false, // CRITICAL: disable retries in tests
19
+ },
20
+ },
21
+ })
22
+ }
23
+
24
+ function createWrapper() {
25
+ const queryClient = createTestQueryClient()
26
+ return function Wrapper({ children }: { children: ReactNode }) {
27
+ return (
28
+ <QueryClientProvider client={queryClient}>
29
+ {children}
30
+ </QueryClientProvider>
31
+ )
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## Testing a Custom Hook
37
+
38
+ ```tsx
39
+ // hooks/useTodos.ts
40
+ export function useTodos() {
41
+ return useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
42
+ }
43
+
44
+ // hooks/useTodos.test.ts
45
+ import { renderHook, waitFor } from '@testing-library/react'
46
+ import { useTodos } from './useTodos'
47
+
48
+ test('fetches todos', async () => {
49
+ const { result } = renderHook(() => useTodos(), {
50
+ wrapper: createWrapper(),
51
+ })
52
+
53
+ await waitFor(() => expect(result.current.isSuccess).toBe(true))
54
+ expect(result.current.data).toEqual([{ id: 1, title: 'Test' }])
55
+ })
56
+ ```
57
+
58
+ ## Network Mocking
59
+
60
+ ### With nock
61
+
62
+ ```tsx
63
+ import nock from 'nock'
64
+
65
+ test('fetches data from API', async () => {
66
+ nock('http://example.com')
67
+ .get('/api/todos')
68
+ .reply(200, [{ id: 1, title: 'Test' }])
69
+
70
+ const { result } = renderHook(() => useTodos(), {
71
+ wrapper: createWrapper(),
72
+ })
73
+
74
+ await waitFor(() => expect(result.current.isSuccess).toBe(true))
75
+ expect(result.current.data).toEqual([{ id: 1, title: 'Test' }])
76
+ })
77
+ ```
78
+
79
+ ### With msw (recommended)
80
+
81
+ ```tsx
82
+ import { setupServer } from 'msw/node'
83
+ import { http, HttpResponse } from 'msw'
84
+
85
+ const server = setupServer(
86
+ http.get('/api/todos', () =>
87
+ HttpResponse.json([{ id: 1, title: 'Test' }])
88
+ ),
89
+ )
90
+
91
+ beforeAll(() => server.listen())
92
+ afterEach(() => server.resetHandlers())
93
+ afterAll(() => server.close())
94
+ ```
95
+
96
+ ## Testing Infinite Queries
97
+
98
+ ```tsx
99
+ import nock from 'nock'
100
+
101
+ test('fetches multiple pages', async () => {
102
+ const expectation = nock('http://example.com')
103
+ .persist() // .persist() for multiple requests
104
+ .query(true)
105
+ .get('/api/items')
106
+ .reply(200, (uri) => {
107
+ const url = new URL(`http://example.com${uri}`)
108
+ const page = url.searchParams.get('page') ?? '1'
109
+ return { items: [`item-${page}`], nextPage: Number(page) < 3 ? Number(page) + 1 : null }
110
+ })
111
+
112
+ const { result } = renderHook(() => useInfiniteItems(), {
113
+ wrapper: createWrapper(),
114
+ })
115
+
116
+ await waitFor(() => expect(result.current.isSuccess).toBe(true))
117
+ expect(result.current.data.pages).toHaveLength(1)
118
+
119
+ // Fetch next page
120
+ result.current.fetchNextPage()
121
+
122
+ await waitFor(() => expect(result.current.data.pages).toHaveLength(2))
123
+ })
124
+ ```
125
+
126
+ ## Testing Mutations
127
+
128
+ ```tsx
129
+ test('creates a todo and invalidates list', async () => {
130
+ const queryClient = createTestQueryClient()
131
+ const wrapper = ({ children }: { children: ReactNode }) => (
132
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
133
+ )
134
+
135
+ // Pre-populate cache
136
+ queryClient.setQueryData(['todos'], [{ id: 1, title: 'Existing' }])
137
+
138
+ const { result } = renderHook(() => useCreateTodo(), { wrapper })
139
+
140
+ result.current.mutate({ title: 'New Todo' })
141
+
142
+ await waitFor(() => expect(result.current.isSuccess).toBe(true))
143
+ })
144
+ ```
145
+
146
+ ## Key Gotchas
147
+
148
+ 1. **Always disable retries** — Default 3 retries with exponential backoff causes test timeouts:
149
+ ```tsx
150
+ new QueryClient({ defaultOptions: { queries: { retry: false } } })
151
+ ```
152
+ Note: explicit `retry` on individual queries overrides this default.
153
+
154
+ 2. **Always use `waitFor`** — Query state updates are async. Never assert synchronously after `renderHook`.
155
+
156
+ 3. **Isolate QueryClient per test** — Create a new `QueryClient` for each test to prevent cross-test contamination. Do not share a client across tests.
157
+
158
+ 4. **Jest "did not exit" warning** — If you get this, set `gcTime: Infinity` on the test QueryClient to prevent garbage collection timers from hanging:
159
+ ```tsx
160
+ new QueryClient({
161
+ defaultOptions: {
162
+ queries: { retry: false, gcTime: Infinity },
163
+ },
164
+ })
165
+ ```
166
+
167
+ 5. **Component testing** — For testing components (not just hooks), render the component inside the wrapper:
168
+ ```tsx
169
+ import { render, screen, waitFor } from '@testing-library/react'
170
+
171
+ test('renders todo list', async () => {
172
+ render(<TodoList />, { wrapper: createWrapper() })
173
+ await waitFor(() => expect(screen.getByText('Test Todo')).toBeInTheDocument())
174
+ })
175
+ ```
@@ -108,8 +108,7 @@ Match skills to the sub-area identified in domain_areas:
108
108
  **Cross-cutting** → combine skills from each sub-area involved.
109
109
  </skill_map>
110
110
 
111
- In SINGLE-ROLE mode: call each skill yourself before starting phase 4.
112
- In MULTI-ROLE mode: include explicit Skill() call instructions in each subagent prompt (see references/agent-dispatch.md).
111
+ In ALL execution modes: include explicit Skill() call instructions in each subagent prompt (see references/agent-dispatch.md). The orchestrator does not need to load skills itself — agents load them before writing code.
113
112
  </required_skills>
114
113
 
115
114
  <available_mcps>
@@ -235,24 +234,28 @@ Constraints:
235
234
  <execution_mode_decision>
236
235
  At the end of phase 2, make an explicit execution mode decision and state it to the user:
237
236
 
238
- > **EXECUTION MODE: SINGLE-ROLE** — I will implement all changes directly.
237
+ > **EXECUTION MODE: SINGLE-ROLE** — Single domain. I will dispatch 1-2 focused agents for implementation.
239
238
 
240
239
  OR
241
240
 
242
- > **EXECUTION MODE: MULTI-ROLE** — I will act as orchestrator, dispatching specialized agents via the Task tool.
241
+ > **EXECUTION MODE: MULTI-ROLE** — Multiple domains. I will dispatch specialized agents across domains via the Task tool.
243
242
 
244
243
  OR
245
244
 
246
245
  > **EXECUTION MODE: PLAN** — The task is highly complex. I will enter plan mode to design a structured implementation plan before executing.
247
246
 
247
+ **CRITICAL: In ALL execution modes, the orchestrator (main session) NEVER writes application code directly.** All implementation — components, hooks, pages, routes, services, tests — is delegated to agents via the Task tool. The orchestrator's role is: context gathering, agent dispatch, output review, integration glue (under 15 lines), and quality gates.
248
+
249
+ The difference between SINGLE-ROLE and MULTI-ROLE is decomposition complexity, NOT whether agents are used:
250
+ - **SINGLE-ROLE**: simpler decomposition (1-2 agents, same domain). Use when the task is contained within one package or domain.
251
+ - **MULTI-ROLE**: complex decomposition (3+ agents, multiple domains). Use when the task spans packages or skill sets.
252
+
248
253
  Use PLAN mode when:
249
254
  - The task has 4+ distinct roles or implementation phases.
250
255
  - The scope is large enough that context window management becomes a concern.
251
256
  - The task benefits from upfront architectural planning before any code is written.
252
257
 
253
- In PLAN mode: use EnterPlanMode to design the full plan. Once the user approves it, exit plan mode and execute phases sequentially as MULTI-ROLE orchestrator, with context cleanup between major phases when needed.
254
-
255
- Follow this decision in phase 4. In MULTI-ROLE and PLAN modes, delegate application code to agents — your job is dispatch, review, integration, and quality gates.
258
+ In PLAN mode: use EnterPlanMode to design the full plan. Once the user approves it, exit plan mode and execute phases sequentially as orchestrator, with context cleanup between major phases when needed.
256
259
  </execution_mode_decision>
257
260
  </phase_2b_multi_role_decomposition>
258
261
 
@@ -290,64 +293,62 @@ Follow this decision in phase 4. In MULTI-ROLE and PLAN modes, delegate applicat
290
293
  </phase_3_task_analysis>
291
294
 
292
295
  <phase_3b_baseline_capture>
293
- Run the verification plan before writing any code to establish the baseline for comparison.
296
+ **MANDATORY.** Run the verification plan before writing any code to establish the baseline for comparison. Do NOT skip this phase.
297
+
298
+ **Step 1: Standard quality gates** — run and record results (pass/fail, counts, bundle sizes).
294
299
 
295
- 1. Run standard quality gates and record results (pass/fail, counts, bundle sizes).
296
- 2. Run domain-specific checks from the verification plan:
297
- - **Frontend**: if playwright/chrome-devtools MCPs are available, navigate to affected pages, capture screenshots, and measure performance (LCP, load time). Otherwise, record build output and bundle sizes.
298
- - **Backend**: execute requests to affected endpoints (via curl or available API MCPs) and record response status, payload shape, and timing.
299
- - **Database**: record current schema state for affected tables.
300
- 3. Store all baseline values — these are compared against post-change results in phase 5b.
300
+ **Step 2: MCP-based checks** follow this decision tree in order:
301
301
 
302
- If testing MCPs are not available, skip those checks and note it:
303
- > **Baseline captured.** MCP-based visual/performance checks skipped no browser MCPs available.
302
+ 1. Use ToolSearch to confirm which browser MCPs are available (playwright, chrome-devtools, or neither).
303
+ 2. **If browser MCPs are available AND the task touches frontend pages:**
304
+ a. Check if the dev server is running (attempt to navigate to `localhost` or the configured URL).
305
+ b. **If dev server is accessible:** navigate to each affected page, capture screenshots of key states, and measure performance (LCP, load time). Use Chrome DevTools traces or Playwright screenshots as appropriate.
306
+ c. **If dev server is NOT accessible:** ask the user whether to start it or skip visual checks. Do NOT silently skip — the user must confirm.
307
+ 3. **If no browser MCPs are available:** note it explicitly and proceed with shell-only checks (build output, bundle sizes).
308
+ 4. **Backend tasks:** execute requests to affected endpoints (via curl or available API MCPs) and record response status, payload shape, and timing.
309
+ 5. **Database tasks:** record current schema state for affected tables.
304
310
 
305
- In MULTI-ROLE mode, the orchestrator runs baseline capture before dispatching any agents.
311
+ **Step 3: Store baseline** all values captured here are compared against post-change results in phase 5b.
312
+
313
+ When visual/performance checks are skipped, state the reason:
314
+ > **Baseline captured.** MCP-based visual checks skipped — [reason: no browser MCPs available | dev server not running (user confirmed skip) | no frontend pages affected].
315
+
316
+ The orchestrator ALWAYS runs baseline capture before dispatching any agents, regardless of execution mode.
306
317
  </phase_3b_baseline_capture>
307
318
 
308
319
  <phase_4_execution>
309
- Before writing any code, check the execution mode decision from phase 2.
320
+ **CRITICAL: The orchestrator (main session) NEVER writes application code.** All implementation is dispatched to agents via the Task tool. The orchestrator may only write trivial glue (under 15 lines total): barrel file exports, small wiring imports, or config one-liners.
310
321
 
311
- **MULTI-ROLE Follow <multi_role_orchestration> below.**
312
- **SINGLE-ROLE → Follow <single_role_implementation> below.**
322
+ Before dispatching, check the execution mode decision from phase 2 to determine decomposition complexity.
313
323
 
314
- <multi_role_orchestration>
315
- As orchestrator, dispatch agents, review their output, and verify integration. Do not implement application code yourself.
324
+ <agent_dispatch_protocol>
325
+ This protocol applies to ALL execution modes (SINGLE-ROLE, MULTI-ROLE, and PLAN).
316
326
 
317
- You may write code directly only for trivial glue (under 15 lines total):
318
- - Adding an export line to a barrel file
319
- - Adding a small schema to the shared package that multiple agents need
320
- - Wiring an import in a top-level file after agents complete
327
+ As orchestrator, your responsibilities are: context gathering, agent dispatch, output review, integration glue, and quality gates. You do NOT write application code (components, hooks, pages, routes, services, tests).
321
328
 
322
- Everything else should be delegated to an agent. For the agent prompt template and dispatch details, see references/agent-dispatch.md.
323
-
324
- Dispatch steps:
325
- 1. Create TaskCreate entries for each role.
329
+ **Dispatch steps:**
330
+ 1. Create TaskCreate entries for each role to track progress.
326
331
  2. For each role, dispatch a specialized agent via the Task tool with a self-contained prompt. Set the `model` parameter according to rule_4_model_selection.
327
332
  3. Launch independent agents in parallel. Launch dependent agents sequentially.
328
333
  4. Each agent runs its own quality gates before reporting completion. Review the agent's output and gate results before dispatching dependents.
329
334
  5. After all agents complete, proceed to phase 5 for the final cross-package quality gates.
330
335
 
331
- If you find yourself creating application files (routes, components, services, hooks, tests) while in MULTI-ROLE mode, delegate to an agent instead.
332
- </multi_role_orchestration>
336
+ For the agent prompt template and dispatch details, see references/agent-dispatch.md.
333
337
 
334
- <single_role_implementation>
335
- Think through each step before acting. Share your reasoning at key decision points.
338
+ **Self-check:** If you find yourself creating application files (routes, components, services, hooks, tests, pages), STOP and delegate to an agent instead.
339
+ </agent_dispatch_protocol>
336
340
 
337
341
  <build_order>
338
- Work from micro to macro — build dependencies before dependents:
339
- 1. Shared code first — new constants, types, schemas, or enums needed by multiple packages go in the shared/common package (discover its location from the project structure).
342
+ Instruct agents to work from micro to macro — build dependencies before dependents:
343
+ 1. Shared code first — new constants, types, schemas, or enums needed by multiple packages go in the shared/common package.
340
344
  2. Check for reuse — before creating a helper, hook, component, or utility, search the codebase for existing code that can be used or extended.
341
345
  3. Implement the core change — build the feature/fix in the target package.
342
346
  4. Wire it together — connect the pieces across packages if needed.
343
347
 
344
- Decision tree:
348
+ Decision tree (include in agent prompts when relevant):
345
349
  - Is this code used by multiple modules? YES → Create in the shared/common package.
346
350
  - Is this code used by multiple modules? NO → Is this component multi-file? YES → Create folder with index.tsx + related files. NO → Single file, co-located with usage.
347
351
  </build_order>
348
-
349
- If you created any temporary files or scripts for iteration, remove them at the end.
350
- </single_role_implementation>
351
352
  </phase_4_execution>
352
353
 
353
354
  <phase_5_quality_gates>