@el-j/magic-helix-core 4.0.0-beta.1 → 4.0.0-beta.3

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 (160) hide show
  1. package/dist/index-B88j4AyE.js +13 -0
  2. package/dist/index-B88j4AyE.js.map +1 -0
  3. package/dist/index-CY-pQbuu.cjs +2 -0
  4. package/dist/index-CY-pQbuu.cjs.map +1 -0
  5. package/dist/index.cjs +75 -1
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.mjs +2234 -51
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/pattern-combiner.d.ts +1 -1
  11. package/dist/plugin-loader.d.ts +7 -1
  12. package/package.json +4 -4
  13. package/dist/BasePlugin-6wv0hYJ9.js +0 -98
  14. package/dist/BasePlugin-6wv0hYJ9.js.map +0 -1
  15. package/dist/BasePlugin-odQJAKA-.cjs +0 -2
  16. package/dist/BasePlugin-odQJAKA-.cjs.map +0 -1
  17. package/dist/builtin-plugins/base/BasePlugin.d.ts +0 -69
  18. package/dist/builtin-plugins/csharp/index.d.ts +0 -20
  19. package/dist/builtin-plugins/go/index.d.ts +0 -23
  20. package/dist/builtin-plugins/index.d.ts +0 -15
  21. package/dist/builtin-plugins/java/index.d.ts +0 -22
  22. package/dist/builtin-plugins/nodejs/index.d.ts +0 -44
  23. package/dist/builtin-plugins/php/index.d.ts +0 -20
  24. package/dist/builtin-plugins/python/index.d.ts +0 -27
  25. package/dist/builtin-plugins/ruby/index.d.ts +0 -20
  26. package/dist/builtin-plugins/rust/index.d.ts +0 -53
  27. package/dist/builtin-plugins/swift/index.d.ts +0 -22
  28. package/dist/default_templates/angular/angular-core.md +0 -19
  29. package/dist/default_templates/architecture/codeowners.md +0 -123
  30. package/dist/default_templates/architecture/monorepo.md +0 -146
  31. package/dist/default_templates/architecture/nx.md +0 -122
  32. package/dist/default_templates/architecture/turborepo.md +0 -114
  33. package/dist/default_templates/ci/github-actions.md +0 -268
  34. package/dist/default_templates/ci/gitlab-ci.md +0 -330
  35. package/dist/default_templates/containers/docker-multistage.md +0 -120
  36. package/dist/default_templates/containers/kubernetes-deploy.md +0 -210
  37. package/dist/default_templates/devops/docker-compose.md +0 -111
  38. package/dist/default_templates/devops/docker-dockerfile.md +0 -94
  39. package/dist/default_templates/devops/github-actions.md +0 -160
  40. package/dist/default_templates/devops/gitlab-ci.md +0 -210
  41. package/dist/default_templates/dotnet/framework-aspnetcore.md +0 -205
  42. package/dist/default_templates/dotnet/framework-blazor.md +0 -271
  43. package/dist/default_templates/dotnet/lang-csharp.md +0 -162
  44. package/dist/default_templates/generic/lang-typescript.md +0 -11
  45. package/dist/default_templates/generic/state-redux.md +0 -21
  46. package/dist/default_templates/generic/state-rxjs.md +0 -6
  47. package/dist/default_templates/generic/style-mui.md +0 -23
  48. package/dist/default_templates/generic/style-tailwind.md +0 -6
  49. package/dist/default_templates/generic/test-cypress.md +0 -21
  50. package/dist/default_templates/generic/test-jest.md +0 -20
  51. package/dist/default_templates/generic/test-playwright.md +0 -21
  52. package/dist/default_templates/generic/test-vitest.md +0 -6
  53. package/dist/default_templates/go/lang-go.md +0 -571
  54. package/dist/default_templates/java/build-gradle.md +0 -102
  55. package/dist/default_templates/java/build-maven.md +0 -86
  56. package/dist/default_templates/java/framework-spring-boot.md +0 -179
  57. package/dist/default_templates/java/lang-java.md +0 -78
  58. package/dist/default_templates/java/lang-kotlin.md +0 -88
  59. package/dist/default_templates/meta/magic-helix-meta.md +0 -213
  60. package/dist/default_templates/meta/meta-debug.md +0 -459
  61. package/dist/default_templates/meta/meta-implement.md +0 -450
  62. package/dist/default_templates/meta/meta-roadmap.md +0 -265
  63. package/dist/default_templates/nestjs/nestjs-core.md +0 -7
  64. package/dist/default_templates/patterns/architecture/clean-architecture.md +0 -469
  65. package/dist/default_templates/patterns/architecture/dependency-injection.md +0 -517
  66. package/dist/default_templates/patterns/architecture/domain-driven-design.md +0 -621
  67. package/dist/default_templates/patterns/architecture/layered-architecture.md +0 -382
  68. package/dist/default_templates/patterns/architecture/repository-pattern.md +0 -408
  69. package/dist/default_templates/patterns/domain-expertise/nextjs-rules.md +0 -115
  70. package/dist/default_templates/patterns/domain-expertise/react-patterns.md +0 -181
  71. package/dist/default_templates/patterns/domain-expertise/server-components.md +0 -212
  72. package/dist/default_templates/patterns/domain-expertise/shadcn-ui.md +0 -52
  73. package/dist/default_templates/patterns/domain-expertise/tailwind-patterns.md +0 -52
  74. package/dist/default_templates/patterns/environment/container-awareness.md +0 -17
  75. package/dist/default_templates/patterns/environment/ide-features.md +0 -17
  76. package/dist/default_templates/patterns/environment/os-commands.md +0 -17
  77. package/dist/default_templates/patterns/organization/heading-hierarchy.md +0 -103
  78. package/dist/default_templates/patterns/organization/sequential-workflows.md +0 -102
  79. package/dist/default_templates/patterns/organization/xml-rule-groups.md +0 -64
  80. package/dist/default_templates/patterns/reasoning/agent-loop.md +0 -151
  81. package/dist/default_templates/patterns/reasoning/confirmation-gates.md +0 -141
  82. package/dist/default_templates/patterns/reasoning/dependency-analysis.md +0 -132
  83. package/dist/default_templates/patterns/reasoning/one-tool-per-iteration.md +0 -152
  84. package/dist/default_templates/patterns/reasoning/preview-before-action.md +0 -194
  85. package/dist/default_templates/patterns/reasoning/reflection-checkpoints.md +0 -166
  86. package/dist/default_templates/patterns/reasoning/result-verification.md +0 -157
  87. package/dist/default_templates/patterns/reasoning/subtask-breakdown.md +0 -131
  88. package/dist/default_templates/patterns/reasoning/thinking-tags.md +0 -100
  89. package/dist/default_templates/patterns/role-definition/capability-declarations.md +0 -72
  90. package/dist/default_templates/patterns/role-definition/expert-identity.md +0 -45
  91. package/dist/default_templates/patterns/role-definition/scope-boundaries.md +0 -61
  92. package/dist/default_templates/patterns/safety/code-safety-rules.md +0 -17
  93. package/dist/default_templates/patterns/safety/credential-handling.md +0 -17
  94. package/dist/default_templates/patterns/safety/destructive-warnings.md +0 -17
  95. package/dist/default_templates/patterns/safety/refusal-messages.md +0 -17
  96. package/dist/default_templates/patterns/tone/adaptive-tone.md +0 -17
  97. package/dist/default_templates/patterns/tone/concise-communication.md +0 -17
  98. package/dist/default_templates/patterns/tone/forbidden-phrases.md +0 -17
  99. package/dist/default_templates/patterns/tool-guidelines/function-schemas.md +0 -143
  100. package/dist/default_templates/patterns/tool-guidelines/parameter-examples.md +0 -137
  101. package/dist/default_templates/patterns/tool-guidelines/usage-policies.md +0 -105
  102. package/dist/default_templates/php/framework-laravel.md +0 -112
  103. package/dist/default_templates/php/lang-php.md +0 -94
  104. package/dist/default_templates/python/lang-python.md +0 -508
  105. package/dist/default_templates/react/react-core.md +0 -677
  106. package/dist/default_templates/react/react-zustand.md +0 -7
  107. package/dist/default_templates/ruby/framework-rails.md +0 -309
  108. package/dist/default_templates/ruby/framework-sinatra.md +0 -227
  109. package/dist/default_templates/ruby/lang-ruby.md +0 -216
  110. package/dist/default_templates/rust/lang-rust.md +0 -89
  111. package/dist/default_templates/swift/framework-vapor.md +0 -352
  112. package/dist/default_templates/swift/lang-swift.md +0 -291
  113. package/dist/default_templates/vue/style-primevue.md +0 -6
  114. package/dist/default_templates/vue/style-quasar.md +0 -22
  115. package/dist/default_templates/vue/vue-core.md +0 -28
  116. package/dist/default_templates/vue/vue-pinia.md +0 -5
  117. package/dist/index-AkVwRl-r.js +0 -92
  118. package/dist/index-AkVwRl-r.js.map +0 -1
  119. package/dist/index-B6BeG1yT.cjs +0 -68
  120. package/dist/index-B6BeG1yT.cjs.map +0 -1
  121. package/dist/index-B8pyjKdF.js +0 -94
  122. package/dist/index-B8pyjKdF.js.map +0 -1
  123. package/dist/index-B_6W_RnJ.cjs +0 -76
  124. package/dist/index-B_6W_RnJ.cjs.map +0 -1
  125. package/dist/index-Bg8DD8ku.js +0 -216
  126. package/dist/index-Bg8DD8ku.js.map +0 -1
  127. package/dist/index-BkJhe5Af.js +0 -1748
  128. package/dist/index-BkJhe5Af.js.map +0 -1
  129. package/dist/index-Bv4Q1Pr7.cjs +0 -33
  130. package/dist/index-Bv4Q1Pr7.cjs.map +0 -1
  131. package/dist/index-CN8J45Nc.cjs +0 -24
  132. package/dist/index-CN8J45Nc.cjs.map +0 -1
  133. package/dist/index-CPbv2Od1.js +0 -62
  134. package/dist/index-CPbv2Od1.js.map +0 -1
  135. package/dist/index-Cf-MC6Al.js +0 -63
  136. package/dist/index-Cf-MC6Al.js.map +0 -1
  137. package/dist/index-DDPXXXDy.cjs +0 -19
  138. package/dist/index-DDPXXXDy.cjs.map +0 -1
  139. package/dist/index-DO30AzDe.cjs +0 -19
  140. package/dist/index-DO30AzDe.cjs.map +0 -1
  141. package/dist/index-Dm37u5ut.js +0 -2128
  142. package/dist/index-Dm37u5ut.js.map +0 -1
  143. package/dist/index-DqHvgoXJ.cjs +0 -19
  144. package/dist/index-DqHvgoXJ.cjs.map +0 -1
  145. package/dist/index-J1qAfsnO.cjs +0 -2
  146. package/dist/index-J1qAfsnO.cjs.map +0 -1
  147. package/dist/index-Jz0HYZ7B.js +0 -13
  148. package/dist/index-Jz0HYZ7B.js.map +0 -1
  149. package/dist/index-K39pdw94.cjs +0 -31
  150. package/dist/index-K39pdw94.cjs.map +0 -1
  151. package/dist/index-L3IVvhd1.cjs +0 -89
  152. package/dist/index-L3IVvhd1.cjs.map +0 -1
  153. package/dist/index-OT2XAJkc.js +0 -117
  154. package/dist/index-OT2XAJkc.js.map +0 -1
  155. package/dist/index-TPAX4XKg.cjs +0 -30
  156. package/dist/index-TPAX4XKg.cjs.map +0 -1
  157. package/dist/index-WmVSB57y.js +0 -107
  158. package/dist/index-WmVSB57y.js.map +0 -1
  159. package/dist/index-mYXvc3Fs.js +0 -68
  160. package/dist/index-mYXvc3Fs.js.map +0 -1
@@ -1,677 +0,0 @@
1
- # Framework: React
2
-
3
- ## Modern React: Functional, Composable, Framework-Agnostic
4
-
5
- **PREFER** functional programming over classes. Use plain objects, pure functions, and React hooks.
6
-
7
- ## Architecture: Separation of Concerns
8
-
9
- **Components should be logic-free presentation layers. Business logic lives in framework-free services.**
10
-
11
- ### Layer Structure
12
-
13
- ```
14
- src/
15
- ├── components/ # Presentation layer (React-specific, logic-free)
16
- │ ├── UserProfile.tsx # UI only: rendering, event handlers → call hooks
17
- │ └── OrderList.tsx
18
-
19
- ├── hooks/ # React bridge layer (connects React to business logic)
20
- │ ├── useUser.ts # Wraps services in React state/effects
21
- │ └── useOrders.ts
22
-
23
- ├── services/ # Business logic layer (framework-free, pure functions)
24
- │ ├── user.ts # Pure functions, no React dependencies
25
- │ └── order.ts
26
-
27
- └── models/ # Domain models (types + pure functions)
28
- ├── user.ts # Plain types + validators
29
- └── order.ts
30
- ```
31
-
32
- ## Modern Patterns
33
-
34
- ### ✅ DO: Use Functional Components + Hooks
35
-
36
- ```tsx
37
- // ✅ Modern: Functional component
38
- export function UserProfile({ userId }: { userId: string }) {
39
- const { user, loading } = useUser(userId);
40
-
41
- if (loading) return <Spinner />;
42
- return <div>{user.name}</div>;
43
- }
44
- ```
45
-
46
- ### ❌ DON'T: Use Class Components
47
-
48
- ```tsx
49
- // ❌ Outdated: Class component
50
- class UserProfile extends React.Component {
51
- // Don't use classes in modern React
52
- }
53
- ```
54
-
55
- ### ✅ DO: Use Plain Objects and Pure Functions
56
-
57
- ```tsx
58
- // ✅ Modern: Plain type + pure functions
59
- export type User = {
60
- id: string;
61
- name: string;
62
- email: string;
63
- };
64
-
65
- export function validateUser(user: User): boolean {
66
- return user.email.includes('@');
67
- }
68
- ```
69
-
70
- ### ❌ DON'T: Use Classes for Models
71
-
72
- ```tsx
73
- // ❌ Outdated: Class-based model
74
- class User {
75
- constructor(public id: string, public name: string) {}
76
- validate() { /* ... */ }
77
- }
78
- ```
79
-
80
- ### Rule: Components are Logic-Free
81
-
82
- **ALWAYS** keep components focused on rendering and user interactions. **NEVER** put business logic in components.
83
-
84
- ```tsx
85
- // ✅ Good: Logic-free component
86
- export function UserProfile({ userId }: { userId: string }) {
87
- const { user, loading, updateEmail } = useUser(userId);
88
-
89
- if (loading) return <Spinner />;
90
- if (!user) return <NotFound />;
91
-
92
- return (
93
- <div>
94
- <h1>{user.name}</h1>
95
- <EmailForm
96
- currentEmail={user.email}
97
- onSubmit={updateEmail} // Hook handles the logic
98
- />
99
- </div>
100
- );
101
- }
102
-
103
- // ❌ Bad: Business logic in component
104
- export function UserProfile({ userId }: { userId: string }) {
105
- const [user, setUser] = useState<User | null>(null);
106
-
107
- async function updateEmail(newEmail: string) {
108
- // ❌ Validation logic in component
109
- if (!newEmail.includes('@')) {
110
- alert('Invalid email');
111
- return;
112
- }
113
-
114
- // ❌ API call in component
115
- const response = await fetch(`/api/users/${userId}`, {
116
- method: 'PATCH',
117
- body: JSON.stringify({ email: newEmail })
118
- });
119
-
120
- const updated = await response.json();
121
- setUser(updated);
122
- }
123
-
124
- return (/* ... */);
125
- }
126
- ```
127
-
128
- ### Rule: Hooks Wire React to Services
129
-
130
- **ALWAYS** use custom hooks to bridge React state/effects with framework-free services.
131
-
132
- ```tsx
133
- // ✅ hooks/useUser.ts - React bridge
134
- import { useState, useEffect } from 'react';
135
- import * as userService from '@/services/userService';
136
- import type { User } from '@/models/User';
137
-
138
- export function useUser(userId: string) {
139
- const [user, setUser] = useState<User | null>(null);
140
- const [loading, setLoading] = useState(true);
141
- const [error, setError] = useState<Error | null>(null);
142
-
143
- useEffect(() => {
144
- let cancelled = false;
145
-
146
- async function loadUser() {
147
- try {
148
- setLoading(true);
149
- const data = await userService.getUser(userId); // Pure function call
150
- if (!cancelled) {
151
- setUser(data);
152
- }
153
- } catch (err) {
154
- if (!cancelled) {
155
- setError(err as Error);
156
- }
157
- } finally {
158
- if (!cancelled) {
159
- setLoading(false);
160
- }
161
- }
162
- }
163
-
164
- loadUser();
165
-
166
- return () => {
167
- cancelled = true; // Cleanup
168
- };
169
- }, [userId]);
170
-
171
- const updateEmail = async (newEmail: string) => {
172
- const updated = await userService.updateUserEmail(userId, newEmail);
173
- setUser(updated);
174
- };
175
-
176
- return { user, loading, error, updateEmail };
177
- }
178
- ```
179
-
180
- ### Rule: Services are Framework-Free
181
-
182
- **ALWAYS** write services as pure functions or plain objects. **NEVER** import React or use hooks in services.
183
-
184
- ```typescript
185
- // ✅ Modern: services/userService.ts - Pure functions (no class)
186
- import type { User } from '@/models/User';
187
- import { validateEmail } from '@/models/User';
188
- import { apiClient } from '@/lib/apiClient';
189
-
190
- export async function getUser(userId: string): Promise<User> {
191
- const response = await apiClient.get(`/users/${userId}`);
192
- return {
193
- id: response.data.id,
194
- name: response.data.name,
195
- email: response.data.email,
196
- createdAt: new Date(response.data.created_at),
197
- };
198
- }
199
-
200
- export async function updateUserEmail(userId: string, newEmail: string): Promise<User> {
201
- // Business logic: validation
202
- if (!validateEmail(newEmail)) {
203
- throw new Error('Invalid email format');
204
- }
205
-
206
- // API call
207
- const response = await apiClient.patch(`/users/${userId}`, {
208
- email: newEmail
209
- });
210
-
211
- return {
212
- id: response.data.id,
213
- name: response.data.name,
214
- email: response.data.email,
215
- createdAt: new Date(response.data.created_at),
216
- };
217
- }
218
-
219
- export async function deleteUser(userId: string): Promise<void> {
220
- await apiClient.delete(`/users/${userId}`);
221
- }
222
-
223
- // Alternative: Object grouping for organization
224
- export const userService = {
225
- getUser,
226
- updateUserEmail,
227
- deleteUser,
228
- } as const;
229
- ```
230
-
231
- **Benefits:**
232
- - Easy to test (no React mocking needed)
233
- - Reusable in Node.js scripts, CLI tools, React Native
234
- - Can swap React for Vue/Svelte/Angular without rewriting logic
235
- - Tree-shakeable (only import functions you use)
236
-
237
- ### Rule: Domain Models are Plain Objects + Pure Functions
238
-
239
- **PREFER** plain TypeScript types with pure utility functions over classes.
240
-
241
- ```typescript
242
- // ✅ Modern: models/User.ts - Type + pure functions
243
- export type User = {
244
- readonly id: string;
245
- name: string;
246
- email: string;
247
- createdAt: Date;
248
- };
249
-
250
- // Pure utility functions
251
- export function createUser(data: { name: string; email: string }): User {
252
- if (!validateEmail(data.email)) {
253
- throw new Error('Invalid email format');
254
- }
255
-
256
- return {
257
- id: crypto.randomUUID(),
258
- name: data.name,
259
- email: data.email,
260
- createdAt: new Date(),
261
- };
262
- }
263
-
264
- export function isAdmin(user: User): boolean {
265
- return user.email.endsWith('@company.com');
266
- }
267
-
268
- export function validateEmail(email: string): boolean {
269
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
270
- }
271
-
272
- // For complex transformations
273
- export function userFromAPI(data: unknown): User {
274
- // Add runtime validation with zod or similar
275
- return {
276
- id: data.id,
277
- name: data.name,
278
- email: data.email,
279
- createdAt: new Date(data.created_at),
280
- };
281
- }
282
- ```
283
-
284
- ### Complete Example: Order Management
285
-
286
- ```
287
- src/
288
- ├── components/
289
- │ └── OrderList.tsx # UI only
290
- ├── hooks/
291
- │ └── useOrders.ts # React state bridge
292
- ├── services/
293
- │ └── orderService.ts # Business logic
294
- └── models/
295
- └── Order.ts # Domain model
296
- ```
297
-
298
- ```tsx
299
- // components/OrderList.tsx - Presentation
300
- export function OrderList() {
301
- const { orders, loading, createOrder, cancelOrder } = useOrders();
302
-
303
- if (loading) return <Spinner />;
304
-
305
- return (
306
- <div>
307
- <button onClick={() => createOrder({ items: [...] })}>
308
- New Order
309
- </button>
310
- {orders.map(order => (
311
- <OrderCard
312
- key={order.id}
313
- order={order}
314
- onCancel={() => cancelOrder(order.id)}
315
- />
316
- ))}
317
- </div>
318
- );
319
- }
320
-
321
- // hooks/useOrders.ts - React bridge
322
- export function useOrders() {
323
- const [orders, setOrders] = useState<Order[]>([]);
324
- const [loading, setLoading] = useState(true);
325
-
326
- useEffect(() => {
327
- orderService.getOrders().then(setOrders).finally(() => setLoading(false));
328
- }, []);
329
-
330
- const createOrder = async (data: CreateOrderData) => {
331
- const newOrder = await orderService.createOrder(data);
332
- setOrders(prev => [...prev, newOrder]);
333
- };
334
-
335
- const cancelOrder = async (orderId: string) => {
336
- await orderService.cancelOrder(orderId);
337
- setOrders(prev => prev.filter(o => o.id !== orderId));
338
- };
339
-
340
- return { orders, loading, createOrder, cancelOrder };
341
- }
342
-
343
- // services/orderService.ts - Framework-free logic
344
- class OrderService {
345
- async getOrders(): Promise<Order[]> {
346
- const response = await apiClient.get('/orders');
347
- return response.data.map(Order.fromAPI);
348
- }
349
-
350
- async createOrder(data: CreateOrderData): Promise<Order> {
351
- // Validation
352
- if (data.items.length === 0) {
353
- throw new Error('Order must have at least one item');
354
- }
355
-
356
- const response = await apiClient.post('/orders', data);
357
- return Order.fromAPI(response.data);
358
- }
359
-
360
- async cancelOrder(orderId: string): Promise<void> {
361
- await apiClient.post(`/orders/${orderId}/cancel`);
362
- }
363
- }
364
-
365
- export const orderService = new OrderService();
366
-
367
- // models/Order.ts - Domain model
368
- export class Order {
369
- constructor(
370
- public readonly id: string,
371
- public status: OrderStatus,
372
- public items: OrderItem[],
373
- public total: number
374
- ) {}
375
-
376
- static fromAPI(data: any): Order {
377
- return new Order(
378
- data.id,
379
- data.status,
380
- data.items.map(OrderItem.fromAPI),
381
- data.total
382
- );
383
- }
384
-
385
- canBeCancelled(): boolean {
386
- return this.status === 'pending' || this.status === 'processing';
387
- }
388
- }
389
- ```
390
-
391
- ## React Best Practices
392
-
393
- ### Modern React Patterns
394
-
395
- - **ALWAYS** use Functional Components with Hooks (never classes)
396
- - **ALWAYS** use plain objects and pure functions over classes for models/services
397
- - **PREFER** function exports over default exports for better tree-shaking
398
- - **PREFER** named exports: `export function Component()` over `export default`
399
- - **HOOKS**: Use `useState` for simple state, `useReducer` for complex state logic
400
- - **EFFECTS**: `useEffect` dependencies must be complete. Use `eslint-plugin-react-hooks`
401
- - **MEMOIZATION**: Use `useCallback` for functions passed as props, `useMemo` for expensive calculations
402
- - **NAMING**: Components are `PascalCase.tsx`, utilities are `camelCase.ts`
403
-
404
- ### Recommended Modern Libraries
405
-
406
- **State Management:**
407
- - ✅ Zustand (simple, functional, no boilerplate)
408
- - ✅ Jotai (atomic state, React-like)
409
- - ✅ TanStack Query (server state, caching)
410
- - ⚠️ Redux Toolkit (if you need Redux, use RTK not classic Redux)
411
- - ❌ Classic Redux (too much boilerplate)
412
-
413
- **Forms:**
414
- - ✅ React Hook Form (performant, minimal re-renders)
415
- - ✅ Zod (schema validation, type-safe)
416
- - ❌ Formik (legacy, prefer React Hook Form)
417
-
418
- **Data Fetching:**
419
- - ✅ TanStack Query (React Query) - best for server state
420
- - ✅ SWR (simple, from Vercel)
421
- - ✅ Native `fetch` + custom hooks for simple cases
422
-
423
- **Styling:**
424
- - ✅ Tailwind CSS (utility-first, fast)
425
- - ✅ CSS Modules (scoped, simple)
426
- - ✅ Vanilla Extract (type-safe CSS-in-TS)
427
- - ⚠️ Styled Components (runtime cost, slower)
428
- - ⚠️ Emotion (runtime cost, slower)
429
-
430
- **Type Safety:**
431
- - ✅ TypeScript (essential)
432
- - ✅ Zod (runtime validation + type inference)
433
- - ✅ ts-pattern (pattern matching)
434
-
435
- **Testing:**
436
- - ✅ Vitest (modern, fast, Vite-compatible)
437
- - ✅ Testing Library (user-centric testing)
438
- - ⚠️ Jest (slower, but widely used)
439
-
440
- ### Example: Modern Stack
441
-
442
- ```tsx
443
- // ✅ Modern React with TanStack Query + Zod
444
- import { useQuery, useMutation } from '@tanstack/react-query';
445
- import { z } from 'zod';
446
-
447
- // Schema validation with Zod
448
- const UserSchema = z.object({
449
- id: z.string(),
450
- name: z.string(),
451
- email: z.string().email(),
452
- });
453
-
454
- type User = z.infer<typeof UserSchema>;
455
-
456
- // Pure function for API call
457
- async function fetchUser(userId: string): Promise<User> {
458
- const res = await fetch(`/api/users/${userId}`);
459
- const data = await res.json();
460
- return UserSchema.parse(data); // Runtime validation
461
- }
462
-
463
- // Component using TanStack Query (no custom hook needed!)
464
- export function UserProfile({ userId }: { userId: string }) {
465
- const { data: user, isLoading } = useQuery({
466
- queryKey: ['user', userId],
467
- queryFn: () => fetchUser(userId),
468
- });
469
-
470
- if (isLoading) return <Spinner />;
471
- if (!user) return <NotFound />;
472
-
473
- return <div>{user.name}</div>;
474
- }
475
- ```
476
-
477
- ### Example: Zustand for Client State
478
-
479
- ```typescript
480
- // stores/userStore.ts - Simple, functional state
481
- import { create } from 'zustand';
482
-
483
- type UserStore = {
484
- currentUser: User | null;
485
- setUser: (user: User) => void;
486
- logout: () => void;
487
- };
488
-
489
- export const useUserStore = create<UserStore>((set) => ({
490
- currentUser: null,
491
- setUser: (user) => set({ currentUser: user }),
492
- logout: () => set({ currentUser: null }),
493
- }));
494
-
495
- // Usage in component
496
- export function Header() {
497
- const currentUser = useUserStore((state) => state.currentUser);
498
- const logout = useUserStore((state) => state.logout);
499
-
500
- return <button onClick={logout}>{currentUser?.name}</button>;
501
- }
502
- ```
503
-
504
- ## File Organization
505
-
506
- ```
507
- src/
508
- ├── components/ # React components (presentation only)
509
- │ ├── UserProfile/
510
- │ │ ├── UserProfile.tsx
511
- │ │ ├── UserProfile.test.tsx
512
- │ │ └── index.ts
513
- │ └── shared/ # Reusable UI components
514
- │ ├── Button.tsx
515
- │ └── Card.tsx
516
-
517
- ├── hooks/ # Custom hooks (React bridge to services)
518
- │ ├── useUser.ts
519
- │ ├── useOrders.ts
520
- │ └── useAuth.ts
521
-
522
- ├── services/ # Business logic (framework-free)
523
- │ ├── userService.ts
524
- │ ├── orderService.ts
525
- │ └── authService.ts
526
-
527
- ├── models/ # Domain models and types
528
- │ ├── User.ts
529
- │ ├── Order.ts
530
- │ └── types.ts
531
-
532
- ├── lib/ # Utilities (API client, formatters, etc.)
533
- │ ├── apiClient.ts
534
- │ └── validators.ts
535
-
536
- └── App.tsx
537
- ```
538
-
539
- ## Testing Strategy
540
-
541
- **ALWAYS write tests BEFORE implementation (Test-Driven Development).**
542
-
543
- ### TDD Workflow
544
-
545
- ```typescript
546
- // 1. RED: Write failing test first
547
- describe('UserService', () => {
548
- it('should validate email format before API call', async () => {
549
- await expect(
550
- userService.updateUserEmail('1', 'invalid-email')
551
- ).rejects.toThrow('Invalid email format');
552
-
553
- expect(apiClient.patch).not.toHaveBeenCalled();
554
- });
555
- });
556
-
557
- // 2. GREEN: Write minimal code to pass
558
- export async function updateUserEmail(userId: string, email: string): Promise<User> {
559
- if (!isValidEmail(email)) {
560
- throw new Error('Invalid email format');
561
- }
562
- const response = await apiClient.patch(`/users/${userId}`, { email });
563
- return response.data;
564
- }
565
-
566
- // 3. REFACTOR: Clean up while keeping tests green
567
- ```
568
-
569
- ### Test Layer Strategy
570
-
571
- **Services (Framework-Free):** Test first, 90%+ coverage
572
- ```typescript
573
- // userService.test.ts - Write BEFORE userService.ts
574
- import { userService } from './userService';
575
- import { apiClient } from '@/lib/apiClient';
576
-
577
- jest.mock('@/lib/apiClient');
578
-
579
- describe('UserService - TDD', () => {
580
- // Test validates business rules
581
- test('validates email before updating', async () => {
582
- await expect(
583
- userService.updateUserEmail('1', 'invalid-email')
584
- ).rejects.toThrow('Invalid email format');
585
-
586
- expect(apiClient.patch).not.toHaveBeenCalled();
587
- });
588
-
589
- test('updates email when valid', async () => {
590
- const mockUser = { id: '1', email: 'new@example.com' };
591
- (apiClient.patch as jest.Mock).mockResolvedValue({ data: mockUser });
592
-
593
- const result = await userService.updateUserEmail('1', 'new@example.com');
594
-
595
- expect(apiClient.patch).toHaveBeenCalledWith('/users/1', {
596
- email: 'new@example.com'
597
- });
598
- expect(result.email).toBe('new@example.com');
599
- });
600
- });
601
- ```
602
-
603
- **Hooks (React Integration):** Test React behavior
604
- ```tsx
605
- // useUser.test.ts
606
- import { renderHook, waitFor } from '@testing-library/react';
607
- import { useUser } from './useUser';
608
- import { userService } from '@/services/userService';
609
-
610
- jest.mock('@/services/userService');
611
-
612
- test('loads user on mount', async () => {
613
- const mockUser = { id: '1', name: 'John', email: 'john@example.com' };
614
- (userService.getUser as jest.Mock).mockResolvedValue(mockUser);
615
-
616
- const { result } = renderHook(() => useUser('1'));
617
-
618
- expect(result.current.loading).toBe(true);
619
-
620
- await waitFor(() => {
621
- expect(result.current.loading).toBe(false);
622
- expect(result.current.user).toEqual(mockUser);
623
- });
624
- });
625
- ```
626
-
627
- **Components (UI):** Test rendering and user interactions
628
- ```tsx
629
- // UserProfile.test.tsx
630
- import { render, screen } from '@testing-library/react';
631
- import { UserProfile } from './UserProfile';
632
-
633
- // Mock the hook
634
- jest.mock('@/hooks/useUser', () => ({
635
- useUser: () => ({
636
- user: { id: '1', name: 'John', email: 'john@example.com' },
637
- loading: false,
638
- updateEmail: jest.fn()
639
- })
640
- }));
641
-
642
- test('renders user name', () => {
643
- render(<UserProfile userId="1" />);
644
- expect(screen.getByText('John')).toBeInTheDocument();
645
- });
646
- ```
647
-
648
- ### Test Coverage Goals
649
-
650
- - **Services/Business Logic**: 90%+ coverage (test-first, critical paths)
651
- - **Hooks**: 80%+ coverage (state/effect integration)
652
- - **Components**: 70%+ coverage (user interactions, conditional rendering)
653
- - **Models/Utilities**: 100% coverage (pure functions, edge cases)
654
-
655
- ## Architecture Summary
656
-
657
- 1. **Components** = Presentation (UI rendering, event handlers)
658
- - Import: hooks, UI libraries, CSS
659
- - Export: JSX components
660
- - No business logic, no API calls
661
-
662
- 2. **Hooks** = React Bridge (state management, effects)
663
- - Import: React hooks, services
664
- - Export: State + actions
665
- - Minimal logic (mostly wiring)
666
-
667
- 3. **Services** = Business Logic (framework-free)
668
- - Import: models, utilities, API client
669
- - Export: Business operations
670
- - No React dependencies
671
-
672
- 4. **Models** = Domain Rules (types, validation, transformations)
673
- - Import: nothing or minimal utilities
674
- - Export: Classes, types, validators
675
- - Pure TypeScript
676
-
677
- **Reference:** See `patterns/architecture/clean-architecture.md` and `patterns/architecture/layered-architecture.md` for detailed architectural guidance.
@@ -1,7 +0,0 @@
1
- # State: Zustand
2
- - **ALWAYS** use Zustand for global state management.
3
- - Define stores in `src/stores/` or `src/hooks/`.
4
- - **PREFER** the `create(set => ({ ... }))` syntax.
5
- - **ACTIONS** should be defined as methods inside the created store object.
6
- - **NEVER** mutate state directly. Always use the `set` function.
7
- - `set({ count: state.count + 1 })`