| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 1 | 13 | 18 | 3% |
| Commands | 0 | 2 | 9 | 0% |
| Section tags | 2 | 5 | 4 | 18% |
What each file covers
Sections
1 shared · 13 only in A · 18 only in B- − iOS Package (Native iOS App)
- − Package Structure
- − Key Architecture Decisions
- − Authentication Flow
- − Data Flow
- − iPad Support
- − Development
- − Prerequisites
- − Commands
- − From monorepo root:
- − From packages/ios/:
- − Transcript Bundle
- − Key Files
- + Electron Package
- + Development Commands
- + Architecture
- + Main and Renderer Processes
- + IPC Communication
- + Preload API
- + Document Service
- + Common IPC Issues
- + Data Persistence
- + Renderer State Architecture
- + Logging
- + Window State Persistence
- + Theme Support
- + File Operations
- + AI Providers
- + macOS Code Signing & Notarization
- + Git Worktree Integration
- + Analytics
- Testing
Commands
0 shared · 2 only in A · 9 only in B- − npm run ios:test:swift
- − npm run ios:build:transcript
- + npm run dev
- + npm run dev:loop
- + npm run build:mac:local
- + npm run build:mac:notarized
- + npm run dev:user2
- + npm run dev:url-handler
- + npx playwright test e2e/monaco/file-watcher-updates.spec.ts
- + npx playwright test e2e/monaco/
- + npx playwright test
Section tags
2 shared · 5 only in A · 4 only in B- − setup
- − architecture
- − security
- − dependencies
- − monorepo
- + git-pr
- + api
- + do-not
- + agent-behaviour
- build
- test
Line diff
nimbalyst/nimbalyst · packages/ios/CLAUDE.md
@@ −1 @@
1# iOS Package (Native iOS App)
2
3This package contains the native SwiftUI iOS/iPadOS app for Nimbalyst. It provides a mobile interface for viewing and interacting with AI sessions synced from the desktop Electron app via end-to-end encrypted WebSocket sync.
4
5The app is **pure Swift/SwiftUI** with no Capacitor or web framework dependency. The only web view is `TranscriptWebView` (WKWebView) which renders the rich chat transcript using the same React components as the desktop app.
6
7## Package Structure
8
9```
10packages/ios/
11 NimbalystNative/ # Swift Package - all business logic and UI
12 Sources/
13 App/ # AppState (root observable), ContentView, navigation
14 Auth/ # AuthManager (Stytch OAuth via ASWebAuthenticationSession)
15 Crypto/ # CryptoManager (AES-256-GCM, PBKDF2), KeychainManager
16 Database/ # DatabaseManager (GRDB migrations, queries)
17 Models/ # GRDB record types: Project, Session, Message, QueuedPrompt, SyncState
18 Notifications/ # NotificationManager (push notification registration)
19 Sync/ # SyncManager, WebSocketClient, SyncProtocol types
20 Utils/ # RelativeTimestamp, NimbalystColors
21 Views/ # All SwiftUI views
22 Tests/ # Unit and integration tests (68 tests)
23 Package.swift # Swift Package Manager manifest (GRDB dependency)
24
25 NimbalystApp/ # Xcode app target
26 Sources/ # App entry point (@main), DebugMenu
27 Resources/ # Assets.xcassets (AppIcon, Splash), transcript-dist bundle
28 project.yml # XcodeGen project definition
29
30 CryptoCompatibility/ # CommonCrypto bridging header for PBKDF2 key derivation
31
32 src/transcript/ # React transcript web bundle (loaded in WKWebView)
33 main.tsx # Entry point with Swift <-> JS bridge
34 styles.css # Styles with bundled Material Symbols font
35 fonts/ # Locally bundled Material Symbols TTF
36
37 vite.config.transcript.ts # Vite config for transcript bundle (IIFE format for file://)
38 transcript.html # HTML entry point for Vite build
39 dist-transcript/ # Build output (not committed)
40```
41
42## Key Architecture Decisions
43
44### Authentication Flow
451. QR pairing stores encryption seed + server URL in Keychain
462. Stytch OAuth stores JWT + user ID in Keychain
473. When both paired AND authenticated, managers initialize
484. Encryption key derived from seed + user ID via PBKDF2
49
50### Data Flow
51- **Sync**: WebSocket connection to CollabV3 Durable Object (same server as desktop)
52- **Encryption**: All session data encrypted with AES-256-GCM before transmission
53- **Storage**: GRDB (SQLite) with reactive `ValueObservation` for live UI updates
54- **Transcript**: WKWebView loads bundled React app, communicates via `webkit.messageHandlers.bridge`
55
56### iPad Support
57- `NavigationSplitView` for regular size class (sidebar + detail)
58- `NavigationStack` for compact size class (iPhone)
59
60## Development
61
62### Prerequisites
63- Xcode 16+
64- Node.js 20+ (for transcript bundle)
65- XcodeGen (`brew install xcodegen`)
66
67### Commands
68```bash
69# From monorepo root:
70npm run ios:test:swift # Run all 68 Swift tests
71npm run ios:build:transcript # Build transcript web bundle
72
73# From packages/ios/:
74cd NimbalystNative && swift test # Run tests directly
75cd NimbalystApp && xcodegen generate # Regenerate .xcodeproj
76open NimbalystApp/NimbalystApp.xcodeproj # Open in Xcode
77```
78
79### Transcript Bundle
80The Xcode pre-build script in `project.yml` automatically builds the transcript with Vite and copies it to `Resources/transcript-dist/`. You can also build manually:
81
82```bash
83npm run ios:build:transcript
84```
85
86Output: `dist-transcript/transcript.html` + `dist-transcript/assets/` (JS bundle + Material Symbols font).
87
88After building, copy the output to Xcode resources:
89```bash
90rm -f NimbalystApp/Resources/transcript-dist/assets/transcript-*.js
91cp dist-transcript/transcript.html NimbalystApp/Resources/transcript-dist/transcript.html
92cp dist-transcript/assets/* NimbalystApp/Resources/transcript-dist/assets/
93```
94
95**CRITICAL: React hooks rules in `src/transcript/main.tsx`**
96
97The transcript React app runs inside WKWebView where errors are invisible (cross-origin `window.onerror` reports "Script error." with no details). This makes hooks violations especially dangerous -- the screen goes blank with no diagnostic information.
98
99Rules for editing `TranscriptApp` in `main.tsx`:
100- **All hooks (`useState`, `useRef`, `useCallback`, `useMemo`, `useEffect`) must come BEFORE any early returns.** React requires the same hooks to run in the same order on every render. An early `return` before a hook means that hook runs on some renders but not others, crashing React with "Rendered more hooks than during the previous render."
101- **The `TranscriptErrorBoundary` wraps the app** to catch render errors and display them on screen + report to the native bridge. Do not remove it.
102- **The `postErrorToNative` helper** sends error details through `webkit.messageHandlers.bridge` so they appear in Xcode console logs with full stack traces. Use it in any new try-catch blocks.
103- **Test after any change**: Always rebuild the transcript (`npm run ios:build:transcript`), copy to Xcode resources, and rebuild in Xcode. Vite build success does NOT mean React will render correctly at runtime.
104
105## Key Files
106
107| File | Purpose |
108|------|---------|
109| `Sources/App/AppState.swift` | Root observable object; owns database, crypto, and sync managers |
110| `Sources/Sync/SyncManager.swift` | WebSocket sync with CollabV3; processes index responses and broadcasts |
111| `Sources/Sync/SyncProtocol.swift` | All wire protocol types (Codable structs with CodingKeys) |
112| `Sources/Database/DatabaseManager.swift` | GRDB schema migrations, queries, and project stats refresh |
113| `Sources/Crypto/CryptoManager.swift` | AES-256-GCM encrypt/decrypt, deterministic project ID encryption |
114| `Sources/Views/TranscriptWebView.swift` | WKWebView + Coordinator with JS bridge, TranscriptController |
115| `Sources/Views/SessionDetailView.swift` | Session detail with transcript, scroll-to-top, jump-to-prompt |
116| `Sources/Views/SessionListView.swift` | Time-grouped session list with search and swipe-to-delete |
117| `Sources/Views/ProjectListView.swift` | Project list sorted by last activity with desktop connection indicator |
118| `src/transcript/main.tsx` | React transcript app with `scrollToTop`, `scrollToMessage`, `getPromptList` JS bridge |
119
120## Testing
121- 68 Swift tests covering database, crypto, sync integration, and web view
122- See [TESTING.md](./TESTING.md) for CI/CD pipeline details
123- Tests run on both macOS (via Swift Package Manager) and iOS simulator (via Xcode)
124
nimbalyst/nimbalyst · packages/electron/CLAUDE.md
@@ +1 @@
1# Electron Package
2
3The Nimbalyst desktop app, built with Electron.
4
5## Development Commands
6
7- **Dev server**: `npm run dev` (user runs this — don't do it yourself)
8- **Dev with restart loop**: `npm run dev:loop` (enables restart button / `/restart` command)
9- **Build for Mac**: `npm run build:mac:local` or `npm run build:mac:notarized`
10- **Auth callbacks in dev**: no URL-handler setup is required, including for `npm run dev:user2`. Every sign-in flow uses a nonce-protected one-shot listener on `127.0.0.1` owned by the instance that started it.
11- **Other deep links in dev (macOS)**: `npm run dev:url-handler` (from the repo root) points non-auth `nimbalyst://` links at this checkout. The applet in `scripts/install-dev-url-handler.sh` is not part of authentication. The dev app deliberately does *not* claim the scheme itself because all development copies share Electron's `com.github.Electron` bundle id. See `src/main/utils/protocolRegistration.ts`.
12
13### Testing
14
15From the repository root:
16- Run one spec: `npx playwright test e2e/monaco/file-watcher-updates.spec.ts`
17- Run a directory: `npx playwright test e2e/monaco/`
18- Run all: `npx playwright test`
19
20**Always use `npx playwright test` directly.** Never use parallel execution — it corrupts PGLite. See [/docs/E2E_TESTING.md](/docs/E2E_TESTING.md).
21
22## Architecture
23
24### Main and Renderer Processes
25
26Electron apps split into two contexts:
27- **Main** runs Node.js, manages lifecycle, windows, menus, system interactions.
28- **Renderer** runs in a Chromium context; UI only.
29
30Renderers cannot access Node.js APIs directly — use IPC to request main-process services. For initialization rules (dynamic import in `bootstrap.ts`, lazy init for `app.getPath()` consumers, `safeHandle` / `safeOn`), and cross-platform code patterns, see [MAIN_PROCESS_INIT.md](./MAIN_PROCESS_INIT.md).
31
32## IPC Communication
33
34### Preload API
35- **Location**: `src/preload/index.ts`
36- **Exposed as**: `window.electronAPI` (NOT `window.api`)
37- **Generic methods**: `invoke`, `send`, `on` (returns an unsubscribe closure — there is no `off`, see [/docs/IPC_LISTENERS.md](/docs/IPC_LISTENERS.md))
38- Renderer services use these to talk to main-process services.
39
40### Document Service
41- Main: `ElectronDocumentService` (file scanning, metadata extraction, caching)
42- Renderer: `RendererDocumentService` (facade over IPC)
43- **Metadata**: frontmatter extraction with bounded reads (4KB)
44- **Channels**: `document-service:*`
45
46### Common IPC Issues
47- `window.api undefined` → use `window.electronAPI`
48- Empty responses → check the window has a valid workspace path
49- Service resolution is keyed off workspace path
50
51For deep IPC patterns (`safeHandle`/`safeOn`, error handling, channel structure), see [/docs/IPC_GUIDE.md](/docs/IPC_GUIDE.md).
52
53## Data Persistence
54
55The app runs over **either PGLite (PostgreSQL in WebAssembly) or better-sqlite3** — both backends are active during the in-progress migration. Code must work on either; do not assume one. **Never use `localStorage` in the renderer.** Persist via IPC to main using:
56- **app-settings store** (`src/main/utils/store.ts`) for global app settings
57- **workspace-settings store** for per-project state
58- **AppDatabase** (PGLite or SQLite, selected at init) for complex data (AI sessions, document history, trackers)
59
60The biggest divergence to remember: `data->'key'` returns a parsed object on PGLite but a JSON string on SQLite. For tables, locations, shutdown rules, timestamp handling, and the full list of backend-divergent behaviors, see [DATABASE.md](./DATABASE.md).
61
62## Renderer State Architecture
63
64The renderer uses Jotai for state that crosses component boundaries. Editors use **EditorHost** — a stable service object — for all host communication; content state lives in the editor, not parent components.
65
66| Domain | Atoms | Owner |
67| --- | --- | --- |
68| Theme | `themeAtom` | Global, IPC-synced |
69| Editors | `editorDirtyAtom(key)`, `editorProcessingAtom(key)` | EditorHost writes, Tab reads |
70| Sessions | `sessionUnreadAtom(id)`, `sessionProcessingAtom(id)` | AgenticPanel writes, UI reads |
71| File Tree | `gitStatusAtom`, `expandedDirsAtom` | WorkspaceSidebar writes, FileTree reads |
72| Trackers | `trackerCountsAtom` | TrackerService writes, UI reads |
73
74**Re-render isolation**: parents subscribe to lists of IDs; children subscribe to their own atoms. If you need `React.memo` to prevent re-renders, you have the wrong architecture.
75
76For full patterns, see [/docs/EDITOR_STATE.md](/docs/EDITOR_STATE.md) and [/docs/JOTAI.md](/docs/JOTAI.md).
77
78## Logging
79
80Three log destinations:
81
82- **Main process log**: `~/Library/Application Support/@nimbalyst/electron/logs/main.log` — main-process events, AI, sync, file ops; categories like `(MAIN)`, `(AI)`, `(API)`, `(SYNC)`.
83- **Renderer console log** (dev mode only): `~/Library/Application Support/@nimbalyst/electron/nimbalyst-debug.log` — captured via `webContents.on('console-message')` in `src/main/index.ts`.
84
85Use the agent log access tools (`get_main_process_logs`, `get_renderer_debug_logs`) instead of asking users to paste logs. See [/docs/DEBUGGING_LOGS.md](/docs/DEBUGGING_LOGS.md).
86
87## Window State Persistence
88
89- **Global session state** restores all windows on restart (bounds, focus order, dev tools state).
90- **Per-project state** restores window configuration, open file, AI panel width and collapsed state, draft inputs.
91- **Session continuity** — chat sessions persist across restarts.
92
93## Theme Support
94
95Themes: Light, Dark (#2d2d2d / #1a1a1a / #3a3a3a), Crystal Dark (Tailwind gray scale), Auto.
96
97**Critical rules:**
98- Never hardcode colors in CSS files — use CSS variables.
99- `src/renderer/index.css` is the only place theme colors are defined.
100- Apply themes by setting both the `data-theme` attribute and the CSS class on the root element.
101
102Comprehensive: [THEMING.md](./THEMING.md).
103
104## File Operations
105
106- **Drag-and-drop**: move files/folders in the Project Sidebar; hold Option/Alt to copy.
107- **Context menus**: rename, delete, open in new window.
108- **File watching**: auto-update on disk changes.
109
110## AI Providers
111
112Provider implementations live in `packages/runtime` — see `/packages/runtime/CLAUDE.md`. Electron-only pieces:
113
114- **Renderer panels**: `src/renderer/components/AIModels/panels/ClaudePanel.tsx`, `ClaudeCodePanel.tsx`
115- **Claude Code installer**: `src/renderer/components/AIModels/services/CLIInstaller.ts` (manages local installation of `@anthropic-ai/claude-agent-sdk`)
116
117## macOS Code Signing & Notarization
118
119- **Certificate**: Developer ID Application
120- **Builds**: `npm run build:mac:notarized` (notarized), `build:mac:local` (local testing)
121- **Bundled tools**: ripgrep is signed; JAR files are excluded automatically (can't be notarized)
122- **Entitlements**: hardened runtime with necessary exceptions
123
124## Git Worktree Integration
125
126Nimbalyst creates git worktrees for isolated AI coding sessions. See [/docs/WORKTREES.md](/docs/WORKTREES.md). The `worktrees` table stores metadata; `ai_sessions.worktree_id` links sessions to worktrees. IPC channels: `worktree:create`, `worktree:get-status`, `worktree:delete`, `worktree:list`, `worktree:get`.
127
128## Analytics
129
130See [/docs/ANALYTICS_GUIDE.md](/docs/ANALYTICS_GUIDE.md). **When adding, modifying, or removing PostHog events, update [/docs/POSTHOG_EVENTS.md](/docs/POSTHOG_EVENTS.md).**
131
@@ −1 +1 @@
1−# iOS Package (Native iOS App)
1+# Electron Package
22
3−This package contains the native SwiftUI iOS/iPadOS app for Nimbalyst. It provides a mobile interface for viewing and interacting with AI sessions synced from the desktop Electron app via end-to-end encrypted WebSocket sync.
3+The Nimbalyst desktop app, built with Electron.
44
5−The app is **pure Swift/SwiftUI** with no Capacitor or web framework dependency. The only web view is `TranscriptWebView` (WKWebView) which renders the rich chat transcript using the same React components as the desktop app.
5+## Development Commands
66
7−## Package Structure
7+- **Dev server**: `npm run dev` (user runs this — don't do it yourself)
8+- **Dev with restart loop**: `npm run dev:loop` (enables restart button / `/restart` command)
9+- **Build for Mac**: `npm run build:mac:local` or `npm run build:mac:notarized`
10+- **Auth callbacks in dev**: no URL-handler setup is required, including for `npm run dev:user2`. Every sign-in flow uses a nonce-protected one-shot listener on `127.0.0.1` owned by the instance that started it.
11+- **Other deep links in dev (macOS)**: `npm run dev:url-handler` (from the repo root) points non-auth `nimbalyst://` links at this checkout. The applet in `scripts/install-dev-url-handler.sh` is not part of authentication. The dev app deliberately does *not* claim the scheme itself because all development copies share Electron's `com.github.Electron` bundle id. See `src/main/utils/protocolRegistration.ts`.
812
9−```
10−packages/ios/
11− NimbalystNative/ # Swift Package - all business logic and UI
12− Sources/
13− App/ # AppState (root observable), ContentView, navigation
14− Auth/ # AuthManager (Stytch OAuth via ASWebAuthenticationSession)
15− Crypto/ # CryptoManager (AES-256-GCM, PBKDF2), KeychainManager
16− Database/ # DatabaseManager (GRDB migrations, queries)
17− Models/ # GRDB record types: Project, Session, Message, QueuedPrompt, SyncState
18− Notifications/ # NotificationManager (push notification registration)
19− Sync/ # SyncManager, WebSocketClient, SyncProtocol types
20− Utils/ # RelativeTimestamp, NimbalystColors
21− Views/ # All SwiftUI views
22− Tests/ # Unit and integration tests (68 tests)
23− Package.swift # Swift Package Manager manifest (GRDB dependency)
13+### Testing
2414
25− NimbalystApp/ # Xcode app target
26− Sources/ # App entry point (@main), DebugMenu
27− Resources/ # Assets.xcassets (AppIcon, Splash), transcript-dist bundle
28− project.yml # XcodeGen project definition
15+From the repository root:
16+- Run one spec: `npx playwright test e2e/monaco/file-watcher-updates.spec.ts`
17+- Run a directory: `npx playwright test e2e/monaco/`
18+- Run all: `npx playwright test`
2919
30− CryptoCompatibility/ # CommonCrypto bridging header for PBKDF2 key derivation
20+**Always use `npx playwright test` directly.** Never use parallel execution — it corrupts PGLite. See [/docs/E2E_TESTING.md](/docs/E2E_TESTING.md).
3121
32− src/transcript/ # React transcript web bundle (loaded in WKWebView)
33− main.tsx # Entry point with Swift <-> JS bridge
34− styles.css # Styles with bundled Material Symbols font
35− fonts/ # Locally bundled Material Symbols TTF
22+## Architecture
3623
37− vite.config.transcript.ts # Vite config for transcript bundle (IIFE format for file://)
38− transcript.html # HTML entry point for Vite build
39− dist-transcript/ # Build output (not committed)
40−```
24+### Main and Renderer Processes
4125
42−## Key Architecture Decisions
26+Electron apps split into two contexts:
27+- **Main** runs Node.js, manages lifecycle, windows, menus, system interactions.
28+- **Renderer** runs in a Chromium context; UI only.
4329
44−### Authentication Flow
45−1. QR pairing stores encryption seed + server URL in Keychain
46−2. Stytch OAuth stores JWT + user ID in Keychain
47−3. When both paired AND authenticated, managers initialize
48−4. Encryption key derived from seed + user ID via PBKDF2
30+Renderers cannot access Node.js APIs directly — use IPC to request main-process services. For initialization rules (dynamic import in `bootstrap.ts`, lazy init for `app.getPath()` consumers, `safeHandle` / `safeOn`), and cross-platform code patterns, see [MAIN_PROCESS_INIT.md](./MAIN_PROCESS_INIT.md).
4931
50−### Data Flow
51−- **Sync**: WebSocket connection to CollabV3 Durable Object (same server as desktop)
52−- **Encryption**: All session data encrypted with AES-256-GCM before transmission
53−- **Storage**: GRDB (SQLite) with reactive `ValueObservation` for live UI updates
54−- **Transcript**: WKWebView loads bundled React app, communicates via `webkit.messageHandlers.bridge`
32+## IPC Communication
5533
56−### iPad Support
57−- `NavigationSplitView` for regular size class (sidebar + detail)
58−- `NavigationStack` for compact size class (iPhone)
34+### Preload API
35+- **Location**: `src/preload/index.ts`
36+- **Exposed as**: `window.electronAPI` (NOT `window.api`)
37+- **Generic methods**: `invoke`, `send`, `on` (returns an unsubscribe closure — there is no `off`, see [/docs/IPC_LISTENERS.md](/docs/IPC_LISTENERS.md))
38+- Renderer services use these to talk to main-process services.
5939
60−## Development
40+### Document Service
41+- Main: `ElectronDocumentService` (file scanning, metadata extraction, caching)
42+- Renderer: `RendererDocumentService` (facade over IPC)
43+- **Metadata**: frontmatter extraction with bounded reads (4KB)
44+- **Channels**: `document-service:*`
6145
62−### Prerequisites
63−- Xcode 16+
64−- Node.js 20+ (for transcript bundle)
65−- XcodeGen (`brew install xcodegen`)
46+### Common IPC Issues
47+- `window.api undefined` → use `window.electronAPI`
48+- Empty responses → check the window has a valid workspace path
49+- Service resolution is keyed off workspace path
6650
67−### Commands
68−```bash
69−# From monorepo root:
70−npm run ios:test:swift # Run all 68 Swift tests
71−npm run ios:build:transcript # Build transcript web bundle
51+For deep IPC patterns (`safeHandle`/`safeOn`, error handling, channel structure), see [/docs/IPC_GUIDE.md](/docs/IPC_GUIDE.md).
7252
73−# From packages/ios/:
74−cd NimbalystNative && swift test # Run tests directly
75−cd NimbalystApp && xcodegen generate # Regenerate .xcodeproj
76−open NimbalystApp/NimbalystApp.xcodeproj # Open in Xcode
77−```
53+## Data Persistence
7854
79−### Transcript Bundle
80−The Xcode pre-build script in `project.yml` automatically builds the transcript with Vite and copies it to `Resources/transcript-dist/`. You can also build manually:
55+The app runs over **either PGLite (PostgreSQL in WebAssembly) or better-sqlite3** — both backends are active during the in-progress migration. Code must work on either; do not assume one. **Never use `localStorage` in the renderer.** Persist via IPC to main using:
56+- **app-settings store** (`src/main/utils/store.ts`) for global app settings
57+- **workspace-settings store** for per-project state
58+- **AppDatabase** (PGLite or SQLite, selected at init) for complex data (AI sessions, document history, trackers)
8159
82−```bash
83−npm run ios:build:transcript
84−```
60+The biggest divergence to remember: `data->'key'` returns a parsed object on PGLite but a JSON string on SQLite. For tables, locations, shutdown rules, timestamp handling, and the full list of backend-divergent behaviors, see [DATABASE.md](./DATABASE.md).
8561
86−Output: `dist-transcript/transcript.html` + `dist-transcript/assets/` (JS bundle + Material Symbols font).
62+## Renderer State Architecture
8763
88−After building, copy the output to Xcode resources:
89−```bash
90−rm -f NimbalystApp/Resources/transcript-dist/assets/transcript-*.js
91−cp dist-transcript/transcript.html NimbalystApp/Resources/transcript-dist/transcript.html
92−cp dist-transcript/assets/* NimbalystApp/Resources/transcript-dist/assets/
93−```
64+The renderer uses Jotai for state that crosses component boundaries. Editors use **EditorHost** — a stable service object — for all host communication; content state lives in the editor, not parent components.
9465
95−**CRITICAL: React hooks rules in `src/transcript/main.tsx`**
66+| Domain | Atoms | Owner |
67+| --- | --- | --- |
68+| Theme | `themeAtom` | Global, IPC-synced |
69+| Editors | `editorDirtyAtom(key)`, `editorProcessingAtom(key)` | EditorHost writes, Tab reads |
70+| Sessions | `sessionUnreadAtom(id)`, `sessionProcessingAtom(id)` | AgenticPanel writes, UI reads |
71+| File Tree | `gitStatusAtom`, `expandedDirsAtom` | WorkspaceSidebar writes, FileTree reads |
72+| Trackers | `trackerCountsAtom` | TrackerService writes, UI reads |
9673
97−The transcript React app runs inside WKWebView where errors are invisible (cross-origin `window.onerror` reports "Script error." with no details). This makes hooks violations especially dangerous -- the screen goes blank with no diagnostic information.
74+**Re-render isolation**: parents subscribe to lists of IDs; children subscribe to their own atoms. If you need `React.memo` to prevent re-renders, you have the wrong architecture.
9875
99−Rules for editing `TranscriptApp` in `main.tsx`:
100−- **All hooks (`useState`, `useRef`, `useCallback`, `useMemo`, `useEffect`) must come BEFORE any early returns.** React requires the same hooks to run in the same order on every render. An early `return` before a hook means that hook runs on some renders but not others, crashing React with "Rendered more hooks than during the previous render."
101−- **The `TranscriptErrorBoundary` wraps the app** to catch render errors and display them on screen + report to the native bridge. Do not remove it.
102−- **The `postErrorToNative` helper** sends error details through `webkit.messageHandlers.bridge` so they appear in Xcode console logs with full stack traces. Use it in any new try-catch blocks.
103−- **Test after any change**: Always rebuild the transcript (`npm run ios:build:transcript`), copy to Xcode resources, and rebuild in Xcode. Vite build success does NOT mean React will render correctly at runtime.
76+For full patterns, see [/docs/EDITOR_STATE.md](/docs/EDITOR_STATE.md) and [/docs/JOTAI.md](/docs/JOTAI.md).
10477
105−## Key Files
78+## Logging
10679
107−| File | Purpose |
108−|------|---------|
109−| `Sources/App/AppState.swift` | Root observable object; owns database, crypto, and sync managers |
110−| `Sources/Sync/SyncManager.swift` | WebSocket sync with CollabV3; processes index responses and broadcasts |
111−| `Sources/Sync/SyncProtocol.swift` | All wire protocol types (Codable structs with CodingKeys) |
112−| `Sources/Database/DatabaseManager.swift` | GRDB schema migrations, queries, and project stats refresh |
113−| `Sources/Crypto/CryptoManager.swift` | AES-256-GCM encrypt/decrypt, deterministic project ID encryption |
114−| `Sources/Views/TranscriptWebView.swift` | WKWebView + Coordinator with JS bridge, TranscriptController |
115−| `Sources/Views/SessionDetailView.swift` | Session detail with transcript, scroll-to-top, jump-to-prompt |
116−| `Sources/Views/SessionListView.swift` | Time-grouped session list with search and swipe-to-delete |
117−| `Sources/Views/ProjectListView.swift` | Project list sorted by last activity with desktop connection indicator |
118−| `src/transcript/main.tsx` | React transcript app with `scrollToTop`, `scrollToMessage`, `getPromptList` JS bridge |
80+Three log destinations:
11981
120−## Testing
121−- 68 Swift tests covering database, crypto, sync integration, and web view
122−- See [TESTING.md](./TESTING.md) for CI/CD pipeline details
123−- Tests run on both macOS (via Swift Package Manager) and iOS simulator (via Xcode)
82+- **Main process log**: `~/Library/Application Support/@nimbalyst/electron/logs/main.log` — main-process events, AI, sync, file ops; categories like `(MAIN)`, `(AI)`, `(API)`, `(SYNC)`.
83+- **Renderer console log** (dev mode only): `~/Library/Application Support/@nimbalyst/electron/nimbalyst-debug.log` — captured via `webContents.on('console-message')` in `src/main/index.ts`.
84+
85+Use the agent log access tools (`get_main_process_logs`, `get_renderer_debug_logs`) instead of asking users to paste logs. See [/docs/DEBUGGING_LOGS.md](/docs/DEBUGGING_LOGS.md).
86+
87+## Window State Persistence
88+
89+- **Global session state** restores all windows on restart (bounds, focus order, dev tools state).
90+- **Per-project state** restores window configuration, open file, AI panel width and collapsed state, draft inputs.
91+- **Session continuity** — chat sessions persist across restarts.
92+
93+## Theme Support
94+
95+Themes: Light, Dark (#2d2d2d / #1a1a1a / #3a3a3a), Crystal Dark (Tailwind gray scale), Auto.
96+
97+**Critical rules:**
98+- Never hardcode colors in CSS files — use CSS variables.
99+- `src/renderer/index.css` is the only place theme colors are defined.
100+- Apply themes by setting both the `data-theme` attribute and the CSS class on the root element.
101+
102+Comprehensive: [THEMING.md](./THEMING.md).
103+
104+## File Operations
105+
106+- **Drag-and-drop**: move files/folders in the Project Sidebar; hold Option/Alt to copy.
107+- **Context menus**: rename, delete, open in new window.
108+- **File watching**: auto-update on disk changes.
109+
110+## AI Providers
111+
112+Provider implementations live in `packages/runtime` — see `/packages/runtime/CLAUDE.md`. Electron-only pieces:
113+
114+- **Renderer panels**: `src/renderer/components/AIModels/panels/ClaudePanel.tsx`, `ClaudeCodePanel.tsx`
115+- **Claude Code installer**: `src/renderer/components/AIModels/services/CLIInstaller.ts` (manages local installation of `@anthropic-ai/claude-agent-sdk`)
116+
117+## macOS Code Signing & Notarization
118+
119+- **Certificate**: Developer ID Application
120+- **Builds**: `npm run build:mac:notarized` (notarized), `build:mac:local` (local testing)
121+- **Bundled tools**: ripgrep is signed; JAR files are excluded automatically (can't be notarized)
122+- **Entitlements**: hardened runtime with necessary exceptions
123+
124+## Git Worktree Integration
125+
126+Nimbalyst creates git worktrees for isolated AI coding sessions. See [/docs/WORKTREES.md](/docs/WORKTREES.md). The `worktrees` table stores metadata; `ai_sessions.worktree_id` links sessions to worktrees. IPC channels: `worktree:create`, `worktree:get-status`, `worktree:delete`, `worktree:list`, `worktree:get`.
127+
128+## Analytics
129+
130+See [/docs/ANALYTICS_GUIDE.md](/docs/ANALYTICS_GUIDE.md). **When adding, modifying, or removing PostHog events, update [/docs/POSTHOG_EVENTS.md](/docs/POSTHOG_EVENTS.md).**
124131
