Skip to content

Commit ebc2fa7

Browse files
authored
chore: Update docs (#674)
1 parent 3f67f73 commit ebc2fa7

File tree

3 files changed

+67
-102
lines changed

3 files changed

+67
-102
lines changed

CLAUDE.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@
5555
- TypeScript strict mode enabled
5656
- Tailwind CSS classes should be sorted (biome `useSortedClasses` rule)
5757

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+
await this.interrupt(); // hangs: waits for query to stop
65+
this.abortController.abort(); // never reached
66+
67+
// RIGHT - abort first so the operation can actually stop
68+
this.abortController.abort(); // cancels in-flight HTTP requests
69+
await this.interrupt(); // resolves because the query was aborted
70+
```
71+
5872
### Avoid Barrel Files
5973

6074
- Do not make use of index.ts
@@ -74,11 +88,10 @@ See [ARCHITECTURE.md](./apps/twig/ARCHITECTURE.md) for detailed patterns (DI, se
7488

7589
### Electron App (apps/twig)
7690

77-
- **Main process** (`src/main/`) - Stateless services, tRPC routers, system I/O
78-
- **Renderer process** (`src/renderer/`) - React app, all application state
91+
- **Main process** (`src/main/`) - Services own all business logic, orchestration, polling, data fetching, and system I/O
92+
- **Renderer process** (`src/renderer/`) - React app with Zustand stores holding pure UI state and thin action wrappers over tRPC
7993
- **IPC**: tRPC over Electron IPC (type-safe via @posthog/electron-trpc)
8094
- **DI**: InversifyJS in both processes (`src/main/di/`, `src/renderer/di/`)
81-
- **State**: Zustand stores in renderer only - main is stateless
8295
- **Testing**: Vitest with React Testing Library
8396

8497
### Agent Package (packages/agent)
@@ -158,9 +171,53 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
158171
}
159172
```
160173

174+
### Store / Service Boundary
175+
176+
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+
161218
### Zustand Stores
162219

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:
164221

165222
```typescript
166223
interface SidebarStoreState {
@@ -215,7 +272,7 @@ export const gitRouter = router({
215272

216273
### Services (Main Process)
217274

218-
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:
219276

220277
```typescript
221278
@injectable()

PROBLEM.md

Lines changed: 0 additions & 82 deletions
This file was deleted.

README.md

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
> [!IMPORTANT]
22
> Twig is pre-alpha and not production-ready. Interested? Email [email protected]
33
4-
**[Download the latest desktop app release](https://github.com/PostHog/twig/releases/latest)**
4+
**[Download the latest version](https://github.com/PostHog/twig/releases/latest)**
5+
6+
Found a bug or have feedback? [Open an issue](https://github.com/PostHog/twig/issues/new) on GitHub.
57

68
# Twig
79

@@ -54,7 +56,7 @@ twig/
5456
├── apps/
5557
│ ├── twig/ # Electron desktop app (React, Vite)
5658
│ ├── mobile/ # React Native mobile app (Expo)
57-
│ └── cli/ # arr CLI for stacked PRs
59+
│ └── cli/ # CLI for stacked PRs
5860
├── packages/
5961
│ ├── agent/ # TypeScript agent framework
6062
│ ├── core/ # Shared business logic
@@ -107,16 +109,4 @@ Native modules (like node-pty) need to be rebuilt for your Electron version:
107109

108110
```bash
109111
pnpm --filter twig exec electron-rebuild
110-
```
111-
112-
## Acknowledgments
113-
114-
Built with love by the PostHog team.
115-
116-
## Roadmap
117-
118-
Stay tuned for upcoming features and improvements.
119-
120-
## FAQ
121-
122-
Check the issues page for common questions and answers.
112+
```

0 commit comments

Comments
 (0)