@ai-support-agent/cli 0.1.32 → 0.1.33-beta.1

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 (74) hide show
  1. package/dist/alert-processor.d.ts +0 -8
  2. package/dist/alert-processor.d.ts.map +1 -1
  3. package/dist/alert-processor.js +5 -73
  4. package/dist/alert-processor.js.map +1 -1
  5. package/dist/api-client.d.ts +0 -4
  6. package/dist/api-client.d.ts.map +1 -1
  7. package/dist/api-client.js +0 -4
  8. package/dist/api-client.js.map +1 -1
  9. package/dist/commands/claude-code-args.d.ts +1 -0
  10. package/dist/commands/claude-code-args.d.ts.map +1 -1
  11. package/dist/commands/claude-code-args.js +3 -0
  12. package/dist/commands/claude-code-args.js.map +1 -1
  13. package/dist/commands/claude-code-runner.d.ts.map +1 -1
  14. package/dist/commands/claude-code-runner.js +2 -1
  15. package/dist/commands/claude-code-runner.js.map +1 -1
  16. package/dist/commands/plugin-dir.d.ts +20 -0
  17. package/dist/commands/plugin-dir.d.ts.map +1 -0
  18. package/dist/commands/plugin-dir.js +71 -0
  19. package/dist/commands/plugin-dir.js.map +1 -0
  20. package/dist/constants.d.ts +0 -1
  21. package/dist/constants.d.ts.map +1 -1
  22. package/dist/constants.js +0 -1
  23. package/dist/constants.js.map +1 -1
  24. package/dist/plugin/.claude-plugin/plugin.json +9 -0
  25. package/dist/plugin/LICENSE +26 -0
  26. package/dist/plugin/README.md +86 -0
  27. package/dist/plugin/SYNC.md +113 -0
  28. package/dist/plugin/agents/build-error-resolver.md +159 -0
  29. package/dist/plugin/agents/code-reviewer.md +277 -0
  30. package/dist/plugin/agents/django-reviewer.md +159 -0
  31. package/dist/plugin/agents/infra-reviewer.md +172 -0
  32. package/dist/plugin/agents/investigator.md +75 -0
  33. package/dist/plugin/agents/nextjs-reviewer.md +191 -0
  34. package/dist/plugin/agents/php-reviewer.md +167 -0
  35. package/dist/plugin/agents/planner.md +184 -0
  36. package/dist/plugin/agents/python-reviewer.md +161 -0
  37. package/dist/plugin/agents/react-reviewer.md +150 -0
  38. package/dist/plugin/agents/silent-failure-hunter.md +158 -0
  39. package/dist/plugin/agents/typescript-reviewer.md +179 -0
  40. package/dist/plugin/agents/ui-reviewer.md +203 -0
  41. package/dist/plugin/commands/add-feature.md +301 -0
  42. package/dist/plugin/commands/build-fix.md +47 -0
  43. package/dist/plugin/commands/code-review.md +228 -0
  44. package/dist/plugin/commands/fix-defect.md +393 -0
  45. package/dist/plugin/commands/learn-eval.md +94 -0
  46. package/dist/plugin/commands/learn.md +84 -0
  47. package/dist/plugin/commands/plan.md +211 -0
  48. package/dist/plugin/commands/test-coverage.md +64 -0
  49. package/dist/plugin/commands/update-docs.md +98 -0
  50. package/dist/plugin/hooks/hooks.json +59 -0
  51. package/dist/plugin/hooks/scripts/auto-format.sh +63 -0
  52. package/dist/plugin/hooks/scripts/check-secrets-before-commit.sh +50 -0
  53. package/dist/plugin/hooks/scripts/guard-dangerous-commands.sh +55 -0
  54. package/dist/plugin/hooks/scripts/on-command-resume.sh +112 -0
  55. package/dist/plugin/hooks/scripts/on-command-stop.sh +200 -0
  56. package/dist/plugin/hooks/scripts/protect-sensitive-files.sh +58 -0
  57. package/dist/plugin/rules/common/coding-guidelines.md +73 -0
  58. package/dist/plugin/rules/documentation/api-docs.md +46 -0
  59. package/dist/plugin/rules/documentation/docs-site.md +60 -0
  60. package/dist/plugin/rules/documentation/source-docs.md +89 -0
  61. package/dist/plugin/rules/documentation/test-docs.md +39 -0
  62. package/dist/plugin/rules/logging/logging-rules.md +83 -0
  63. package/dist/plugin/rules/php/coding-rules.md +40 -0
  64. package/dist/plugin/rules/python/coding-rules.md +40 -0
  65. package/dist/plugin/rules/typescript/coding-rules.md +45 -0
  66. package/dist/plugin/skills/api-design/SKILL.md +269 -0
  67. package/dist/plugin/skills/backend-patterns/SKILL.md +312 -0
  68. package/dist/plugin/skills/database-migrations/SKILL.md +323 -0
  69. package/dist/plugin/skills/docker-patterns/SKILL.md +308 -0
  70. package/dist/plugin/skills/docs-site/SKILL.md +341 -0
  71. package/dist/plugin/skills/e2e-testing/SKILL.md +334 -0
  72. package/dist/plugin/skills/frontend-patterns/SKILL.md +318 -0
  73. package/dist/plugin/skills/integration-testing/SKILL.md +273 -0
  74. package/package.json +2 -2
@@ -0,0 +1,334 @@
1
+ ---
2
+ name: e2e-testing
3
+ description: A pattern collection for Playwright-based E2E testing. Covers how to choose what to test in Next.js/Vue apps, selector strategy, wait strategy, test structure, test data management, flaky test countermeasures, and CI integration. Use when writing new E2E tests, reviewing E2E tests, investigating flaky tests, or analyzing test failures in CI.
4
+ ---
5
+
6
+ # E2E Testing Patterns (Playwright / Next.js / Vue)
7
+
8
+ E2E tests should be "few and elite." This skill assumes Playwright and
9
+ collects patterns — and anti-patterns — for writing E2E tests that stay
10
+ robust and easy to maintain.
11
+
12
+ ## 1. What should you test with E2E?
13
+
14
+ ### Scope it to critical business flows
15
+
16
+ E2E tests are expensive to run and expensive to maintain. Don't aim for
17
+ exhaustive coverage — target only "the flows that would directly hurt the
18
+ business if they broke."
19
+
20
+ Things worth covering:
21
+
22
+ - Login/logout and screen transitions involving authentication
23
+ - Flows that finalize money or data — applications, orders, payments
24
+ - End-to-end paths for major forms: submission through list/detail confirmation
25
+ - Permission-based differences in what's shown or actionable (e.g. admin vs. regular user)
26
+
27
+ Things that should NOT be E2E tests (leave these to unit/component tests):
28
+
29
+ - Every variation of a validation message
30
+ - Pure logic such as date formatting or amount calculations
31
+ - Individual component rendering variations (combinations of props)
32
+ - Fine-grained branches of error handling
33
+
34
+ ### Where to draw the line with unit tests
35
+
36
+ | What you want to verify | Appropriate layer |
37
+ |---|---|
38
+ | Function/class logic | Unit test |
39
+ | Component rendering/events | Component test |
40
+ | API input/output contract | API test |
41
+ | Business flow spanning multiple screens | E2E test |
42
+
43
+ Always ask yourself "can this only be verified through E2E?" Don't push
44
+ something into E2E that could be written at a lower layer.
45
+
46
+ ## 2. Selector strategy
47
+
48
+ ### Priority order
49
+
50
+ Prefer attributes that are visible to the user. Work through this list top to bottom:
51
+
52
+ 1. Role: `page.getByRole('button', { name: 'Save' })`
53
+ 2. Label: `page.getByLabel('Email address')`
54
+ 3. Placeholder/text: `page.getByPlaceholder(...)` / `page.getByText(...)`
55
+ 4. Test ID: `page.getByTestId('order-summary')`
56
+
57
+ Always use role and label when an element can be identified that way —
58
+ it doubles as an accessibility check. Reserve test IDs for elements with
59
+ no visual cue (containers, rows in a dynamic list, etc.).
60
+
61
+ ```typescript
62
+ // Good: identify by the name the user perceives
63
+ await page.getByRole('textbox', { name: 'Email address' }).fill('a@example.com');
64
+ await page.getByRole('button', { name: 'Log in' }).click();
65
+
66
+ // Acceptable: use a test ID for a row with no visual cue
67
+ const row = page.getByTestId('user-row-123');
68
+ await row.getByRole('button', { name: 'Edit' }).click();
69
+ ```
70
+
71
+ ### No selectors that depend on CSS structure
72
+
73
+ Selectors that depend on DOM structure or framework-generated class names
74
+ are forbidden. They break easily during refactors, and when they break
75
+ it's not obvious why.
76
+
77
+ ```typescript
78
+ // Forbidden: depends on structure / auto-generated classes
79
+ await page.locator('div.container > div:nth-child(2) > button').click();
80
+ await page.locator('.css-1q2w3e4').click(); // CSS-in-JS generated class
81
+ await page.locator('#app > main span').first().click(); // depends on ordering
82
+ ```
83
+
84
+ This also applies to attributes generated by Vue's scoped CSS
85
+ (`data-v-xxxxx`) and to Next.js/Tailwind utility classes — don't depend on
86
+ those either.
87
+
88
+ ## 3. Wait strategy
89
+
90
+ ### Trust auto-waiting and web-first assertions
91
+
92
+ Playwright's locator actions and `expect(locator)`-style assertions
93
+ automatically retry until the element is actionable or the condition
94
+ holds. Rely on this by default.
95
+
96
+ ```typescript
97
+ // Good: an assertion that retries automatically
98
+ await expect(page.getByRole('heading', { name: 'Order complete' })).toBeVisible();
99
+ await expect(page.getByTestId('cart-count')).toHaveText('3');
100
+
101
+ // Forbidden: a fixed sleep. Flaky on slow environments, wasteful on fast ones
102
+ await page.waitForTimeout(3000);
103
+
104
+ // Forbidden: an assertion evaluated immediately, with no retry — vulnerable to races
105
+ expect(await page.getByTestId('cart-count').textContent()).toBe('3');
106
+ ```
107
+
108
+ Also avoid depending on `networkidle`. In Next.js/Vue apps that use
109
+ polling or SSE, the network may never go idle — or idle may trigger too
110
+ early. What you should wait for isn't network silence, but the result
111
+ that's visible to the user.
112
+
113
+ ### Arm response waits before the action, not after
114
+
115
+ When you need to wait for a specific response, start `waitForResponse`
116
+ *before* the click that triggers it. If you start waiting afterward, you
117
+ can lose the race and miss it.
118
+
119
+ ```typescript
120
+ // Good: arm the wait first, then perform the action
121
+ const responsePromise = page.waitForResponse(
122
+ (res) => res.url().includes('/api/orders') && res.status() === 201
123
+ );
124
+ await page.getByRole('button', { name: 'Confirm order' }).click();
125
+ await responsePromise;
126
+ await expect(page.getByText('Your order has been received')).toBeVisible();
127
+ ```
128
+
129
+ ## 4. Test structure
130
+
131
+ ### Test independence
132
+
133
+ Every test must be able to run alone, in any order.
134
+
135
+ - Don't carry variables, created data, or login state across tests
136
+ - Set up prerequisite data at the start of each test (or in `beforeEach`) — don't rely on shared fixtures
137
+ - Never let one test's "output" become another test's "input"
138
+ - Avoid `test.describe.serial` as a rule; if a sequence is truly required, fold it into a single test
139
+
140
+ ```typescript
141
+ test.beforeEach(async ({ page }) => {
142
+ // Each test sets up its own prerequisites, independent of other tests' results
143
+ await page.goto('/projects');
144
+ });
145
+
146
+ test('can create a project', async ({ page }) => { /* ... */ });
147
+ test('can search for a project', async ({ page }) => {
148
+ // The data to search for is created within this test (or via API setup)
149
+ });
150
+ ```
151
+
152
+ ### Use the Page Object Model in moderation
153
+
154
+ Use a Page Object to consolidate "a sequence of operations repeated
155
+ across multiple tests." Don't build a giant class that wraps every
156
+ element on the screen.
157
+
158
+ ```typescript
159
+ // Good: a thin Page Object that exposes only business-level operations
160
+ export class LoginPage {
161
+ constructor(private page: Page) {}
162
+
163
+ async login(email: string, password: string) {
164
+ await this.page.goto('/login');
165
+ await this.page.getByLabel('Email address').fill(email);
166
+ await this.page.getByLabel('Password').fill(password);
167
+ await this.page.getByRole('button', { name: 'Log in' }).click();
168
+ }
169
+ }
170
+ ```
171
+
172
+ Signs of over-abstraction (avoid these):
173
+
174
+ - A pile of methods that are each called from only one test
175
+ - Assertions buried inside the Page Object, making failures hard to trace
176
+ - Three layers of wrapper around what could be a single `getByRole` call
177
+
178
+ Only extract a shared helper after you've repeated something two or three
179
+ times. Don't abstract preemptively.
180
+
181
+ ## 5. Test data management
182
+
183
+ ### Generate unique data
184
+
185
+ Fixed-value data collides under parallel execution or retries. Generate a
186
+ value that's unique per run.
187
+
188
+ ```typescript
189
+ // Include a unique identifier for each run
190
+ const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
191
+ const email = `e2e-${unique}@example.com`;
192
+ const projectName = `E2E Test Project-${unique}`;
193
+ ```
194
+
195
+ Prioritize "a design that can't collide" over cleanup after the fact.
196
+ Make sure that even if cleanup fails, it doesn't drag other tests down
197
+ with it.
198
+
199
+ ### Reuse authentication state (storageState)
200
+
201
+ Don't repeat a UI login in every test. Log in once in a setup project,
202
+ save the resulting state, and reuse it across all tests.
203
+
204
+ ```typescript
205
+ // auth.setup.ts: create and save authentication state once
206
+ import { test as setup } from '@playwright/test';
207
+
208
+ setup('save authentication state', async ({ page }) => {
209
+ await page.goto('/login');
210
+ await page.getByLabel('Email address').fill(process.env.E2E_USER!);
211
+ await page.getByLabel('Password').fill(process.env.E2E_PASS!);
212
+ await page.getByRole('button', { name: 'Log in' }).click();
213
+ await page.waitForURL('/dashboard');
214
+ await page.context().storageState({ path: 'playwright/.auth/user.json' });
215
+ });
216
+ ```
217
+
218
+ ```typescript
219
+ // playwright.config.ts: make setup a dependency and reuse the storageState
220
+ projects: [
221
+ { name: 'setup', testMatch: /auth\.setup\.ts/ },
222
+ {
223
+ name: 'chromium',
224
+ use: { storageState: 'playwright/.auth/user.json' },
225
+ dependencies: ['setup'],
226
+ },
227
+ ],
228
+ ```
229
+
230
+ The one exception is testing the login feature itself — that should still
231
+ go through the UI rather than reusing `storageState`.
232
+
233
+ ### Mock external APIs (route)
234
+
235
+ Mock external APIs you don't control (payment providers, maps, other
236
+ external SaaS) with `page.route` to make the test deterministic.
237
+
238
+ ```typescript
239
+ // Mock the external payment API and pin down a success response
240
+ await page.route('**/api/external/payment', async (route) => {
241
+ await route.fulfill({
242
+ status: 200,
243
+ contentType: 'application/json',
244
+ body: JSON.stringify({ status: 'succeeded', id: 'pay_test_001' }),
245
+ });
246
+ });
247
+ ```
248
+
249
+ Don't mock your own backend as a rule. The moment you do, everything
250
+ downstream stops being an E2E test. Only mock things outside your own
251
+ system boundary.
252
+
253
+ ## 6. Flaky test countermeasures
254
+
255
+ ### Symptom-to-cause-to-fix table
256
+
257
+ | Symptom | Typical cause | Fix |
258
+ |---|---|---|
259
+ | Passes locally but fails in CI | Fixed sleeps, dependence on machine speed | Replace with web-first assertions |
260
+ | Element occasionally not found | Evaluated immediately, before rendering completes | Rely on `expect(locator)`'s auto-retry |
261
+ | Fails only under parallel execution | Data collisions between tests | Generate unique data; restore independence |
262
+ | Fails when order changes | Depends on a previous test's result | Have each test set up its own prerequisites |
263
+ | Click doesn't register | Competing with an animation or overlay | Assert the target is stably visible before acting |
264
+ | Can't capture the response you're waiting for | `waitForResponse` armed after the action | Arm the wait before the action |
265
+
266
+ ### Where retries fit in
267
+
268
+ Retries (`retries`) are a safety net for absorbing transient
269
+ infrastructure failures — they are not a "cure" for flakiness. It's fine
270
+ to set something like `retries: 2` in CI, but any test that only passes
271
+ after a retry should be logged as flaky and investigated. "It has
272
+ retries, so we don't need to fix it" is not an acceptable conclusion.
273
+
274
+ ### Quarantining requires a ticket number
275
+
276
+ If root-causing takes time, you may temporarily quarantine a test — but
277
+ only with a ticket number attached. A `skip`/`fixme` with no ticket
278
+ number should be sent back in review.
279
+
280
+ ```typescript
281
+ // Always leave a ticket number and reason. skip without one is forbidden
282
+ test.fixme('can export order history as CSV', async ({ page }) => {
283
+ // TICKET-1234: investigating an issue where the download event isn't captured in CI only
284
+ });
285
+ ```
286
+
287
+ ## 7. CI integration
288
+
289
+ ### Traces and screenshots on failure
290
+
291
+ Always retain the evidence needed to reproduce a failure. Recording
292
+ everything all the time wastes storage and run time, so limit it to
293
+ failures and retries.
294
+
295
+ ```typescript
296
+ // playwright.config.ts: keep evidence only on failure
297
+ export default defineConfig({
298
+ retries: process.env.CI ? 2 : 0,
299
+ use: {
300
+ trace: 'on-first-retry', // record a trace on retry
301
+ screenshot: 'only-on-failure', // screenshot only on failure
302
+ video: 'retain-on-failure', // keep video only on failure
303
+ },
304
+ reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list',
305
+ });
306
+ ```
307
+
308
+ In CI, save the trace and HTML report as artifacts, and investigate
309
+ failures with `npx playwright show-trace` rather than guessing from logs
310
+ alone.
311
+
312
+ ### Parallelism and sharding basics
313
+
314
+ - Control in-machine parallelism with workers (`workers`). If tests are
315
+ truly independent, the default parallel execution works as-is
316
+ - As the test count grows, split across multiple machines:
317
+ `npx playwright test --shard=1/4`
318
+ - If a test starts failing under parallel execution, fix the test's
319
+ independence (data collisions, shared state) rather than lowering
320
+ parallelism
321
+
322
+ ```yaml
323
+ # CI example: matrix run with 4-way sharding
324
+ strategy:
325
+ matrix:
326
+ shard: [1/4, 2/4, 3/4, 4/4]
327
+ steps:
328
+ - run: npx playwright test --shard=${{ matrix.shard }}
329
+ ```
330
+
331
+ ## Related
332
+
333
+ - Use `/test-coverage` to check coverage
334
+ - Use `/code-review` to review implementation and test code
@@ -0,0 +1,318 @@
1
+ ---
2
+ name: frontend-patterns
3
+ description: A pattern collection for component design, state management, performance, forms, error handling, and accessibility in React (Next.js App Router) and Vue.js (Composition API). Use when creating or refactoring frontend components, deciding on a state management or data-fetching approach, or checking what to look for when reviewing frontend code.
4
+ ---
5
+
6
+ # Frontend Development Patterns
7
+
8
+ This guide centers on React (Next.js App Router), with the equivalent Vue.js (Composition API)
9
+ pattern noted alongside each topic. When in doubt, follow the principle of "start simple, add
10
+ complexity only when you actually need it."
11
+
12
+ ## 1. Component Design
13
+
14
+ ### Splitting responsibilities
15
+
16
+ Aim for one responsibility per component. If you can't describe what a component does in a
17
+ single sentence, consider splitting it.
18
+
19
+ - Separate presentation (display) from data fetching and state management (container).
20
+ - In the Next.js App Router, fetch data in Server Components and carve out only the
21
+ interactive parts into small `"use client"` Client Components.
22
+ - In Vue, once the logic inside `setup` starts spanning multiple areas of the screen, split it
23
+ into child components or composables.
24
+
25
+ ### Signs a component has grown too large
26
+
27
+ If several of these apply, it's a signal to split the component:
28
+
29
+ - The file is over 300 lines, or the JSX/template is over 100 lines.
30
+ - Seven or more `useState`/`ref` calls are lined up.
31
+ - Boolean props (`isCompact`, `showHeader`, etc.) keep growing and their conditional branches
32
+ start interacting with each other.
33
+ - The component performs multiple unrelated data fetches.
34
+ - Writing a test for it requires mocking a pile of unrelated functionality.
35
+
36
+ ### Composing with children / slots
37
+
38
+ Express variants through composition rather than piling on flag props.
39
+
40
+ ```tsx
41
+ // Bad: variants expressed as a growing set of flag props
42
+ <Card title="Sales" showIcon iconType="chart" footerText="Details" footerLink="/sales" />
43
+
44
+ // Good: composition via children lets the caller decide the structure
45
+ <Card>
46
+ <Card.Header icon={<ChartIcon />}>Sales</Card.Header>
47
+ <Card.Body>{children}</Card.Body>
48
+ <Card.Footer><Link href="/sales">Details</Link></Card.Footer>
49
+ </Card>
50
+ ```
51
+
52
+ ```vue
53
+ <!-- In Vue, named slots express the same structure -->
54
+ <Card>
55
+ <template #header>Sales</template>
56
+ <template #default>...</template>
57
+ <template #footer><RouterLink to="/sales">Details</RouterLink></template>
58
+ </Card>
59
+ ```
60
+
61
+ ## 2. Reusing Logic (Custom Hooks / Composables)
62
+
63
+ ### When to extract
64
+
65
+ - The same combination of state and effects shows up in two or more places.
66
+ - There's logic you want to unit test independently of the component.
67
+ - You can give it a name that describes what it does (`useDebounce`, `usePagination`, etc.).
68
+
69
+ Conversely, don't force an extraction for logic that's used in only one place and is hard to
70
+ name meaningfully.
71
+
72
+ ```tsx
73
+ // Good: extract the state + effect boilerplate into a hook (React)
74
+ function useDebouncedValue<T>(value: T, delayMs = 300): T {
75
+ const [debounced, setDebounced] = useState(value);
76
+ useEffect(() => {
77
+ const id = setTimeout(() => setDebounced(value), delayMs);
78
+ return () => clearTimeout(id);
79
+ }, [value, delayMs]);
80
+ return debounced;
81
+ }
82
+ ```
83
+
84
+ ```ts
85
+ // Good: the same logic implemented as a Vue composable
86
+ export function useDebouncedValue<T>(source: Ref<T>, delayMs = 300) {
87
+ const debounced = ref(source.value) as Ref<T>;
88
+ let id: ReturnType<typeof setTimeout>;
89
+ watch(source, (v) => {
90
+ clearTimeout(id);
91
+ id = setTimeout(() => (debounced.value = v), delayMs);
92
+ });
93
+ onUnmounted(() => clearTimeout(id));
94
+ return debounced;
95
+ }
96
+ ```
97
+
98
+ - Hooks/composables should not return UI. If the reusable unit involves UI, make it a
99
+ component instead.
100
+ - Return an object so callers can destructure only what they need.
101
+
102
+ ## 3. State Management
103
+
104
+ ### Where state should live: priority order
105
+
106
+ 1. Local state (`useState` / `ref`). Always start here.
107
+ 2. Lift state up to a parent, once it needs to be shared between siblings.
108
+ 3. Context (`createContext` / `provide` and `inject`), once passing props down deeply becomes
109
+ painful.
110
+ 4. A global store (Zustand / Pinia), once it meets the criteria below.
111
+
112
+ ### When a global store is warranted
113
+
114
+ - State that outlives a single screen (authenticated user, theme, cart, etc.).
115
+ - State that must survive route changes without being discarded.
116
+ - State that's read and written by many far-apart components.
117
+
118
+ If the only issue is "props get passed through 2-3 levels," component composition or context
119
+ can usually solve it — that alone isn't a reason to introduce a store.
120
+
121
+ ### Let a data-fetching library own server state
122
+
123
+ Don't hand-roll caching, refetching, and loading-state management for API responses. Delegate
124
+ to SWR or TanStack Query (React Query / Vue Query).
125
+
126
+ ```tsx
127
+ // Bad: manually managing server data with useEffect + a global store
128
+ useEffect(() => {
129
+ fetch("/api/users").then((r) => r.json()).then((d) => store.setUsers(d));
130
+ }, []);
131
+
132
+ // Good: delegate to a data-fetching library (caching, revalidation, and dedup included)
133
+ const { data, error, isLoading } = useQuery({
134
+ queryKey: ["users"],
135
+ queryFn: fetchUsers,
136
+ });
137
+ ```
138
+
139
+ - Reserve the global store for genuinely client-only state.
140
+ - Copying server data into a store means you end up reimplementing freshness tracking and
141
+ invalidation yourself.
142
+
143
+ ## 4. Derived State
144
+
145
+ Values that can be computed from existing state should be derived during render (React) or via
146
+ `computed` (Vue) — don't duplicate them into a separate piece of state via an effect or watcher.
147
+
148
+ ```tsx
149
+ // Bad: duplicating a derived value into separate state via useEffect
150
+ // (lags a render behind and is a common source of bugs)
151
+ const [filtered, setFiltered] = useState<Item[]>([]);
152
+ useEffect(() => {
153
+ setFiltered(items.filter((i) => i.name.includes(query)));
154
+ }, [items, query]);
155
+
156
+ // Good: compute it during render; add useMemo only if profiling shows it's expensive
157
+ const filtered = items.filter((i) => i.name.includes(query));
158
+ ```
159
+
160
+ ```ts
161
+ // Bad: duplicating into a separate ref via watch (Vue)
162
+ watch([items, query], () => {
163
+ filtered.value = items.value.filter((i) => i.name.includes(query.value));
164
+ });
165
+
166
+ // Good: derive it declaratively with computed
167
+ const filtered = computed(() =>
168
+ items.value.filter((i) => i.name.includes(query.value))
169
+ );
170
+ ```
171
+
172
+ - Whenever you see "an effect that updates state B when state A changes," ask first whether B
173
+ can just be a derived value instead.
174
+ - Effects/watchers are legitimately for syncing with external systems (DOM manipulation,
175
+ subscriptions, sending logs, etc.) — not for mirroring state.
176
+
177
+ ## 5. Performance
178
+
179
+ ### Memoization discipline: measure first
180
+
181
+ - Check actual re-render cost with React DevTools Profiler / Vue DevTools before optimizing.
182
+ - Don't reflexively slap `useMemo` / `useCallback` / `memo` on everything — it costs
183
+ readability, and a wrong dependency array turns into a bug.
184
+ - It's worth adding when a computation is measurably expensive, or when referential stability
185
+ matters for a `memo`-ized child or a dependency array.
186
+ - In environments where the React Compiler or Vue's built-in optimizations are effective,
187
+ manual memoization can often be reduced further.
188
+
189
+ ### Code splitting and lazy loading
190
+
191
+ - Route-level splitting is handled automatically by Next.js / Vue Router — rely on that first.
192
+ - Lazy-load heavy components that aren't needed on initial render (modals, charts, editors)
193
+ with `next/dynamic` or `defineAsyncComponent`.
194
+
195
+ ```tsx
196
+ // Good: lazily load a heavy component that's only needed once it's opened
197
+ const ChartPanel = dynamic(() => import("./ChartPanel"), { ssr: false });
198
+ ```
199
+
200
+ ### Virtualizing long lists
201
+
202
+ Don't render every row of a list with hundreds of items — render only the visible range with
203
+ `@tanstack/react-virtual` / `@tanstack/vue-virtual` or similar. Before reaching for
204
+ virtualization, first consider whether pagination or a "load more" pattern can simply reduce
205
+ the item count.
206
+
207
+ ## 6. Forms
208
+
209
+ ### Schema-based validation
210
+
211
+ Define validation rules as a schema (Zod, Valibot, etc.) and wire it into your form library
212
+ (React Hook Form / VeeValidate). Re-validate with the same schema on the server.
213
+
214
+ ```tsx
215
+ // Good: the schema is the single source of truth for validation rules
216
+ const schema = z.object({
217
+ email: z.string().email("Please enter a valid email address"),
218
+ age: z.coerce.number().int().min(18, "Must be 18 or older"),
219
+ });
220
+
221
+ const { register, handleSubmit, formState } = useForm({
222
+ resolver: zodResolver(schema),
223
+ });
224
+ ```
225
+
226
+ - Display error messages near the relevant field and associate them with the input via
227
+ `aria-describedby`.
228
+ - Don't hardcode validation logic inline in JSX or templates.
229
+
230
+ ### Submission state and preventing double submits
231
+
232
+ ```tsx
233
+ // Bad: no submission-state guard, so rapid clicks trigger duplicate POSTs
234
+ <button onClick={() => submit(values)}>Submit</button>
235
+
236
+ // Good: disable the button while submitting and show the state to the user
237
+ const { isSubmitting } = formState;
238
+ <button type="submit" disabled={isSubmitting}>
239
+ {isSubmitting ? "Submitting..." : "Submit"}
240
+ </button>
241
+ ```
242
+
243
+ - Using `useMutation` from React Query / Vue Query for mutations standardizes handling of
244
+ `isPending`, errors, and retries.
245
+ - After a successful submission, reset the form or navigate away so the user isn't left in a
246
+ state where they could resubmit the same data.
247
+
248
+ ## 7. Error Handling
249
+
250
+ ### Error boundaries / onErrorCaptured
251
+
252
+ - React: place an error boundary around each layout region so one failure doesn't blank out
253
+ the whole screen. In the Next.js App Router, add an `error.tsx` per segment.
254
+ - Vue: catch descendant rendering errors in a parent's `onErrorCaptured` and switch to a
255
+ fallback UI.
256
+ - Never swallow the error silently in the boundary — always report it to your error monitoring
257
+ service.
258
+
259
+ ### The three states of a data-fetching UI
260
+
261
+ Always explicitly handle the three states: loading, error, and empty (zero results).
262
+
263
+ ```tsx
264
+ // Good: handle all three states before rendering the real content
265
+ if (isLoading) return <Spinner />;
266
+ if (error) return <ErrorMessage error={error} onRetry={refetch} />;
267
+ if (data.length === 0) return <EmptyState message="No data available" />;
268
+ return <UserList users={data} />;
269
+ ```
270
+
271
+ - Give the error state a retry action (a retry button).
272
+ - Word the empty state so it clearly reads as "not an error," and point toward a next action
273
+ (e.g., create new).
274
+ - To avoid flicker, consider a skeleton UI or `placeholderData` (keeping the previous data
275
+ visible while refetching).
276
+
277
+ ## 8. Accessibility
278
+
279
+ ### Semantic elements
280
+
281
+ - Use `button` / `a` for anything clickable. Don't attach `onClick` to a `div`.
282
+ - Use `a` (`Link`) for navigation/URL changes and `button` for in-place actions.
283
+ - Keep heading levels in order starting from `h1`; don't pick a heading level for its visual
284
+ size.
285
+ - Use landmarks (`main`, `nav`, `header`, `footer`) to convey document structure.
286
+
287
+ ```tsx
288
+ // Bad: onClick on a div (unreachable via keyboard or assistive technology)
289
+ <div onClick={save}>Save</div>
290
+
291
+ // Good: use a native button (focus and Enter/Space work out of the box)
292
+ <button type="button" onClick={save}>Save</button>
293
+ ```
294
+
295
+ ### Labels
296
+
297
+ - Associate every form input with a `label` (`htmlFor` / `for`).
298
+ - Add `aria-label` to icon-only buttons.
299
+ - Image `alt` text should describe the content; use `alt=""` for purely decorative images.
300
+
301
+ ### Keyboard operation basics
302
+
303
+ - Everything should be reachable via Tab, operable with Enter/Space, and modals should close
304
+ with Esc.
305
+ - Don't remove the focus ring via CSS. If you do, provide an alternative visible style.
306
+ - When a modal opens, move focus into it, and restore focus to the triggering element when it
307
+ closes.
308
+ - Before building custom UI, consider whether a native element or a proven headless UI library
309
+ already covers the need.
310
+
311
+ ## Related
312
+
313
+ - For reviews, use the react-reviewer / typescript-reviewer subagents, or `/code-review`.
314
+ - For Next.js-specific concerns (Server/Client boundaries, Server Actions, caching, etc.), use
315
+ the nextjs-reviewer.
316
+ - For usability and consistency (UI/UX) concerns, use the ui-reviewer.
317
+ - Treat this skill as the decision criteria to apply during implementation and refactoring;
318
+ delegate the actual application of review criteria to the subagents listed above.