autoworkflow 3.1.5 → 3.5.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 (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,346 @@
1
+ # Jotai Skill
2
+
3
+ ## Primitive Atoms
4
+ \`\`\`typescript
5
+ import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
6
+
7
+ // Primitive atom (read-write)
8
+ const countAtom = atom(0);
9
+ const userAtom = atom<User | null>(null);
10
+ const isDarkModeAtom = atom(false);
11
+
12
+ // Usage in components
13
+ function Counter() {
14
+ // Read and write
15
+ const [count, setCount] = useAtom(countAtom);
16
+
17
+ // Read only
18
+ const count = useAtomValue(countAtom);
19
+
20
+ // Write only (doesn't subscribe to changes)
21
+ const setCount = useSetAtom(countAtom);
22
+
23
+ return (
24
+ <div>
25
+ <span>{count}</span>
26
+ <button onClick={() => setCount((c) => c + 1)}>+</button>
27
+ <button onClick={() => setCount(0)}>Reset</button>
28
+ </div>
29
+ );
30
+ }
31
+ \`\`\`
32
+
33
+ ## Derived Atoms (Computed Values)
34
+ \`\`\`typescript
35
+ // Read-only derived atom
36
+ const userNameAtom = atom((get) => get(userAtom)?.name ?? 'Guest');
37
+
38
+ const isLoggedInAtom = atom((get) => !!get(userAtom));
39
+
40
+ // Derived from multiple atoms
41
+ const totalAtom = atom((get) => {
42
+ const items = get(cartItemsAtom);
43
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
44
+ });
45
+
46
+ // Derived with async
47
+ const userPostsAtom = atom(async (get) => {
48
+ const user = get(userAtom);
49
+ if (!user) return [];
50
+
51
+ const response = await fetch(\`/api/users/\${user.id}/posts\`);
52
+ return response.json();
53
+ });
54
+
55
+ // Usage
56
+ function UserPosts() {
57
+ const posts = useAtomValue(userPostsAtom); // Suspense required
58
+ return <PostList posts={posts} />;
59
+ }
60
+ \`\`\`
61
+
62
+ ## Writable Derived Atoms
63
+ \`\`\`typescript
64
+ // Read-write derived atom
65
+ const uppercaseNameAtom = atom(
66
+ // Getter
67
+ (get) => get(nameAtom).toUpperCase(),
68
+ // Setter
69
+ (get, set, newName: string) => {
70
+ set(nameAtom, newName.toLowerCase());
71
+ }
72
+ );
73
+
74
+ // Lens-like pattern (update nested state)
75
+ const userNameAtom = atom(
76
+ (get) => get(userAtom)?.name ?? '',
77
+ (get, set, newName: string) => {
78
+ const user = get(userAtom);
79
+ if (user) {
80
+ set(userAtom, { ...user, name: newName });
81
+ }
82
+ }
83
+ );
84
+
85
+ // Action atom (write-only)
86
+ const incrementAtom = atom(null, (get, set) => {
87
+ set(countAtom, get(countAtom) + 1);
88
+ });
89
+
90
+ const fetchUserAtom = atom(null, async (get, set, userId: string) => {
91
+ set(loadingAtom, true);
92
+ try {
93
+ const response = await fetch(\`/api/users/\${userId}\`);
94
+ const user = await response.json();
95
+ set(userAtom, user);
96
+ } catch (error) {
97
+ set(errorAtom, (error as Error).message);
98
+ } finally {
99
+ set(loadingAtom, false);
100
+ }
101
+ });
102
+
103
+ // Usage
104
+ function UserLoader({ userId }: { userId: string }) {
105
+ const fetchUser = useSetAtom(fetchUserAtom);
106
+
107
+ useEffect(() => {
108
+ fetchUser(userId);
109
+ }, [userId, fetchUser]);
110
+
111
+ return <UserProfile />;
112
+ }
113
+ \`\`\`
114
+
115
+ ## Atom Families
116
+ \`\`\`typescript
117
+ import { atomFamily } from 'jotai/utils';
118
+
119
+ // Create atoms dynamically by parameter
120
+ const todoAtomFamily = atomFamily((id: string) =>
121
+ atom<Todo | null>(null)
122
+ );
123
+
124
+ const userByIdAtom = atomFamily((userId: string) =>
125
+ atom(async () => {
126
+ const response = await fetch(\`/api/users/\${userId}\`);
127
+ return response.json();
128
+ })
129
+ );
130
+
131
+ // Usage
132
+ function TodoItem({ id }: { id: string }) {
133
+ const [todo, setTodo] = useAtom(todoAtomFamily(id));
134
+ // ...
135
+ }
136
+
137
+ function UserCard({ userId }: { userId: string }) {
138
+ const user = useAtomValue(userByIdAtom(userId));
139
+ // ...
140
+ }
141
+
142
+ // With default value
143
+ const settingAtomFamily = atomFamily(
144
+ (key: string) => atom(localStorage.getItem(key) ?? ''),
145
+ (a, b) => a === b // Equality check
146
+ );
147
+ \`\`\`
148
+
149
+ ## Persistence
150
+ \`\`\`typescript
151
+ import { atomWithStorage } from 'jotai/utils';
152
+
153
+ // Auto-sync with localStorage
154
+ const themeAtom = atomWithStorage('theme', 'light');
155
+ const userPrefsAtom = atomWithStorage<UserPrefs>('userPrefs', {
156
+ notifications: true,
157
+ language: 'en',
158
+ });
159
+
160
+ // Custom storage (sessionStorage)
161
+ const sessionAtom = atomWithStorage('session', null, {
162
+ getItem: (key) => {
163
+ const value = sessionStorage.getItem(key);
164
+ return value ? JSON.parse(value) : null;
165
+ },
166
+ setItem: (key, value) => {
167
+ sessionStorage.setItem(key, JSON.stringify(value));
168
+ },
169
+ removeItem: (key) => {
170
+ sessionStorage.removeItem(key);
171
+ },
172
+ });
173
+
174
+ // Async storage (e.g., IndexedDB)
175
+ import { createStore, get as idbGet, set as idbSet, del as idbDel } from 'idb-keyval';
176
+
177
+ const idbStorage = {
178
+ getItem: async (key: string) => await idbGet(key),
179
+ setItem: async (key: string, value: unknown) => await idbSet(key, value),
180
+ removeItem: async (key: string) => await idbDel(key),
181
+ };
182
+
183
+ const persistedAtom = atomWithStorage('key', defaultValue, idbStorage);
184
+ \`\`\`
185
+
186
+ ## Async Atoms & Suspense
187
+ \`\`\`typescript
188
+ import { atom, useAtomValue } from 'jotai';
189
+ import { Suspense } from 'react';
190
+ import { loadable } from 'jotai/utils';
191
+
192
+ // Async atom (requires Suspense)
193
+ const userAtom = atom(async () => {
194
+ const response = await fetch('/api/user');
195
+ return response.json();
196
+ });
197
+
198
+ // With Suspense
199
+ function App() {
200
+ return (
201
+ <Suspense fallback={<div>Loading...</div>}>
202
+ <UserProfile />
203
+ </Suspense>
204
+ );
205
+ }
206
+
207
+ function UserProfile() {
208
+ const user = useAtomValue(userAtom); // Suspends until resolved
209
+ return <div>{user.name}</div>;
210
+ }
211
+
212
+ // Without Suspense (using loadable)
213
+ const loadableUserAtom = loadable(userAtom);
214
+
215
+ function UserProfileNoSuspense() {
216
+ const userLoadable = useAtomValue(loadableUserAtom);
217
+
218
+ if (userLoadable.state === 'loading') return <div>Loading...</div>;
219
+ if (userLoadable.state === 'hasError') return <div>Error!</div>;
220
+ return <div>{userLoadable.data.name}</div>;
221
+ }
222
+
223
+ // Refresh async atom
224
+ import { useAtomRefresher } from 'jotai/utils';
225
+
226
+ function UserProfile() {
227
+ const user = useAtomValue(userAtom);
228
+ const refresh = useAtomRefresher(userAtom);
229
+
230
+ return (
231
+ <div>
232
+ <span>{user.name}</span>
233
+ <button onClick={refresh}>Refresh</button>
234
+ </div>
235
+ );
236
+ }
237
+ \`\`\`
238
+
239
+ ## Reset & Utils
240
+ \`\`\`typescript
241
+ import { atomWithReset, useResetAtom, RESET } from 'jotai/utils';
242
+
243
+ // Resettable atom
244
+ const filterAtom = atomWithReset({
245
+ search: '',
246
+ category: 'all',
247
+ sortBy: 'date',
248
+ });
249
+
250
+ function Filters() {
251
+ const [filter, setFilter] = useAtom(filterAtom);
252
+ const resetFilter = useResetAtom(filterAtom);
253
+
254
+ return (
255
+ <div>
256
+ <input
257
+ value={filter.search}
258
+ onChange={(e) => setFilter((f) => ({ ...f, search: e.target.value }))}
259
+ />
260
+ <button onClick={resetFilter}>Reset Filters</button>
261
+ {/* Or: setFilter(RESET) */}
262
+ </div>
263
+ );
264
+ }
265
+
266
+ // Select atom (derived with equality)
267
+ import { selectAtom } from 'jotai/utils';
268
+
269
+ const userNameAtom = selectAtom(userAtom, (user) => user?.name);
270
+
271
+ // Focus atom (lens for nested state)
272
+ import { focusAtom } from 'jotai-optics';
273
+
274
+ const nameAtom = focusAtom(userAtom, (optic) => optic.prop('name'));
275
+ \`\`\`
276
+
277
+ ## Provider & Store
278
+ \`\`\`typescript
279
+ import { Provider, createStore, useStore } from 'jotai';
280
+
281
+ // Create isolated store
282
+ const myStore = createStore();
283
+
284
+ // Set initial values
285
+ myStore.set(countAtom, 10);
286
+ myStore.set(userAtom, { id: '1', name: 'John' });
287
+
288
+ // Get value outside React
289
+ const count = myStore.get(countAtom);
290
+
291
+ // Subscribe outside React
292
+ const unsubscribe = myStore.sub(countAtom, () => {
293
+ console.log('Count changed:', myStore.get(countAtom));
294
+ });
295
+
296
+ // Provide store to React tree
297
+ function App() {
298
+ return (
299
+ <Provider store={myStore}>
300
+ <Counter />
301
+ </Provider>
302
+ );
303
+ }
304
+
305
+ // Access store in component
306
+ function Debug() {
307
+ const store = useStore();
308
+ console.log('Store:', store.get(countAtom));
309
+ return null;
310
+ }
311
+ \`\`\`
312
+
313
+ ## DevTools
314
+ \`\`\`typescript
315
+ import { useAtomsDebugValue } from 'jotai-devtools';
316
+
317
+ // In development
318
+ function DebugAtoms() {
319
+ useAtomsDebugValue();
320
+ return null;
321
+ }
322
+
323
+ function App() {
324
+ return (
325
+ <Provider>
326
+ {process.env.NODE_ENV === 'development' && <DebugAtoms />}
327
+ <MainApp />
328
+ </Provider>
329
+ );
330
+ }
331
+ \`\`\`
332
+
333
+ ## ❌ DON'T
334
+ - Create atoms inside components (recreates on every render)
335
+ - Forget Suspense for async atoms
336
+ - Use useAtom when you only need read or write
337
+ - Mutate atom values directly
338
+
339
+ ## ✅ DO
340
+ - Define atoms outside components (module level)
341
+ - Use useAtomValue for read-only subscriptions
342
+ - Use useSetAtom when you don't need the value
343
+ - Use atomFamily for dynamic/parameterized atoms
344
+ - Use atomWithStorage for persistence
345
+ - Use loadable for non-Suspense async handling
346
+ - Use derived atoms for computed values
@@ -0,0 +1,353 @@
1
+ # MobX Skill
2
+
3
+ ## Basic Store
4
+ \`\`\`typescript
5
+ import { makeAutoObservable, runInAction } from 'mobx';
6
+
7
+ interface User {
8
+ id: string;
9
+ email: string;
10
+ name: string;
11
+ }
12
+
13
+ class UserStore {
14
+ // Observable state
15
+ user: User | null = null;
16
+ loading = false;
17
+ error: string | null = null;
18
+
19
+ constructor() {
20
+ // Automatically makes all properties observable,
21
+ // getters computed, and methods actions
22
+ makeAutoObservable(this);
23
+ }
24
+
25
+ // Computed value (automatically cached)
26
+ get isLoggedIn() {
27
+ return !!this.user;
28
+ }
29
+
30
+ get displayName() {
31
+ return this.user?.name ?? 'Guest';
32
+ }
33
+
34
+ // Synchronous action
35
+ setUser(user: User) {
36
+ this.user = user;
37
+ }
38
+
39
+ logout() {
40
+ this.user = null;
41
+ this.error = null;
42
+ }
43
+
44
+ // Async action
45
+ async login(email: string, password: string) {
46
+ this.loading = true;
47
+ this.error = null;
48
+
49
+ try {
50
+ const response = await fetch('/api/auth/login', {
51
+ method: 'POST',
52
+ body: JSON.stringify({ email, password }),
53
+ });
54
+
55
+ if (!response.ok) throw new Error('Login failed');
56
+
57
+ const user = await response.json();
58
+
59
+ // Must wrap state changes after await in runInAction
60
+ runInAction(() => {
61
+ this.user = user;
62
+ this.loading = false;
63
+ });
64
+ } catch (error) {
65
+ runInAction(() => {
66
+ this.error = (error as Error).message;
67
+ this.loading = false;
68
+ });
69
+ }
70
+ }
71
+ }
72
+
73
+ export const userStore = new UserStore();
74
+ \`\`\`
75
+
76
+ ## Manual Observable Configuration
77
+ \`\`\`typescript
78
+ import { makeObservable, observable, computed, action, flow } from 'mobx';
79
+
80
+ class TodoStore {
81
+ todos: Todo[] = [];
82
+ filter: 'all' | 'active' | 'completed' = 'all';
83
+
84
+ constructor() {
85
+ makeObservable(this, {
86
+ // State
87
+ todos: observable,
88
+ filter: observable,
89
+ // Computed
90
+ filteredTodos: computed,
91
+ totalCount: computed,
92
+ // Actions
93
+ addTodo: action,
94
+ toggleTodo: action,
95
+ removeTodo: action,
96
+ // Generators (for async)
97
+ fetchTodos: flow,
98
+ });
99
+ }
100
+
101
+ get filteredTodos() {
102
+ switch (this.filter) {
103
+ case 'active':
104
+ return this.todos.filter((t) => !t.completed);
105
+ case 'completed':
106
+ return this.todos.filter((t) => t.completed);
107
+ default:
108
+ return this.todos;
109
+ }
110
+ }
111
+
112
+ get totalCount() {
113
+ return this.todos.length;
114
+ }
115
+
116
+ addTodo(text: string) {
117
+ this.todos.push({
118
+ id: crypto.randomUUID(),
119
+ text,
120
+ completed: false,
121
+ });
122
+ }
123
+
124
+ toggleTodo(id: string) {
125
+ const todo = this.todos.find((t) => t.id === id);
126
+ if (todo) {
127
+ todo.completed = !todo.completed;
128
+ }
129
+ }
130
+
131
+ removeTodo(id: string) {
132
+ this.todos = this.todos.filter((t) => t.id !== id);
133
+ }
134
+
135
+ // Flow for async (alternative to runInAction)
136
+ *fetchTodos() {
137
+ this.loading = true;
138
+ try {
139
+ const response = yield fetch('/api/todos');
140
+ const todos = yield response.json();
141
+ this.todos = todos;
142
+ } finally {
143
+ this.loading = false;
144
+ }
145
+ }
146
+ }
147
+ \`\`\`
148
+
149
+ ## React Integration
150
+ \`\`\`tsx
151
+ import { observer } from 'mobx-react-lite';
152
+ import { userStore } from './stores/userStore';
153
+
154
+ // Wrap component with observer to react to observable changes
155
+ const UserProfile = observer(() => {
156
+ const { user, isLoggedIn, loading, logout } = userStore;
157
+
158
+ if (loading) return <div>Loading...</div>;
159
+
160
+ if (!isLoggedIn) {
161
+ return <button onClick={() => userStore.login('test@example.com', 'password')}>Login</button>;
162
+ }
163
+
164
+ return (
165
+ <div>
166
+ <h1>Welcome, {user?.name}</h1>
167
+ <button onClick={logout}>Logout</button>
168
+ </div>
169
+ );
170
+ });
171
+
172
+ // With local observable state
173
+ import { useLocalObservable } from 'mobx-react-lite';
174
+
175
+ const Counter = observer(() => {
176
+ const state = useLocalObservable(() => ({
177
+ count: 0,
178
+ increment() {
179
+ this.count++;
180
+ },
181
+ decrement() {
182
+ this.count--;
183
+ },
184
+ get doubled() {
185
+ return this.count * 2;
186
+ },
187
+ }));
188
+
189
+ return (
190
+ <div>
191
+ <span>{state.count} (doubled: {state.doubled})</span>
192
+ <button onClick={state.increment}>+</button>
193
+ <button onClick={state.decrement}>-</button>
194
+ </div>
195
+ );
196
+ });
197
+ \`\`\`
198
+
199
+ ## Context Provider Pattern
200
+ \`\`\`tsx
201
+ import { createContext, useContext } from 'react';
202
+ import { UserStore } from './stores/UserStore';
203
+ import { TodoStore } from './stores/TodoStore';
204
+
205
+ // Root store combining all stores
206
+ class RootStore {
207
+ userStore: UserStore;
208
+ todoStore: TodoStore;
209
+
210
+ constructor() {
211
+ this.userStore = new UserStore(this);
212
+ this.todoStore = new TodoStore(this);
213
+ }
214
+ }
215
+
216
+ const StoreContext = createContext<RootStore | null>(null);
217
+
218
+ export function StoreProvider({ children }: { children: React.ReactNode }) {
219
+ const store = new RootStore();
220
+ return (
221
+ <StoreContext.Provider value={store}>
222
+ {children}
223
+ </StoreContext.Provider>
224
+ );
225
+ }
226
+
227
+ export function useStore() {
228
+ const store = useContext(StoreContext);
229
+ if (!store) throw new Error('useStore must be used within StoreProvider');
230
+ return store;
231
+ }
232
+
233
+ // Usage
234
+ const TodoList = observer(() => {
235
+ const { todoStore } = useStore();
236
+ return (
237
+ <ul>
238
+ {todoStore.filteredTodos.map((todo) => (
239
+ <li key={todo.id}>{todo.text}</li>
240
+ ))}
241
+ </ul>
242
+ );
243
+ });
244
+ \`\`\`
245
+
246
+ ## Reactions
247
+ \`\`\`typescript
248
+ import { autorun, reaction, when } from 'mobx';
249
+
250
+ // autorun - runs immediately and on every change
251
+ const dispose1 = autorun(() => {
252
+ console.log('User:', userStore.user?.name);
253
+ });
254
+
255
+ // reaction - only runs when specific data changes
256
+ const dispose2 = reaction(
257
+ () => userStore.user, // Data function
258
+ (user, previousUser) => { // Effect function
259
+ console.log('User changed from', previousUser, 'to', user);
260
+ if (user) {
261
+ localStorage.setItem('lastUserId', user.id);
262
+ }
263
+ },
264
+ { fireImmediately: false } // Don't run on setup
265
+ );
266
+
267
+ // when - runs once when condition is met
268
+ const dispose3 = when(
269
+ () => userStore.isLoggedIn,
270
+ () => {
271
+ console.log('User logged in!');
272
+ // Fetch user-specific data
273
+ }
274
+ );
275
+
276
+ // Cleanup
277
+ dispose1();
278
+ dispose2();
279
+ dispose3();
280
+
281
+ // In React useEffect
282
+ useEffect(() => {
283
+ const dispose = autorun(() => {
284
+ document.title = \`\${todoStore.totalCount} todos\`;
285
+ });
286
+
287
+ return () => dispose();
288
+ }, []);
289
+ \`\`\`
290
+
291
+ ## Persistence
292
+ \`\`\`typescript
293
+ import { makePersistable } from 'mobx-persist-store';
294
+
295
+ class SettingsStore {
296
+ theme: 'light' | 'dark' = 'light';
297
+ language = 'en';
298
+
299
+ constructor() {
300
+ makeAutoObservable(this);
301
+
302
+ makePersistable(this, {
303
+ name: 'SettingsStore',
304
+ properties: ['theme', 'language'],
305
+ storage: window.localStorage,
306
+ });
307
+ }
308
+
309
+ setTheme(theme: 'light' | 'dark') {
310
+ this.theme = theme;
311
+ }
312
+ }
313
+
314
+ // Or manual persistence with reaction
315
+ class UserStore {
316
+ constructor() {
317
+ makeAutoObservable(this);
318
+
319
+ // Load from storage
320
+ const stored = localStorage.getItem('user');
321
+ if (stored) {
322
+ this.user = JSON.parse(stored);
323
+ }
324
+
325
+ // Save on changes
326
+ reaction(
327
+ () => this.user,
328
+ (user) => {
329
+ if (user) {
330
+ localStorage.setItem('user', JSON.stringify(user));
331
+ } else {
332
+ localStorage.removeItem('user');
333
+ }
334
+ }
335
+ );
336
+ }
337
+ }
338
+ \`\`\`
339
+
340
+ ## ❌ DON'T
341
+ - Forget to wrap components with observer
342
+ - Mutate state outside of actions
343
+ - Forget runInAction after await
344
+ - Destructure observables (loses reactivity)
345
+ - Create stores inside components
346
+
347
+ ## ✅ DO
348
+ - Use makeAutoObservable for simple stores
349
+ - Use observer() for all components using observables
350
+ - Use runInAction or flow for async mutations
351
+ - Use reactions for side effects
352
+ - Access store properties directly (not destructured)
353
+ - Create stores once and share via context