You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+62-5Lines changed: 62 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,6 +55,20 @@
55
55
- TypeScript strict mode enabled
56
56
- Tailwind CSS classes should be sorted (biome `useSortedClasses` rule)
57
57
58
+
### Async Cleanup Ordering
59
+
60
+
When tearing down async operations that use an AbortController, always abort the controller **before** awaiting any cleanup that depends on it. Otherwise you get a deadlock: the cleanup waits for the operation to stop, but the operation won't stop until the abort signal fires.
61
+
62
+
```typescript
63
+
// WRONG - deadlocks if interrupt() waits for the operation to finish
64
+
awaitthis.interrupt(); // hangs: waits for query to stop
65
+
this.abortController.abort(); // never reached
66
+
67
+
// RIGHT - abort first so the operation can actually stop
Stores and services have a strict separation of concerns:
177
+
178
+
```
179
+
Renderer Main Process
180
+
+------------------+ +------------------+
181
+
| Zustand Store | -- tRPC --> | tRPC Router |
182
+
| | <-- subs -- +------------------+
183
+
| - Pure state | |
184
+
| - Event cache | +------------------+
185
+
| - UI concerns | | Service |
186
+
| - Thin actions | | |
187
+
+------------------+ | - Orchestration |
188
+
| | - Polling |
189
+
+------------------+ | - Data fetching |
190
+
| Service | | - Business logic |
191
+
| | +------------------+
192
+
| - Cross-store |
193
+
| coordination |
194
+
| - Client-side |
195
+
| state machines |
196
+
+------------------+
197
+
```
198
+
199
+
**Renderer stores own:**
200
+
- Pure UI state (open/closed, selected item, scroll position)
201
+
- Cached data from subscriptions
202
+
- Message queues and event buffers
203
+
- Permission display state
204
+
- Thin action wrappers that call tRPC mutations
205
+
206
+
**Renderer services own:**
207
+
- Coordination between multiple stores
208
+
- Client-side-only state machines and logic
209
+
210
+
**Main process services own:**
211
+
- Business logic and orchestration
212
+
- Polling loops and background work
213
+
- Data fetching, parsing, and transformation
214
+
- Connection management and coordination between services
215
+
216
+
Stores should never contain business logic, orchestration, or data fetching. If a store action does more than update local state or call a single tRPC method, that logic belongs in a service. Services typically live in the main process, but renderer-side services are fine when the logic is purely client-side (e.g., coordinating between stores, managing local-only state machines).
217
+
161
218
### Zustand Stores
162
219
163
-
Stores separate state and actions with persistence middleware:
220
+
Stores hold pure state with thin actions. Separate state and action interfaces, use persistence middleware where needed:
Services are injectable, stateless, and can emit events:
275
+
Services are injectable, own all business logic, and emit events to the renderer via tRPC subscriptions. Orchestration, polling, data fetching, and coordination between services all belong here - not in stores:
0 commit comments