@baldurpan/create-ai-workflow 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/agents-block.js +70 -0
  4. package/dist/bin.js +14 -0
  5. package/dist/check/markdown.js +47 -0
  6. package/dist/check/parse.js +113 -0
  7. package/dist/check/rules.js +245 -0
  8. package/dist/cli.js +109 -0
  9. package/dist/commands/check.js +41 -0
  10. package/dist/commands/install.js +76 -0
  11. package/dist/commands/standards-add.js +145 -0
  12. package/dist/commands/update.js +214 -0
  13. package/dist/layout.js +115 -0
  14. package/dist/log.js +20 -0
  15. package/dist/manifest.js +53 -0
  16. package/dist/paths.js +41 -0
  17. package/package.json +37 -0
  18. package/templates/blocks/agents-block.md +30 -0
  19. package/templates/claude/agents/planner.agent.md +60 -0
  20. package/templates/claude/agents/reviewer.agent.md +77 -0
  21. package/templates/context/README.md +81 -0
  22. package/templates/context/plan-template.md +69 -0
  23. package/templates/context/plan-template.notes.md +55 -0
  24. package/templates/context/roles/coder.md +76 -0
  25. package/templates/context/workflow.md +143 -0
  26. package/templates/skills/feature-close/SKILL.md +102 -0
  27. package/templates/skills/feature-implement/SKILL.md +142 -0
  28. package/templates/skills/feature-plan/SKILL.md +138 -0
  29. package/templates/skills/feature-status/SKILL.md +86 -0
  30. package/templates/skills/onboard/SKILL.md +122 -0
  31. package/templates/skills/orchestrate/SKILL.md +92 -0
  32. package/templates/skills/roadmap/SKILL.md +97 -0
  33. package/templates/standards/.source +8 -0
  34. package/templates/standards/README.md +104 -0
  35. package/templates/standards/architecture/api-design.md +212 -0
  36. package/templates/standards/architecture/dependency-boundaries.md +104 -0
  37. package/templates/standards/architecture/feature-driven.md +102 -0
  38. package/templates/standards/architecture/folder-structure.md +108 -0
  39. package/templates/standards/architecture/monorepos.md +107 -0
  40. package/templates/standards/architecture/refactoring.md +97 -0
  41. package/templates/standards/architecture/shared-code.md +81 -0
  42. package/templates/standards/docs/PHP-SPEC.md +1195 -0
  43. package/templates/standards/docs/SPEC.md +540 -0
  44. package/templates/standards/examples/bad/data-fetching.tsx +52 -0
  45. package/templates/standards/examples/bad/derived-state.tsx +60 -0
  46. package/templates/standards/examples/bad/feature-component.tsx +80 -0
  47. package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
  48. package/templates/standards/examples/good/data-fetching.tsx +53 -0
  49. package/templates/standards/examples/good/derived-state.tsx +52 -0
  50. package/templates/standards/examples/good/feature-component.tsx +141 -0
  51. package/templates/standards/examples/good/invoice.schema.ts +20 -0
  52. package/templates/standards/examples/good/prop-drilling.tsx +83 -0
  53. package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
  54. package/templates/standards/philosophy/core-principles.md +49 -0
  55. package/templates/standards/philosophy/incremental-abstraction.md +56 -0
  56. package/templates/standards/philosophy/maintainability.md +66 -0
  57. package/templates/standards/philosophy/readability.md +87 -0
  58. package/templates/standards/php/README.md +62 -0
  59. package/templates/standards/php/anti-patterns.md +259 -0
  60. package/templates/standards/php/architecture.md +241 -0
  61. package/templates/standards/php/frameworks.md +197 -0
  62. package/templates/standards/php/rules.md +205 -0
  63. package/templates/standards/php/security.md +190 -0
  64. package/templates/standards/php/testing.md +139 -0
  65. package/templates/standards/php/tooling.md +245 -0
  66. package/templates/standards/react/accessibility.md +130 -0
  67. package/templates/standards/react/anti-patterns.md +187 -0
  68. package/templates/standards/react/component-design.md +139 -0
  69. package/templates/standards/react/error-boundaries.md +195 -0
  70. package/templates/standards/react/forms.md +147 -0
  71. package/templates/standards/react/hooks.md +88 -0
  72. package/templates/standards/react/memoization.md +87 -0
  73. package/templates/standards/react/state-management.md +123 -0
  74. package/templates/standards/react/testing.md +118 -0
  75. package/templates/standards/react/use-effect.md +110 -0
  76. package/templates/standards/security/api-security.md +136 -0
  77. package/templates/standards/security/auth.md +112 -0
  78. package/templates/standards/security/secrets.md +105 -0
  79. package/templates/standards/security/validation.md +89 -0
  80. package/templates/standards/templates/.editorconfig +16 -0
  81. package/templates/standards/templates/.nvmrc +1 -0
  82. package/templates/standards/templates/AGENTS.md +46 -0
  83. package/templates/standards/templates/CLAUDE.md +3 -0
  84. package/templates/standards/templates/_dot_gitignore +50 -0
  85. package/templates/standards/templates/biome.json +53 -0
  86. package/templates/standards/templates/eslint.config.js +61 -0
  87. package/templates/standards/templates/project-readme.md +76 -0
  88. package/templates/standards/templates/pull-request-template.md +46 -0
  89. package/templates/standards/templates/tsconfig.json +35 -0
  90. package/templates/standards/tooling/biome.md +92 -0
  91. package/templates/standards/tooling/ci.md +128 -0
  92. package/templates/standards/tooling/dates.md +194 -0
  93. package/templates/standards/tooling/dependencies.md +90 -0
  94. package/templates/standards/tooling/nx.md +113 -0
  95. package/templates/standards/tooling/observability.md +183 -0
  96. package/templates/standards/tooling/prisma.md +152 -0
  97. package/templates/standards/tooling/shadcn.md +115 -0
  98. package/templates/standards/tooling/tailwind.md +135 -0
  99. package/templates/standards/tooling/tanstack-query.md +214 -0
  100. package/templates/standards/tooling/tanstack-router.md +365 -0
  101. package/templates/standards/tooling/tanstack.md +31 -0
  102. package/templates/standards/tooling/vite.md +135 -0
  103. package/templates/standards/typescript/anti-patterns.md +107 -0
  104. package/templates/standards/typescript/error-handling.md +216 -0
  105. package/templates/standards/typescript/naming.md +78 -0
  106. package/templates/standards/typescript/rules.md +159 -0
  107. package/templates/standards/typescript/tsconfig/base.json +32 -0
  108. package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
  109. package/templates/standards/typescript/tsconfig/react.json +9 -0
  110. package/templates/standards/typescript/validation.md +104 -0
  111. package/templates/stubs/executors.md +49 -0
  112. package/templates/stubs/findings.md +40 -0
  113. package/templates/stubs/history.md +13 -0
  114. package/templates/stubs/roadmap.md +29 -0
  115. package/templates/stubs/stack.md +35 -0
  116. package/templates/stubs/verify.md +48 -0
@@ -0,0 +1,139 @@
1
+ # PHP Testing
2
+
3
+ ## Tooling
4
+
5
+ - **Pest** — preferred for new projects; expressive syntax, great DX
6
+ - **PHPUnit** — established standard; use for legacy or teams already on it
7
+
8
+ Both can coexist in the same project. New tests should follow whichever the codebase uses.
9
+
10
+ ## Test Pyramid
11
+
12
+ Every modern PHP project should have:
13
+
14
+ - **Unit tests** — pure logic, value objects, domain services
15
+ - **Integration tests** — application handlers with real database, queue, cache (in containers)
16
+ - **End-to-end tests** — critical user flows via HTTP
17
+
18
+ Avoid excessive mocking. Integration tests with real (containerized) dependencies catch bugs that mock-heavy unit tests miss.
19
+
20
+ ## Test Behavior, Not Implementation
21
+
22
+ ```php
23
+ // BAD — asserts on method calls (couples test to implementation)
24
+ $user->expects()->method('setStatus')->with('active');
25
+
26
+ // GOOD — asserts on observable behavior
27
+ expect($user->status())->toBe(UserStatus::Active);
28
+ // or PHPUnit:
29
+ $this->assertEquals(UserStatus::Active, $user->status());
30
+ ```
31
+
32
+ When the implementation changes but the behavior doesn't, the test should still pass.
33
+
34
+ ## Pest Example
35
+
36
+ ```php
37
+ <?php declare(strict_types=1);
38
+
39
+ use App\Domain\Order;
40
+ use App\Domain\Money;
41
+
42
+ it('calculates the total with tax', function () {
43
+ $order = new Order(amount: new Money(100, 'USD'));
44
+
45
+ $total = $order->totalWithTax(taxRate: 0.20);
46
+
47
+ expect($total)->toEqual(new Money(120, 'USD'));
48
+ });
49
+
50
+ it('rejects negative amounts', function () {
51
+ expect(fn () => new Money(-1, 'USD'))
52
+ ->toThrow(InvalidArgumentException::class);
53
+ });
54
+ ```
55
+
56
+ ## PHPUnit Example
57
+
58
+ ```php
59
+ <?php declare(strict_types=1);
60
+
61
+ namespace Tests\Domain;
62
+
63
+ use App\Domain\Order;
64
+ use App\Domain\Money;
65
+ use PHPUnit\Framework\TestCase;
66
+
67
+ final class OrderTest extends TestCase
68
+ {
69
+ public function test_calculates_total_with_tax(): void
70
+ {
71
+ $order = new Order(amount: new Money(100, 'USD'));
72
+
73
+ $total = $order->totalWithTax(taxRate: 0.20);
74
+
75
+ $this->assertEquals(new Money(120, 'USD'), $total);
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Integration Tests
81
+
82
+ For integration tests, use real (containerized) dependencies — not mocks:
83
+
84
+ ```php
85
+ it('creates an invoice and emits an event', function () {
86
+ $handler = $this->app->make(CreateInvoiceHandler::class);
87
+
88
+ $invoice = $handler->handle(new CreateInvoiceCommand(
89
+ customerId: $this->testCustomer->id,
90
+ amount: new Money(500, 'USD'),
91
+ ));
92
+
93
+ // Asserts against the real database
94
+ expect(Invoice::find($invoice->id))->not->toBeNull();
95
+
96
+ // Asserts the event was dispatched
97
+ Event::assertDispatched(InvoiceCreated::class);
98
+ });
99
+ ```
100
+
101
+ ## Test Factories
102
+
103
+ Use factories (Laravel) or fixtures (Symfony) for test data — never hand-construct entities row by row:
104
+
105
+ ```php
106
+ $user = User::factory()->create(['status' => UserStatus::Active]);
107
+ $invoices = Invoice::factory()->count(5)->for($user)->create();
108
+ ```
109
+
110
+ ## Coverage Expectations
111
+
112
+ - Domain layer: aim for high coverage (it's pure logic)
113
+ - Application handlers: integration tests covering happy and error paths
114
+ - Infrastructure adapters: minimal — test the adapter contract, not the third-party library
115
+ - Presentation: tested via HTTP-level integration tests, not controller unit tests
116
+
117
+ Coverage percentage is a weak proxy. Prefer "every critical flow has at least one integration test" over "85% coverage."
118
+
119
+ ## DO NOT
120
+
121
+ - Mock things you own — refactor the design instead
122
+ - Use `partialMock`, `Mockery::spy`, or method-call assertions for normal logic
123
+ - Write tests that depend on order
124
+ - Share mutable state between tests
125
+ - Skip writing tests because "it's just a small change"
126
+
127
+ ## PRIORITY
128
+
129
+ ```
130
+ Integration tests with real dependencies > Mock-heavy unit tests
131
+ Behavior assertions > Method-call assertions
132
+ Critical-flow coverage > Coverage percentage
133
+ ```
134
+
135
+ ## See Also
136
+
137
+ - [`rules.md`](rules.md) — typed, testable code by construction
138
+ - [`tooling.md`](tooling.md) — Pest, PHPUnit, CI integration
139
+ - [`../react/testing.md`](../react/testing.md) — same philosophy for the frontend
@@ -0,0 +1,245 @@
1
+ # PHP Tooling
2
+
3
+ ## Core Stack
4
+
5
+ | Tool | Purpose | Required? |
6
+ |---|---|---|
7
+ | **Composer** | Dependency management, PSR-4 autoloading | Yes |
8
+ | **PHPStan** | Static analysis | Yes — level max |
9
+ | **Pint** or **PHP-CS-Fixer** | Formatting | Yes |
10
+ | **Pest** or **PHPUnit** | Testing | Yes |
11
+ | **Rector** | Automated refactors, PHP/framework upgrades | Strongly recommended |
12
+ | **Docker** | Containerized dev environment | Strongly recommended |
13
+ | **Xdebug** | Step debugging | Optional but valuable |
14
+
15
+ ## Composer
16
+
17
+ Never manually `include` files. Use Composer for all autoloading and dependencies.
18
+
19
+ ```bash
20
+ composer install # install from composer.lock
21
+ composer require <pkg> # add a runtime dependency
22
+ composer require --dev <pkg> # dev-only
23
+ composer update # update within composer.json constraints
24
+ ```
25
+
26
+ PSR-4 autoload mapping:
27
+
28
+ ```json
29
+ {
30
+ "autoload": {
31
+ "psr-4": {
32
+ "App\\": "src/"
33
+ }
34
+ },
35
+ "autoload-dev": {
36
+ "psr-4": {
37
+ "Tests\\": "tests/"
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ After changing autoload config, regenerate:
44
+
45
+ ```bash
46
+ composer dump-autoload --optimize
47
+ ```
48
+
49
+ Always commit `composer.lock`.
50
+
51
+ ## PHPStan
52
+
53
+ Static analysis is **mandatory**. Modern PHP without it is incomplete.
54
+
55
+ ```bash
56
+ composer require --dev phpstan/phpstan
57
+
58
+ # Run
59
+ vendor/bin/phpstan analyse
60
+ ```
61
+
62
+ `phpstan.neon` baseline:
63
+
64
+ ```yaml
65
+ parameters:
66
+ level: max
67
+ paths:
68
+ - src
69
+ - tests
70
+ excludePaths:
71
+ - vendor
72
+ ```
73
+
74
+ Target **level max** (level 10 as of PHPStan 2). If you're adopting PHPStan into an existing codebase, generate a baseline to suppress existing errors and improve from there:
75
+
76
+ ```bash
77
+ vendor/bin/phpstan analyse --generate-baseline
78
+ ```
79
+
80
+ Then fix baseline entries over time.
81
+
82
+ ### Useful PHPStan Extensions
83
+
84
+ - `phpstan/phpstan-strict-rules` — additional strictness
85
+ - `phpstan/phpstan-deprecation-rules` — flag deprecated API use
86
+ - Framework extensions: `nunomaduro/larastan` (Laravel), `phpstan/phpstan-symfony`
87
+ - `phpstan/phpstan-phpunit` or `pestphp/pest-plugin-phpstan` for test files
88
+
89
+ ### Psalm Alternative
90
+
91
+ Psalm is an excellent alternative to PHPStan. Pick one; don't run both.
92
+
93
+ ## Pint / PHP-CS-Fixer
94
+
95
+ Pick one formatter and let it own style entirely. Never argue about formatting in PRs.
96
+
97
+ **Pint** (Laravel-flavored, simpler config):
98
+
99
+ ```bash
100
+ composer require --dev laravel/pint
101
+
102
+ vendor/bin/pint # format
103
+ vendor/bin/pint --test # check without writing (CI)
104
+ ```
105
+
106
+ **PHP-CS-Fixer** (more configurable):
107
+
108
+ ```bash
109
+ composer require --dev friendsofphp/php-cs-fixer
110
+
111
+ vendor/bin/php-cs-fixer fix
112
+ vendor/bin/php-cs-fixer fix --dry-run --diff # CI
113
+ ```
114
+
115
+ Both default to PSR-12.
116
+
117
+ ## Testing — Pest or PHPUnit
118
+
119
+ See [`testing.md`](testing.md) for the testing philosophy. Setup:
120
+
121
+ **Pest:**
122
+
123
+ ```bash
124
+ composer require --dev pestphp/pest pestphp/pest-plugin-laravel # if Laravel
125
+ vendor/bin/pest --init
126
+
127
+ vendor/bin/pest
128
+ vendor/bin/pest --coverage
129
+ ```
130
+
131
+ **PHPUnit:**
132
+
133
+ ```bash
134
+ composer require --dev phpunit/phpunit
135
+
136
+ vendor/bin/phpunit
137
+ ```
138
+
139
+ ## Rector
140
+
141
+ Rector automates refactors — PHP version upgrades, framework upgrades, code modernization. Extremely valuable.
142
+
143
+ ```bash
144
+ composer require --dev rector/rector
145
+
146
+ # Config in rector.php (sets paths, rules, php version target)
147
+ vendor/bin/rector process
148
+ vendor/bin/rector process --dry-run # preview without writing
149
+ ```
150
+
151
+ Common rule sets:
152
+ - `LevelSetList::UP_TO_PHP_83`
153
+ - `SetList::CODE_QUALITY`
154
+ - `SetList::DEAD_CODE`
155
+ - `SetList::TYPE_DECLARATION`
156
+
157
+ ## Docker
158
+
159
+ Dockerize local dev. Avoid "works on my machine" setups.
160
+
161
+ Typical `docker-compose.yml` services:
162
+
163
+ ```yaml
164
+ services:
165
+ app:
166
+ build: .
167
+ volumes: ['.:/app']
168
+ nginx:
169
+ image: nginx:alpine
170
+ ports: ['8080:80']
171
+ db:
172
+ image: postgres:16
173
+ environment:
174
+ POSTGRES_PASSWORD: dev
175
+ ports: ['5432:5432']
176
+ redis:
177
+ image: redis:7-alpine
178
+ ports: ['6379:6379']
179
+ ```
180
+
181
+ For Laravel: **Sail** wraps this; **Herd** (macOS) gives a local PHP-FPM + DB stack without containers.
182
+
183
+ ## CI Pipeline (Required Gates)
184
+
185
+ Every PHP repo should run on PR:
186
+
187
+ ```yaml
188
+ # .github/workflows/ci.yml
189
+ name: CI
190
+ on: [pull_request, push]
191
+ jobs:
192
+ check:
193
+ runs-on: ubuntu-latest
194
+ steps:
195
+ - uses: actions/checkout@v4
196
+ - uses: shivammathur/setup-php@v2
197
+ with:
198
+ php-version: '8.3'
199
+ coverage: none
200
+ tools: composer:v2
201
+ - run: composer install --prefer-dist --no-progress
202
+ - run: vendor/bin/pint --test
203
+ - run: vendor/bin/phpstan analyse --no-progress
204
+ - run: vendor/bin/pest --parallel
205
+ ```
206
+
207
+ CI must fail on:
208
+ - Format violations
209
+ - PHPStan errors
210
+ - Test failures
211
+ - Security audit findings (`composer audit`)
212
+
213
+ See [`../tooling/ci.md`](../tooling/ci.md) for cross-language CI principles.
214
+
215
+ ## Optional but Useful
216
+
217
+ - **Telescope** (Laravel) — local request/query/job inspector
218
+ - **Horizon** (Laravel) — queue dashboard
219
+ - **Symfony Profiler** — request profiling
220
+ - **Xdebug** — step debugging
221
+ - **PHP Insights** — code quality metrics
222
+ - **Deptrac** — enforce architectural boundaries
223
+
224
+ ## DO NOT
225
+
226
+ - Skip PHPStan to "save time" — bugs cost more than analysis
227
+ - Run both Pint and PHP-CS-Fixer in the same project
228
+ - Edit `vendor/` files
229
+ - Commit `vendor/` to git
230
+ - Use globally-installed PHP/Composer in production — pin versions in Docker or Dockerfile
231
+
232
+ ## PRIORITY
233
+
234
+ ```
235
+ PHPStan level max > "It runs" as quality bar
236
+ Automated formatting > Manual style debates
237
+ Rector for upgrades > Manual sed across the codebase
238
+ ```
239
+
240
+ ## See Also
241
+
242
+ - [`rules.md`](rules.md) — the rules that PHPStan helps enforce
243
+ - [`testing.md`](testing.md) — Pest / PHPUnit setup
244
+ - [`frameworks.md`](frameworks.md) — framework-specific tooling additions
245
+ - [`../tooling/ci.md`](../tooling/ci.md) — cross-language CI principles
@@ -0,0 +1,130 @@
1
+ # Accessibility
2
+
3
+ ## Core Rule
4
+
5
+ Accessibility is not optional. It is part of the definition of done.
6
+
7
+ ## Requirements
8
+
9
+ - Semantic HTML first — use the right element for the job
10
+ - All interactive elements must be keyboard accessible
11
+ - All interactive elements must be usable by screen readers
12
+ - All form inputs must have associated labels
13
+ - Color is never the only means of conveying information
14
+ - Minimum contrast ratio: 4.5:1 for normal text, 3:1 for large text (WCAG AA)
15
+
16
+ ## Semantic HTML
17
+
18
+ ```tsx
19
+ // BAD — no semantics
20
+ <div onClick={handleSubmit}>Submit</div>
21
+ <div className="heading">Invoice Details</div>
22
+
23
+ // GOOD — correct semantics
24
+ <button type="submit" onClick={handleSubmit}>Submit</button>
25
+ <h2>Invoice Details</h2>
26
+ ```
27
+
28
+ Use the correct element:
29
+ - `<button>` for actions
30
+ - `<a>` for navigation
31
+ - `<h1>`–`<h6>` for headings (in order, no skipping)
32
+ - `<ul>` / `<ol>` / `<li>` for lists
33
+ - `<table>` for tabular data (with `<caption>`, `<th scope>`)
34
+ - `<nav>` for navigation regions
35
+ - `<main>`, `<aside>`, `<header>`, `<footer>` for landmarks
36
+
37
+ ## Form Labeling
38
+
39
+ ```tsx
40
+ // BAD — no label association
41
+ <div>
42
+ <span>Email</span>
43
+ <input type="email" />
44
+ </div>
45
+
46
+ // GOOD — explicit label
47
+ <label htmlFor="email">Email</label>
48
+ <input id="email" type="email" />
49
+
50
+ // GOOD — wrapped label
51
+ <label>
52
+ Email
53
+ <input type="email" />
54
+ </label>
55
+ ```
56
+
57
+ Never use `placeholder` as a substitute for a label.
58
+
59
+ ## Keyboard Navigation
60
+
61
+ - All interactive elements must be reachable with Tab
62
+ - All interactive elements must be activatable with Enter or Space
63
+ - Dialogs must trap focus while open and restore focus on close
64
+ - Menus must support Arrow key navigation
65
+ - Escape must close dialogs, dropdowns, and menus
66
+
67
+ ## ARIA — Use Only When Necessary
68
+
69
+ Prefer semantic HTML. Use ARIA only when a native element cannot express the required semantics.
70
+
71
+ ```tsx
72
+ // BAD — redundant ARIA
73
+ <button role="button" aria-label="Submit">Submit</button>
74
+
75
+ // GOOD — ARIA only when HTML cannot express it
76
+ <div role="status" aria-live="polite">{statusMessage}</div>
77
+ ```
78
+
79
+ Common correct ARIA uses:
80
+ - `aria-label` / `aria-labelledby` — when visible label is absent
81
+ - `aria-describedby` — for additional descriptions (error messages, help text)
82
+ - `aria-expanded` — for toggleable regions
83
+ - `aria-live` — for dynamic content announcements
84
+ - `aria-invalid` — for form validation errors
85
+
86
+ ## Images
87
+
88
+ ```tsx
89
+ // Decorative — hide from assistive technology
90
+ <img src="decoration.svg" alt="" />
91
+
92
+ // Informative — describe the content
93
+ <img src="invoice-preview.png" alt="Preview of invoice #1234 for $500" />
94
+
95
+ // Icon buttons — label the action, not the icon
96
+ <button aria-label="Download invoice">
97
+ <DownloadIcon aria-hidden="true" />
98
+ </button>
99
+ ```
100
+
101
+ ## Focus Visibility
102
+
103
+ Never remove focus outlines without providing an equivalent visible replacement:
104
+
105
+ ```css
106
+ /* BAD */
107
+ :focus { outline: none; }
108
+
109
+ /* GOOD */
110
+ :focus-visible {
111
+ outline: 2px solid var(--color-focus-ring);
112
+ outline-offset: 2px;
113
+ }
114
+ ```
115
+
116
+ ## shadcn/ui and Radix
117
+
118
+ Prefer shadcn/ui components built on Radix UI primitives — they handle keyboard navigation and ARIA patterns correctly out of the box. Do not replace them with custom implementations unless there is a specific, documented reason.
119
+
120
+ ## PRIORITY
121
+
122
+ ```
123
+ Semantic HTML > ARIA > Custom implementation
124
+ ```
125
+
126
+ ## See Also
127
+
128
+ - [`component-design.md`](component-design.md) — composable, accessible components
129
+ - [`forms.md`](forms.md) — label, error, and keyboard patterns
130
+ - [`../tooling/shadcn.md`](../tooling/shadcn.md) — Radix-based primitives with a11y built in
@@ -0,0 +1,187 @@
1
+ # React Anti-Patterns
2
+
3
+ ## Giant Components
4
+
5
+ ```tsx
6
+ // BAD — one component doing everything
7
+ function InvoicePage() {
8
+ const [invoices, setInvoices] = useState([]);
9
+ const [filters, setFilters] = useState({});
10
+ const [sortKey, setSortKey] = useState("date");
11
+ const [page, setPage] = useState(1);
12
+ const [selectedIds, setSelectedIds] = useState([]);
13
+ // ... 200 more lines of JSX, logic, and effects
14
+ }
15
+
16
+ // GOOD — decomposed, composed
17
+ function InvoicePage() {
18
+ return (
19
+ <InvoiceLayout>
20
+ <InvoiceFilters />
21
+ <InvoiceTable />
22
+ <InvoicePagination />
23
+ </InvoiceLayout>
24
+ );
25
+ }
26
+ ```
27
+
28
+ ## useEffect for Derived State
29
+
30
+ ```tsx
31
+ // BAD
32
+ const [fullName, setFullName] = useState("");
33
+ useEffect(() => {
34
+ setFullName(`${firstName} ${lastName}`);
35
+ }, [firstName, lastName]);
36
+
37
+ // GOOD
38
+ const fullName = `${firstName} ${lastName}`;
39
+ ```
40
+
41
+ ## useEffect for Data Fetching
42
+
43
+ ```tsx
44
+ // BAD
45
+ const [user, setUser] = useState(null);
46
+ useEffect(() => {
47
+ fetchUser(id).then(setUser);
48
+ }, [id]);
49
+
50
+ // GOOD
51
+ const { data: user } = useQuery({
52
+ queryKey: ["user", id],
53
+ queryFn: () => fetchUser(id),
54
+ });
55
+ ```
56
+
57
+ ## Prop Drilling
58
+
59
+ Prop drilling is when intermediate components accept props they don't use, only to forward them to a deeper child. The pain compounds as you add more shared values — every layer's signature grows.
60
+
61
+ ```tsx
62
+ // BAD — Layout and Sidebar don't use `user`, they just forward it
63
+ function Page({ user }: { user: User }) {
64
+ return <Layout user={user} />;
65
+ }
66
+
67
+ function Layout({ user }: { user: User }) {
68
+ return (
69
+ <div>
70
+ <Header />
71
+ <Sidebar user={user} />
72
+ <Content />
73
+ </div>
74
+ );
75
+ }
76
+
77
+ function Sidebar({ user }: { user: User }) {
78
+ return (
79
+ <aside>
80
+ <Nav />
81
+ <UserMenu user={user} />
82
+ </aside>
83
+ );
84
+ }
85
+
86
+ function UserMenu({ user }: { user: User }) {
87
+ return <button>{user.name}</button>;
88
+ }
89
+
90
+ // GOOD — Context lets UserMenu read `user` directly; intermediates carry nothing extra
91
+ function Page() {
92
+ return <Layout />;
93
+ }
94
+
95
+ function UserMenu() {
96
+ const { user } = useCurrentUser();
97
+ return <button>{user.name}</button>;
98
+ }
99
+ ```
100
+
101
+ When deciding between context, composition, or a store, see [state-management.md](state-management.md).
102
+
103
+ ## Defensive Memoization
104
+
105
+ ```tsx
106
+ // BAD — memoizing trivial values
107
+ const label = useMemo(() => `Hello, ${name}`, [name]);
108
+ const handleClick = useCallback(() => setOpen(true), []);
109
+
110
+ // GOOD — only memoize when there is a proven reason
111
+ const label = `Hello, ${name}`;
112
+ const handleClick = () => setOpen(true);
113
+ ```
114
+
115
+ ## State Synchronization Effects
116
+
117
+ ```tsx
118
+ // BAD — syncing state with state
119
+ const [search, setSearch] = useState("");
120
+ const [filteredItems, setFilteredItems] = useState(items);
121
+
122
+ useEffect(() => {
123
+ setFilteredItems(items.filter((i) => i.name.includes(search)));
124
+ }, [search, items]);
125
+
126
+ // GOOD — compute during render
127
+ const filteredItems = items.filter((i) => i.name.includes(search));
128
+ ```
129
+
130
+ ## Hook Mini-Frameworks
131
+
132
+ ```tsx
133
+ // BAD — over-engineered hook that re-implements a library
134
+ function useDataFetcher<T>(url: string, options: FetcherOptions<T>) {
135
+ const [state, dispatch] = useReducer(fetcherReducer, initialState);
136
+ useEffect(() => {
137
+ // 50 lines of custom fetching, caching, retry logic...
138
+ }, [url, options]);
139
+ return state;
140
+ }
141
+
142
+ // GOOD — use TanStack Query
143
+ const { data, isLoading, error } = useQuery({
144
+ queryKey: [url],
145
+ queryFn: () => fetch(url).then((r) => r.json()),
146
+ retry: 3,
147
+ });
148
+ ```
149
+
150
+ ## Index as Key
151
+
152
+ ```tsx
153
+ // BAD — unstable keys cause incorrect reconciliation
154
+ {items.map((item, index) => (
155
+ <Item key={index} {...item} />
156
+ ))}
157
+
158
+ // GOOD — stable, unique key
159
+ {items.map((item) => (
160
+ <Item key={item.id} {...item} />
161
+ ))}
162
+ ```
163
+
164
+ ## Spreading Unknown Props onto DOM Elements
165
+
166
+ ```tsx
167
+ // BAD — unknown props forwarded to DOM, React will warn
168
+ function Card({ className, ...rest }: Record<string, unknown>) {
169
+ return <div className={className} {...rest} />;
170
+ }
171
+
172
+ // GOOD — explicit allowed props
173
+ interface CardProps {
174
+ className?: string;
175
+ children: React.ReactNode;
176
+ }
177
+ function Card({ className, children }: CardProps) {
178
+ return <div className={className}>{children}</div>;
179
+ }
180
+ ```
181
+
182
+ ## See Also
183
+
184
+ - [`use-effect.md`](use-effect.md) — when useEffect is wrong
185
+ - [`memoization.md`](memoization.md) — when memoization is wrong
186
+ - [`state-management.md`](state-management.md) — state hierarchy
187
+ - [`../examples/bad/`](../examples/bad/) — concrete anti-pattern examples