RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/nimbalyst-nimbalyst-packages-android-claude ↔ nimbalyst-nimbalyst-claude

Comparison

A · CLAUDE.md · nimbalyst/nimbalystB · CLAUDE.md · nimbalyst/nimbalyst
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections015280%
Commands08160%
Section tags60940%

What each file covers

Sections

0 shared · 15 only in A · 28 only in B
  • − Android Package (Native Android App)
  • − Overview
  • − Package Structure
  • − Key Architecture Rules
  • − Transcript
  • − Sync and Encryption
  • − Persistence
  • − Firebase / Notifications
  • − Development
  • − Prerequisites
  • − Commands
  • − Play Store screenshots and video
  • − Builds, signing, and CI
  • − Agent Guidance
  • − Important Files
  • + CLAUDE.md
  • + Critical Rules (read first)
  • + Keep Commit Messages and CHANGELOG Entries Short
  • + Write and Run Tests for Behavioral Changes
  • + Use @floating-ui/react for All Popover/Tooltip/Menu Positioning
  • + No Dynamic Imports in Electron Main Process
  • + CollabV3 Data Isolation — DOs for Customer Data, D1 for Entity Management Only
  • + Never Use Environment Variables as Implicit API Key Sources
  • + Personal JWT vs Team JWT — Never Interchange Them
  • + Database Access Rules
  • + Always Run Your Own Observation Commands — Don't Push Logs/Curl/Tail to the User
  • + End-to-End Verification Before Declaring Victory
  • + Codebase Overview
  • + Extension Architecture
  • + Monorepo Structure
  • + Development Commands
  • + Releases
  • + Cross-Cutting Patterns
  • + Data Persistence
  • + Transcript Storage
  • + Documentation Reference
  • + AI Features (quick reference)
  • + Tracker Workflows
  • + Architecture Diagrams for Decisions
  • + Verifying Development Mode
  • + Debugging with Log Access Tools
  • + General Development Guidelines
  • + Support

Commands

0 shared · 8 only in A · 16 only in B
  • − npm run android:build:transcript
  • − npm run android:test:unit
  • − npm run android:assemble:debug
  • − npm run android:assemble:release
  • − npm run android:bundle:release
  • − npm run android:screenshots
  • − npm run android:walkthrough
  • − npm run android:bundle:signed
  • + git log --oneline
  • + npm run typecheck && npm run test:prepush
  • + npm install
  • + npm run hooks:install
  • + node -e "const { PGlite } = require(...)"
  • + npm run build:mac:local
  • + npm run build:mac:notarized
  • + npm run test:unit
  • + npm run test:unit:ui
  • + npm run ios:test:swift
  • + npm run ios:build:transcript
  • + npm run collabv2:dev
  • + npm run collabv2:deploy
  • + npm run dev
  • + git reset
  • + git add -A

Section tags

6 shared · 0 only in A · 9 only in B
  • + test
  • + testing-strategy
  • + git-pr
  • + security
  • + database
  • + api
  • + ui
  • + monorepo
  • + docs
  •   setup
  •   build
  •   code-style
  •   architecture
  •   do-not
  •   agent-behaviour

Line diff

+219 added−96 removed36 unchanged14.1% identical
nimbalyst/nimbalyst · packages/android/CLAUDE.md
@@ −1 @@
1# Android Package (Native Android App)
2 
3This package contains the native Android app for Nimbalyst. It mirrors the iOS native app architecture where practical: a pure native mobile shell with a single embedded web transcript view that renders the shared React transcript bundle.
4 
5## Overview
6 
7The Android app is:
8 
9- **Pure native Android** using Kotlin and Jetpack Compose
10- **Room-backed** for local persistence
11- **WebSocket-synced** with CollabV3 Durable Objects
12- **End-to-end encrypted** using the same seed + user-derived key model as iOS
13- **Transcript-rendered** through a single `WebView` that loads the bundled React transcript UI
14 
15Voice agent features are intentionally out of scope for Android.
16 
17## Package Structure
18 
19```text
20packages/android/
21 app/
22 src/main/java/com/nimbalyst/app/
23 attachments/ # Image attachment preparation/compression
24 auth/ # Auth callback parsing
25 crypto/ # AES-GCM + PBKDF2 key derivation
26 data/ # Room entities, DAOs, repository
27 notifications/ # Android notification + FCM token plumbing
28 pairing/ # QR payload parsing and persistent pairing state
29 sync/ # WebSocket sync manager and wire protocol
30 transcript/ # WebView host and JS bridge
31 ui/ # Compose screens and app shell
32 src/test/ # Unit tests
33 src/transcript/ # Shared React transcript bundle entrypoint/assets
34 scripts/ # Transcript asset sync helpers
35```
36 
37## Key Architecture Rules
38 
39### Transcript
40 
41- The transcript UI lives in `src/transcript/main.tsx` and is bundled into Android assets.
42- `TranscriptWebView.kt` is the Android host. `TranscriptBridge.kt` is the only place JS bridge actions should be decoded and routed.
43- Keep transcript behavior aligned with iOS unless Android-specific UX requires a different path.
44 
45### Sync and Encryption
 
 
 
 
 
 
46 
47- `SyncManager.kt` owns the device sync lifecycle, room joins, index updates, queued prompt handling, and session control messages.
48- `CryptoManager.kt` must remain wire-compatible with iOS and desktop. Be cautious with any PBKDF2, AES-GCM, or payload format changes.
49- User routing identity and crypto identity are distinct. Do not collapse them back into a single field.
50 
51### Persistence
52 
53- Room is the source of truth for local Android UI state.
54- Prefer repository/DAO changes over screen-local state duplication.
55- If you add persisted fields, update schema, migrations, and any seed/demo paths together.
56 
57### Firebase / Notifications
58 
59- `app/google-services.json` is local environment config. Do **not** commit it. The `google-services` Gradle plugin is applied conditionally (only when the file exists), so a build without it stays green and push stays inert.
60- Client push registration lives in `notifications/NotificationManager.kt`.
61- Server push delivery lives in the collab server, which is the sibling `nimbalyst-collab` repository, not this monorepo. Clone it next to this repo at `../nimbalyst-collab` (override with `COLLAB_SERVER_PATH`); collab tests are gated by `RUN_COLLAB_TESTS=1`. See `.github/workflows/ci.yml`. Android push changes usually require coordinated client + server work.
62 
63## Development
64 
65### Prerequisites
66 
67- Android Studio Ladybug / AGP-compatible version for this project
68- JDK 17 for Gradle builds. The project targets `JavaVersion.VERSION_17` and `jvmTarget = "17"`, and Temurin 17 matches CI. A non-17 JDK (e.g. GraalVM) can fail the AGP `jlink` step.
69- Android SDK + emulator tooling
70- Node.js 20+ for transcript bundle builds
71 
72### Commands
73 
74From the repository root the npm scripts wrap the Gradle tasks:
 
 
75 
76```bash
77npm run android:build:transcript # build the transcript bundle
78npm run android:test:unit # ./gradlew :app:testDebugUnitTest
79npm run android:assemble:debug # ./gradlew :app:assembleDebug
80npm run android:assemble:release # ./gradlew :app:assembleRelease
81npm run android:bundle:release # ./gradlew :app:bundleRelease
82```
83 
84To invoke Gradle directly, point `JAVA_HOME` at a Temurin 17 install (no hard-coded user path):
85 
86```bash
87cd packages/android
88JAVA_HOME=/path/to/temurin-17 ./gradlew :app:assembleDebug
89JAVA_HOME=/path/to/temurin-17 ./gradlew :app:testDebugUnitTest
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90```
 
 
 
 
 
 
 
 
 
91 
92### Play Store screenshots and video
 
 
93 
94`npm run android:screenshots` and `npm run android:walkthrough` drive an emulator against the debug-only screenshot mode in `app/src/debug/java/com/nimbalyst/app/screenshots/` (inert stub in `app/src/release/`). Never move that code into `src/main` — it seeds demo data and a fake paired state. See [ANDROID_MARKETING_SCREENSHOTS.md](../../docs/ANDROID_MARKETING_SCREENSHOTS.md).
95 
96### Builds, signing, and CI
97 
98- The `google-services` plugin is applied only when `app/google-services.json` is present, so a build without it succeeds and push stays inert until the file is added.
99- CI can inject Firebase config from the optional `ANDROID_GOOGLE_SERVICES_JSON_BASE64` GitHub secret by decoding it to `app/google-services.json` before the Gradle build.
100- The release `signingConfig` reads the keystore path and credentials from environment variables: `NIMBALYST_ANDROID_KEYSTORE`, `NIMBALYST_ANDROID_KEYSTORE_PASSWORD`, `NIMBALYST_ANDROID_KEY_ALIAS`, `NIMBALYST_ANDROID_KEY_PASSWORD`. When the keystore is absent the release build is simply unsigned. Minification stays off (signed is not the same as minified).
101- CI builds both the APK and Play-ready AAB via `.github/workflows/android-build.yml`, which supplies the keystore and signing secrets to produce signed release artifacts when secrets are present. CI also decodes `google-services.json` from the `ANDROID_GOOGLE_SERVICES_JSON_BASE64` secret and fails a signed build if that secret is missing, so a signed AAB never ships with push silently inert.
102- To build a signed release locally, run `npm run android:bundle:signed` (wraps `scripts/android-bundle-signed.sh`). It pulls all signing secrets from the 1Password item `Nimbalyst Android Signing` (Nimbalyst vault) at build time via `op read`: the upload keystore is fetched to a temp file deleted on exit, and passwords/alias are injected into the Gradle env only. Never commit a keystore — `*.jks`/`*.keystore` are gitignored.
103 
104Open `packages/android/` in Android Studio, not the repo root.
 
 
105 
106## Agent Guidance
107 
108- Read the root `CLAUDE.md` before changing this package.
109- Prefer following iOS behavior and naming when implementing cross-platform mobile features.
110- Do not commit secrets or local machine config such as:
111 - `app/google-services.json`
112 - `local.properties`
113 - build outputs
114- If Android Studio reports AGP incompatibility, the correct fix is usually to update Android Studio rather than downgrade AGP/Kotlin.
115- When changing sync protocol behavior, inspect the matching iOS code paths in this repo and the collab server code paths in the sibling `nimbalyst-collab` repository before editing.
116- When changing transcript bridge behavior, update or add Android tests in `app/src/test/` where possible.
117 
118## Important Files
119 
120| File | Purpose |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121| --- | --- |
122| `app/src/main/java/com/nimbalyst/app/NimbalystApplication.kt` | App-level dependency setup and startup wiring |
123| `app/src/main/java/com/nimbalyst/app/MainActivity.kt` | Activity entry point and deep-link handling |
124| `app/src/main/java/com/nimbalyst/app/ui/NimbalystAndroidApp.kt` | Root Compose app shell and navigation |
125| `app/src/main/java/com/nimbalyst/app/sync/SyncManager.kt` | Core mobile sync lifecycle and message handling |
126| `app/src/main/java/com/nimbalyst/app/sync/SyncProtocol.kt` | Android wire protocol types |
127| `app/src/main/java/com/nimbalyst/app/crypto/CryptoManager.kt` | Encryption and key derivation |
128| `app/src/main/java/com/nimbalyst/app/data/NimbalystDatabase.kt` | Room database definition |
129| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptWebView.kt` | WebView transcript host |
130| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptBridge.kt` | JS/native bridge handler |
131| `src/transcript/main.tsx` | Shared transcript app entry point for Android |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132 
nimbalyst/nimbalyst · CLAUDE.md
@@ +1 @@
1# CLAUDE.md
2 
3Guidance for Claude Code (claude.ai/code) when working in this repository.
4 
5## Critical Rules (read first)
6 
7### Keep Commit Messages and CHANGELOG Entries Short
8 
9**One-sentence commit subject. One-sentence CHANGELOG bullet.** Commit bodies may include short bullets for distinct key changes — one line each, no prose paragraphs, no root-cause explanations unless the diff truly can't explain itself. Match the existing voice in `[Unreleased]` and recent `git log --oneline`. If your draft is longer than the surrounding entries, cut it before submitting.
 
 
 
 
10 
11**One feature = one CHANGELOG bullet, no matter how many commits built it.** A multi-commit feature (e.g. a whole panel landed over a dozen PRs) gets a single user-facing line, not one bullet per commit. Do NOT append a new bullet for every follow-up commit to the same feature — edit the existing bullet instead. The `[Unreleased]` section must read like a short release summary, not a commit log.
12 
13**Never put internal scaffolding in the CHANGELOG.** No table/column names, IPC channel names, service/store class names, env-var plumbing, migration registration, poll intervals, or "internal scaffolding for the upcoming X" bullets. The changelog answers "what can I now do / what's fixed" for a user — if a line names a symbol or a file, it's wrong. Internal-only changes (typecheck, tests, refactors, doc/agent tweaks, lint, dep bumps with no behavior change) get NO entry.
14 
15**At release time, condense — don't ship the dev-time bullets verbatim.** `[Unreleased]` accumulates verbose per-commit bullets during development. Before tagging, collapse them: merge a feature's scattered bullets into one line, drop scaffolding, squash near-duplicates. If the release notes are longer than the equivalent section in a recent shipped version, cut harder.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16 
17### Write and Run Tests for Behavioral Changes
18 
19**Any change to runtime behavior ships with a unit test** — a new test, or an extension of an existing one. Pure refactors already covered by tests, formatting, docs, and config-only changes are exempt. Before pushing, run the gate locally: `npm run typecheck && npm run test:prepush`. The repo's pre-push hook runs this automatically; it installs on `npm install` (or `npm run hooks:install`). Never push to `main` with a red suite — CI on `main` is a backstop, not the gate. For high-risk areas (sync/collab, main-process init, IPC, restart-to-verify bugs) the test comes **first** and must fail before the fix — see [end-to-end-verification.md](./.claude/rules/end-to-end-verification.md).
20 
21**A test's job is to catch a regression a reader cannot see.** The test corpus is ~2M tokens, and every future session pays to read the tests next to the code it touches. A test that only re-states what is obvious on screen is pure cost. Write fewer, denser tests:
 
 
22 
23- **No presentation-only tests** — icon names, exact title strings, tab ordering, hardcoded element counts. If a human would notice the breakage in one second of looking at the screen, it does not need a unit test. Purely visual changes (label, spacing, color) need no test at all.
24- **Never assert on component source text** via `readFileSync` + `toContain`/`toMatch`. Render it or don't test it. Genuine architectural invariants belong in a `scripts/` gate, not the vitest suite.
25- **Never assert CSS through jsdom by injecting the CSS you are about to assert on** — that is circular. Assert the `className`, or cover it in E2E where real styles load.
26- **Mock the narrowest module, never the `@nimbalyst/runtime` barrel.** Importing that barrel costs ~2.6s of module-import CPU per test file because it drags in the whole Lexical editor tree. `vi.mock('@nimbalyst/runtime/ui/icons/MaterialSymbol', …)` is cheap; `vi.mock('@nimbalyst/runtime', async (importOriginal) => ({ ...await importOriginal(), … }))` is the expensive shape — the spread forces the real barrel to load. Import from the deep path in source too, so the barrel never enters the graph. See NIM-2374.
27- **Prefer extending an existing test file** over creating a new one — but do not merge unrelated tests into a mega-file. Small and focused is correct; total volume is the enemy, not file count.
28- **Add `// @vitest-environment node` as the first line of any test that never touches the DOM.** The jsdom environment costs ~270ms per file for nothing.
29- **Don't write `expect(getBy*(...)).toBeTruthy()`** — `getBy*` already throws.
30 
31### Use @floating-ui/react for All Popover/Tooltip/Menu Positioning
 
 
32 
33See [floating-ui.md](./.claude/rules/floating-ui.md). Never manually calculate `position: fixed` coordinates — always use `@floating-ui/react` with `FloatingPortal`.
34 
35### No Dynamic Imports in Electron Main Process
 
 
36 
37**NEVER convert static imports to dynamic `await import()`** unless absolutely necessary (confirmed circular reference) AND the user has approved it. Dynamic imports cause `__ELECTRON_LOG__` double-registration crashes and side-effect timing issues. All MCP servers and services in `index.ts` use static top-level imports. The only allowed exception is `bootstrap.ts` importing `index.ts` (see [MAIN_PROCESS_INIT.md](./packages/electron/MAIN_PROCESS_INIT.md)).
38 
39### CollabV3 Data Isolation — DOs for Customer Data, D1 for Entity Management Only
 
 
40 
41**Never store customer, org, or team-sensitive data in the D1 shared database.** Customer data (team metadata, member roles, key envelopes, tracker items, documents, sessions) must live in Durable Objects where each entity gets its own isolated SQLite instance. D1 is only for cross-entity management lookups (e.g., git remote hash → org ID mapping). See `packages/collabv3/CLAUDE.md` for the full policy.
42 
43### Never Use Environment Variables as Implicit API Key Sources
44 
45**NEVER read API keys from `process.env` as a fallback for provider authentication.** API keys must come only from values the user explicitly configured in Nimbalyst settings (the electron-store `apiKeys` object or project-level overrides).
 
 
 
46 
47Past incident: a user had `ANTHROPIC_API_KEY` in a `.env` file for unrelated work. Nimbalyst silently picked it up via `process.env`, auto-persisted it, and billed the user's personal Anthropic account $100+ instead of their Nimbalyst subscription.
48 
49- No env fallbacks in `getApiKeyForProvider` — only `globalApiKeys[provider]` or project-level overrides
50- No auto-import into the settings store
51- Provider availability checks must only consider explicitly-stored keys
52 
53If you are tempted to add `|| process.env.SOME_API_KEY` as a convenience fallback, **stop**.
 
 
 
 
 
 
54 
55### Personal JWT vs Team JWT — Never Interchange Them
56 
57Stytch B2B gives a user a **different member id per org**. The **personal JWT** (`getPersonalSessionJwt()` / `personalUserId`) is for **personal sync ONLY** (the personal index room + session/prompt/draft/settings sync to the **mobile app**). The **team JWT** (`getSessionJwt()` / `getOrgScopedJwt(orgId)`) authorizes **ALL team collaboration** (tracker rooms, schema sync, document rooms, team room, project-access gate). Conflating them is this codebase's most-repeated sync bug.
58 
59Use the branded types in `packages/runtime/src/auth/jwtScopes.ts` so a mix-up is a compile error. When "a second client can't see shared data", first check it's actually authenticated (an expired session is silently logged out → no team JWT).
60 
61### Database Access Rules
62 
63**Nimbalyst currently supports BOTH PGLite and better-sqlite3.** The migration is in progress; both backends are active in the codebase and the user's machine may be running either. Never write code that assumes one backend. Anywhere you touch the database — schema, queries, JSON handling, write paths — read [packages/electron/DATABASE.md](./packages/electron/DATABASE.md) for the divergent behaviors first.
64 
65The biggest gotcha: **JSONB sub-extraction (`data->'someKey'`) returns a parsed object on PGLite but a JSON string on SQLite.** Either select the whole `data` column and parse it, or defensively parse the sub-extracted value with the standard `typeof x === 'string' ? JSON.parse(x) : x` idiom. A real bug from this divergence corrupted tracker `labelsMap` rows on 2026-06-02 because `applyRemoteItem` trusted the sub-extraction was already an object.
66 
67**NEVER directly open or query the database files using Node.js or command-line tools.** PGLite at `~/Library/Application Support/@nimbalyst/electron/pglite-db` uses PID-based locking; better-sqlite3 takes its own exclusive lock. In both cases, opening from a second process risks corruption.
68 
69**ALWAYS use the MCP database query tool instead:**
70- Use `mcp__nimbalyst-extension-dev__database_query` for all database queries
71- NEVER use `node -e "const { PGlite } = require(...)"` or sqlite CLI
72 
73**For sync/collab bugs, local PGLite ≠ server collab state.** `tracker_body_cache`, `documents`, and other sync-related tables only reflect the local side. The authoritative state for shared trackers/documents lives in Cloudflare Workers (`packages/collabv3/` DurableObjects) and must be inspected separately via `wrangler tail` against the prod sync worker, or via wrangler-backed E2E tests (`tracker-content-collab.spec.ts` / `tracker-sync-collab.spec.ts` patterns, `RUN_COLLAB_TESTS=1`, `document-sync:open-test` IPC for Stytch bypass). Confirming "the body is in PGLite" is not the same as confirming "the body is on the server." See `feedback_local_state_vs_server_state.md`.
74 
75### Always Run Your Own Observation Commands — Don't Push Logs/Curl/Tail to the User
76 
77**Never ask the user to run `curl`, `wrangler tail`, `tail -f`, `gh` commands, or paste logs.** The agent has direct tool access to all of these.
78 
79- Logs: `mcp__nimbalyst-extension-dev__get_main_process_logs` and `get_renderer_debug_logs`
80- Database: `mcp__nimbalyst-extension-dev__database_query`
81- HTTP: `Bash` with `curl` — the agent runs it
82- Cloudflare workers: `Bash` with `wrangler tail` — the agent runs it (long-running tails can use `run_in_background`)
83- GitHub: `Bash` with `gh`
84- Runtime DOM / renderer state: `mcp__nimbalyst-extension-dev__renderer_eval`
85 
86Past incident: session `702519e3` spent 23 turns debugging a Stytch JWKS rotation because the agent kept handing back commands for the user to run. The user finally said "run tail yourself!" and "you curl it!". If you catch yourself drafting "could you run X and paste the output?", stop and run X.
87 
88Detailed patterns: [DEBUGGING_LOGS.md](./docs/DEBUGGING_LOGS.md).
89 
90### End-to-End Verification Before Declaring Victory
91 
92For any bug whose verification requires a `/restart` or a user manually exercising a UI flow, the **first** deliverable is a failing test that the fix must make pass. Never announce "fixed" before observing the bug go from broken to working — either via a test that flips red→green, or via logs showing the failing step now succeeding. See [end-to-end-verification.md](./.claude/rules/end-to-end-verification.md).
93 
94Past incident: the 2026-05-20 tracker-body workstream announced "fixed" at least four times before the user finally said "you're killing me." Each announcement was based on "the code path looks right" or "tests pass," neither of which is the same as "the user can open the tracker and see the body."
95 
96## Codebase Overview
97 
98Nimbalyst is an extensible, AI-native workspace that supports multiple editor types through a unified extension system. While it originated as a Lexical-based markdown editor, the architecture is evolving toward a fully pluggable model where **all editors** — Lexical, Monaco, spreadsheets, diagrams, custom visual editors — are provided through extensions.
99 
100This monorepo contains the Electron desktop app, runtime services (including the Lexical editor), extension SDK, native iOS app, and mobile support via Capacitor.
101 
102## Extension Architecture
103 
104See [EXTENSION_ARCHITECTURE.md](./docs/EXTENSION_ARCHITECTURE.md) for the EditorHost contract, supported editor types (Monaco, Lexical, custom React), the manifest format, and extension development guidelines.
105 
106## Monorepo Structure
107 
108```
109packages/
110 electron/ # Desktop app (Electron)
111 runtime/ # Cross-platform runtime services (AI, sync, Lexical editor)
112 ios/ # Native iOS app (SwiftUI)
113 core/ # Shared utilities
114 collabv3/ # Collaboration server (Cloudflare Workers)
115 extension-sdk/ # Extension development kit
116 extensions/ # Built-in extensions
117```
118 
119- **Install**: `npm install` at repository root
120- **npm workspaces** (not pnpm); packages reference each other via workspace protocol
121- **Preserve `peer: true` flags in package-lock.json** — Some `npm install` configurations strip these flags, breaking CI for optional native dependencies (e.g., esbuild platform binaries). Investigate before committing if you see them disappearing.
122 
123Package-specific docs: `/packages/electron/CLAUDE.md`, `/packages/runtime/CLAUDE.md`, `/packages/ios/CLAUDE.md`, `/packages/collabv3/CLAUDE.md`.
124 
125## Development Commands
126 
127**Electron app:**
128- Start dev: `cd packages/electron && npm run dev` (user runs this — don't do it yourself)
129- Build for Mac: `npm run build:mac:local` or `npm run build:mac:notarized`
130- Main process log: `~/Library/Application Support/@nimbalyst/electron/logs/main.log`
 
131 
132**Testing:**
133- Unit: `npm run test:unit` (vitest), or `npm run test:unit:ui`
134- E2E: see [E2E_TESTING.md](./docs/E2E_TESTING.md)
135 
136**Marketing screenshots & videos:** See [MARKETING_SCREENSHOTS.md](./docs/MARKETING_SCREENSHOTS.md). Quick: `cd packages/electron && npm run marketing:screenshots` (requires dev server on port 5273).
137 
138**Multiple dev instances** (for collab/sync testing): `cd packages/electron && npm run dev:user2` uses an isolated `NIMBALYST_USER_DATA_DIR`, `VITE_PORT=5274`, and `--outDir=out2` to prevent file-watcher cross-talk. Worktrees auto-derive a per-worktree userData dir via `crystal-run.sh`.
 
 
 
 
 
 
 
 
139 
140**Other packages:** iOS — `npm run ios:test:swift`, `npm run ios:build:transcript`. Collab server — `npm run collabv2:dev`, `npm run collabv2:deploy`.
141 
142## Releases
143 
144See [RELEASING.md](./RELEASING.md). Use `/release-alpha [patch|minor|major]`. All release notes go in the `[Unreleased]` section of `CHANGELOG.md`; the script creates versioned entries and annotated git tags.
145 
146## Cross-Cutting Patterns
147 
148- **Error handling** — fail fast, validate at boundaries, workspace-scoped IPC takes `workspacePath` explicitly. See [ERROR_HANDLING.md](./docs/ERROR_HANDLING.md).
149- **Naming conventions** — `camelCase` for wire protocol/JSON; `snake_case` only for SQL columns. See [NAMING_CONVENTIONS.md](./docs/NAMING_CONVENTIONS.md).
150- **React DOM markers** — Tailwind utilities don't replace semantic class names. Every meaningful component needs a stable kebab-case class on its root. See [REACT_DOM_MARKERS.md](./docs/REACT_DOM_MARKERS.md).
151 
152## Data Persistence
153 
154The app uses **PGLite** (PostgreSQL in WebAssembly) for all data storage.
155 
156- **Never use `localStorage` in the renderer.** Use app-settings store (global), workspace-settings store (per-project), or PGLite (complex data like AI sessions/document history).
157- **All database timestamps must use `TIMESTAMPTZ`.** Never create `TIMESTAMP` (without timezone) columns; migrate legacy tables.
158 
159See [DATABASE.md](./packages/electron/DATABASE.md) for tables, locations, shutdown rules, and timestamp handling.
160 
161## Transcript Storage
162 
163Two-tier architecture — `ai_agent_messages` (raw append-only log, sole source of truth) → `ai_transcript_events` (canonical, provider-agnostic, derived). The `TranscriptTransformer` is the single writer of canonical events; providers only write raw. See [TRANSCRIPT_ARCHITECTURE.md](./docs/TRANSCRIPT_ARCHITECTURE.md).
164 
165## Documentation Reference
166 
167**Read the relevant doc in its entirety before making changes in that area.** These contain authoritative patterns, anti-patterns, and architectural decisions.
168 
169| File | Read when… |
170| --- | --- |
171| [EXTENSION_ARCHITECTURE.md](./docs/EXTENSION_ARCHITECTURE.md) | Working on extensions, creating editors, modifying editor↔host communication, adding new editor types. |
172| [IPC_LISTENERS.md](./docs/IPC_LISTENERS.md) | Adding IPC events, debugging stale closures / race conditions in event handling, or seeing `MaxListenersExceededWarning`. |
173| [IPC_GUIDE.md](./docs/IPC_GUIDE.md) | Writing main-process IPC handlers, adding `electronAPI` methods, or debugging main↔renderer IPC. |
174| [EDITOR_STATE.md](./docs/EDITOR_STATE.md) | Working on editor components or TabEditor infrastructure; debugging editor state. |
175| [JOTAI.md](./docs/JOTAI.md) | Working with Jotai atoms, debugging UI/state divergence, or adding new atoms. |
176| [STATE_PERSISTENCE.md](./docs/STATE_PERSISTENCE.md) | Adding fields to any persisted state, or debugging "Cannot read properties of undefined" on app load. |
177| [UI_PATTERNS.md](./docs/UI_PATTERNS.md) | Writing UI components, styling with CSS/Tailwind, or adding responsive behavior. |
178| [ERROR_HANDLING.md](./docs/ERROR_HANDLING.md) | Writing IPC handlers or service methods that take required parameters or handle workspace state. |
179| [NAMING_CONVENTIONS.md](./docs/NAMING_CONVENTIONS.md) | Designing wire protocols, sync code, or SQL schemas. |
180| [AI_PROVIDER_TYPES.md](./docs/AI_PROVIDER_TYPES.md) | Working on AI integration, adding providers, or modifying model selection. |
181| [TRANSCRIPT_ARCHITECTURE.md](./docs/TRANSCRIPT_ARCHITECTURE.md) | Working on transcript rendering, parsers, the canonical event pipeline, or mobile transcript sync. |
182| [CONTEXT_WINDOW_USAGE_TRACKING.md](./docs/CONTEXT_WINDOW_USAGE_TRACKING.md) | Working on context-usage display, token tracking, or `ClaudeCodeProvider` streaming. |
183| [INTERNAL_MCP_SERVERS.md](./docs/INTERNAL_MCP_SERVERS.md) | Adding MCP server functionality or new tools for AI agents. |
184| [CUSTOM_TOOL_WIDGETS.md](./docs/CUSTOM_TOOL_WIDGETS.md) | Creating visual displays for MCP tool results or customizing tool rendering. |
185| [INTERACTIVE_PROMPTS.md](./docs/INTERACTIVE_PROMPTS.md) | Working on durable prompts (AskUserQuestion, ExitPlanMode, GitCommitProposal, ToolPermission). |
186| [WORKTREES.md](./docs/WORKTREES.md) | Working on worktree features, session isolation, or session↔worktree linkage. |
187| [SESSION_HIERARCHY.md](./docs/SESSION_HIERARCHY.md) | Creating/parenting sessions or debugging session grouping in the left pane. |
188| [HELP_WALKTHROUGHS.md](./docs/HELP_WALKTHROUGHS.md) | Adding help tooltips, creating walkthroughs, or modifying help content. |
189| [REACT_DOM_MARKERS.md](./docs/REACT_DOM_MARKERS.md) | Working on React UI, adding components, or improving testability/devtools navigation. |
190| [WALKTHROUGHS.md](./docs/WALKTHROUGHS.md) | Creating multi-step walkthroughs or debugging walkthrough flow. |
191| [E2E_TESTING.md](./docs/E2E_TESTING.md) | Writing/debugging E2E tests, or running them as an AI agent (especially in worktrees). |
192| [DIALOGS.md](./docs/DIALOGS.md) | Adding or modifying modal dialogs. |
193| [AGENT_PERMISSIONS.md](./docs/AGENT_PERMISSIONS.md) | Working on agent permissions, approval flows, or runtime permission checks. |
194| [ANALYTICS_GUIDE.md](./docs/ANALYTICS_GUIDE.md) | Adding/modifying PostHog events, or using PostHog MCP tools. |
195| [POSTHOG_EVENTS.md](./docs/POSTHOG_EVENTS.md) | Adding, modifying, or removing any PostHog analytics event — keep this in sync. |
196| [POSTHOG_MCP_INTEGRATION.md](./docs/POSTHOG_MCP_INTEGRATION.md) | Using PostHog MCP tools or extending PostHog functionality. |
197| [THEMING.md](./packages/electron/THEMING.md) | Working on themes or color schemes. |
198| [RELEASING.md](./RELEASING.md) | Preparing a release or debugging release scripts. |
199| [MARKETING_SCREENSHOTS.md](./docs/MARKETING_SCREENSHOTS.md) | Adding marketing screenshots/videos or modifying capture choreography. |
200| [ANDROID_MARKETING_SCREENSHOTS.md](./docs/ANDROID_MARKETING_SCREENSHOTS.md) | Capturing Play Store screenshots or the reviewer screencast for the Android app. |
201| [FILE_WATCHING_AND_CHANGE_TRACKING.md](./docs/FILE_WATCHING_AND_CHANGE_TRACKING.md) | Working on file watchers, AI change detection, diff display, or the FilesEditedSidebar. |
202| [WEEKLY_DASHBOARD.md](./docs/WEEKLY_DASHBOARD.md) | Adding/modifying insights on the Weeklys PostHog dashboard. |
203| [VOICE_MODE.md](./docs/VOICE_MODE.md) | Working on voice mode, voice-agent prompts, audio pipeline, or session lifecycle. |
204| [TRACKER_WORKFLOWS.md](./docs/TRACKER_WORKFLOWS.md) | Creating decision or bug tracker items as part of a fix or design decision. |
205| [ARCHITECTURE_DIAGRAMS.md](./docs/ARCHITECTURE_DIAGRAMS.md) | Making any architectural decision — create an Excalidraw diagram. |
206| [DEBUGGING_LOGS.md](./docs/DEBUGGING_LOGS.md) | Investigating bugs — use the log access tools, don't ask the user to paste logs. |
207| [MAIN_PROCESS_INIT.md](./packages/electron/MAIN_PROCESS_INIT.md) | Working on Electron main-process bootstrap, singleton init, or IPC handler registration. |
208| [DATABASE.md](./packages/electron/DATABASE.md) | Working with PGLite tables, shutdown, or timestamp handling. |
209 
210## AI Features (quick reference)
211 
212- **AI Chat Panel**: multi-provider (Claude, OpenAI, LM Studio, Claude Code), document-aware; Cmd+Shift+A
213- **Session Manager**: global view (Cmd+Alt+S); search, export, delete
214- **Model Configuration**: dynamic from provider APIs; no hardcoded models
215- **Git Worktrees**: isolated AI coding sessions via "New Worktree" button
216 
217## Tracker Workflows
218 
219When choosing between alternatives (libraries, patterns, deciding NOT to do something), log a **decision** tracker item. When fixing a bug, ensure a **bug** tracker item exists before writing fix code. See [TRACKER_WORKFLOWS.md](./docs/TRACKER_WORKFLOWS.md) for the exact `tracker_create` calls and lifecycle.
220 
221## Architecture Diagrams for Decisions
222 
223Whenever an architectural change is proposed, create an Excalidraw diagram in `nimbalyst-local/architecture/` and share the diagram file/link in the conversation. Use `capture_editor_screenshot` only when visual verification is needed or the user explicitly asks for an inline image. See [ARCHITECTURE_DIAGRAMS.md](./docs/ARCHITECTURE_DIAGRAMS.md).
224 
225## Verifying Development Mode
226 
227Before making code changes, use `mcp__nimbalyst-extension-dev__get_environment_info` to verify Nimbalyst is running in dev mode. If the user is running a packaged build, code changes won't take effect — tell them to start the dev server.
228 
229## Debugging with Log Access Tools
230 
231See the Critical Rules block above ("Always Run Your Own Observation Commands"). Detailed patterns: [DEBUGGING_LOGS.md](./docs/DEBUGGING_LOGS.md).
232 
233## General Development Guidelines
234 
235- **Never use emojis** — not in commits, code, or documentation, unless explicitly requested
236- **Never use overly enthusiastic phrases** ("Perfect!", "Terrific!", etc.)
237- **Never commit changes unless explicitly asked**
238- **Never commit files under `nimbalyst-local/`** — gitignored, local-only working files
239- **Never provide time or effort estimates**
240- **Don't disable tests without asking first**
241- **Don't run `npm run dev` yourself** — user does that
242- **Never release without being explicitly instructed**
243- **Don't `git reset` or `git add -A` without asking**
244- **Don't add `Co-Authored-By` lines to commit messages**
245- **Never restart Nimbalyst without explicit permission** — always ask before `restart_nimbalyst`
246- **Never mark work done before the user approves it — but a commit IS their approval.** Until the work is committed, set tracker items to `in-review` and session phase to `validating`, never `done` / `complete`. Once the user commits the work, they have reviewed it and agreed it's finished: put a closing reference (`Fixes NIM-123`) in the commit message and the item closes itself; also set session phase to `complete`. Do not leave finished, committed work parked in `in-review`. `approved` on the review lane remains human-only.
247 
248**Keyboard Shortcuts**: when adding or modifying shortcuts, update `KeyboardShortcutsDialog.tsx`.
249 
250## Support
251 
252User support docs live in `support/`. Notable: `force-restore-database-backup.md` for manual database restore.
253if i ask you to propose a commit, first update the `CHANGELOG.md` (at the repo root) and include it in the commit proposal
254if a commit is intended to fix a github issue, include the issue number and a closing reference in the commit message when appropriate (fixes #123 or closes #123)
255 
@@ −1 +1 @@
1−# Android Package (Native Android App)
1+# CLAUDE.md
22  
3−This package contains the native Android app for Nimbalyst. It mirrors the iOS native app architecture where practical: a pure native mobile shell with a single embedded web transcript view that renders the shared React transcript bundle.
3+Guidance for Claude Code (claude.ai/code) when working in this repository.
44  
5−## Overview
5+## Critical Rules (read first)
66  
7−The Android app is:
7+### Keep Commit Messages and CHANGELOG Entries Short
88  
9−- **Pure native Android** using Kotlin and Jetpack Compose
10−- **Room-backed** for local persistence
11−- **WebSocket-synced** with CollabV3 Durable Objects
12−- **End-to-end encrypted** using the same seed + user-derived key model as iOS
13−- **Transcript-rendered** through a single `WebView` that loads the bundled React transcript UI
9+**One-sentence commit subject. One-sentence CHANGELOG bullet.** Commit bodies may include short bullets for distinct key changes — one line each, no prose paragraphs, no root-cause explanations unless the diff truly can't explain itself. Match the existing voice in `[Unreleased]` and recent `git log --oneline`. If your draft is longer than the surrounding entries, cut it before submitting.
1410  
15−Voice agent features are intentionally out of scope for Android.
11+**One feature = one CHANGELOG bullet, no matter how many commits built it.** A multi-commit feature (e.g. a whole panel landed over a dozen PRs) gets a single user-facing line, not one bullet per commit. Do NOT append a new bullet for every follow-up commit to the same feature — edit the existing bullet instead. The `[Unreleased]` section must read like a short release summary, not a commit log.
1612  
17−## Package Structure
13+**Never put internal scaffolding in the CHANGELOG.** No table/column names, IPC channel names, service/store class names, env-var plumbing, migration registration, poll intervals, or "internal scaffolding for the upcoming X" bullets. The changelog answers "what can I now do / what's fixed" for a user — if a line names a symbol or a file, it's wrong. Internal-only changes (typecheck, tests, refactors, doc/agent tweaks, lint, dep bumps with no behavior change) get NO entry.
1814  
19−```text
20−packages/android/
21− app/
22− src/main/java/com/nimbalyst/app/
23− attachments/ # Image attachment preparation/compression
24− auth/ # Auth callback parsing
25− crypto/ # AES-GCM + PBKDF2 key derivation
26− data/ # Room entities, DAOs, repository
27− notifications/ # Android notification + FCM token plumbing
28− pairing/ # QR payload parsing and persistent pairing state
29− sync/ # WebSocket sync manager and wire protocol
30− transcript/ # WebView host and JS bridge
31− ui/ # Compose screens and app shell
32− src/test/ # Unit tests
33− src/transcript/ # Shared React transcript bundle entrypoint/assets
34− scripts/ # Transcript asset sync helpers
35−```
15+**At release time, condense — don't ship the dev-time bullets verbatim.** `[Unreleased]` accumulates verbose per-commit bullets during development. Before tagging, collapse them: merge a feature's scattered bullets into one line, drop scaffolding, squash near-duplicates. If the release notes are longer than the equivalent section in a recent shipped version, cut harder.
3616  
37−## Key Architecture Rules
17+### Write and Run Tests for Behavioral Changes
3818  
39−### Transcript
19+**Any change to runtime behavior ships with a unit test** — a new test, or an extension of an existing one. Pure refactors already covered by tests, formatting, docs, and config-only changes are exempt. Before pushing, run the gate locally: `npm run typecheck && npm run test:prepush`. The repo's pre-push hook runs this automatically; it installs on `npm install` (or `npm run hooks:install`). Never push to `main` with a red suite — CI on `main` is a backstop, not the gate. For high-risk areas (sync/collab, main-process init, IPC, restart-to-verify bugs) the test comes **first** and must fail before the fix — see [end-to-end-verification.md](./.claude/rules/end-to-end-verification.md).
4020  
41−- The transcript UI lives in `src/transcript/main.tsx` and is bundled into Android assets.
42−- `TranscriptWebView.kt` is the Android host. `TranscriptBridge.kt` is the only place JS bridge actions should be decoded and routed.
43−- Keep transcript behavior aligned with iOS unless Android-specific UX requires a different path.
21+**A test's job is to catch a regression a reader cannot see.** The test corpus is ~2M tokens, and every future session pays to read the tests next to the code it touches. A test that only re-states what is obvious on screen is pure cost. Write fewer, denser tests:
4422  
45−### Sync and Encryption
23+- **No presentation-only tests** — icon names, exact title strings, tab ordering, hardcoded element counts. If a human would notice the breakage in one second of looking at the screen, it does not need a unit test. Purely visual changes (label, spacing, color) need no test at all.
24+- **Never assert on component source text** via `readFileSync` + `toContain`/`toMatch`. Render it or don't test it. Genuine architectural invariants belong in a `scripts/` gate, not the vitest suite.
25+- **Never assert CSS through jsdom by injecting the CSS you are about to assert on** — that is circular. Assert the `className`, or cover it in E2E where real styles load.
26+- **Mock the narrowest module, never the `@nimbalyst/runtime` barrel.** Importing that barrel costs ~2.6s of module-import CPU per test file because it drags in the whole Lexical editor tree. `vi.mock('@nimbalyst/runtime/ui/icons/MaterialSymbol', …)` is cheap; `vi.mock('@nimbalyst/runtime', async (importOriginal) => ({ ...await importOriginal(), … }))` is the expensive shape — the spread forces the real barrel to load. Import from the deep path in source too, so the barrel never enters the graph. See NIM-2374.
27+- **Prefer extending an existing test file** over creating a new one — but do not merge unrelated tests into a mega-file. Small and focused is correct; total volume is the enemy, not file count.
28+- **Add `// @vitest-environment node` as the first line of any test that never touches the DOM.** The jsdom environment costs ~270ms per file for nothing.
29+- **Don't write `expect(getBy*(...)).toBeTruthy()`** — `getBy*` already throws.
4630  
47−- `SyncManager.kt` owns the device sync lifecycle, room joins, index updates, queued prompt handling, and session control messages.
48−- `CryptoManager.kt` must remain wire-compatible with iOS and desktop. Be cautious with any PBKDF2, AES-GCM, or payload format changes.
49−- User routing identity and crypto identity are distinct. Do not collapse them back into a single field.
31+### Use @floating-ui/react for All Popover/Tooltip/Menu Positioning
5032  
51−### Persistence
33+See [floating-ui.md](./.claude/rules/floating-ui.md). Never manually calculate `position: fixed` coordinates — always use `@floating-ui/react` with `FloatingPortal`.
5234  
53−- Room is the source of truth for local Android UI state.
54−- Prefer repository/DAO changes over screen-local state duplication.
55−- If you add persisted fields, update schema, migrations, and any seed/demo paths together.
35+### No Dynamic Imports in Electron Main Process
5636  
57−### Firebase / Notifications
37+**NEVER convert static imports to dynamic `await import()`** unless absolutely necessary (confirmed circular reference) AND the user has approved it. Dynamic imports cause `__ELECTRON_LOG__` double-registration crashes and side-effect timing issues. All MCP servers and services in `index.ts` use static top-level imports. The only allowed exception is `bootstrap.ts` importing `index.ts` (see [MAIN_PROCESS_INIT.md](./packages/electron/MAIN_PROCESS_INIT.md)).
5838  
59−- `app/google-services.json` is local environment config. Do **not** commit it. The `google-services` Gradle plugin is applied conditionally (only when the file exists), so a build without it stays green and push stays inert.
60−- Client push registration lives in `notifications/NotificationManager.kt`.
61−- Server push delivery lives in the collab server, which is the sibling `nimbalyst-collab` repository, not this monorepo. Clone it next to this repo at `../nimbalyst-collab` (override with `COLLAB_SERVER_PATH`); collab tests are gated by `RUN_COLLAB_TESTS=1`. See `.github/workflows/ci.yml`. Android push changes usually require coordinated client + server work.
39+### CollabV3 Data Isolation — DOs for Customer Data, D1 for Entity Management Only
6240  
63−## Development
41+**Never store customer, org, or team-sensitive data in the D1 shared database.** Customer data (team metadata, member roles, key envelopes, tracker items, documents, sessions) must live in Durable Objects where each entity gets its own isolated SQLite instance. D1 is only for cross-entity management lookups (e.g., git remote hash → org ID mapping). See `packages/collabv3/CLAUDE.md` for the full policy.
6442  
65−### Prerequisites
43+### Never Use Environment Variables as Implicit API Key Sources
6644  
67−- Android Studio Ladybug / AGP-compatible version for this project
68−- JDK 17 for Gradle builds. The project targets `JavaVersion.VERSION_17` and `jvmTarget = "17"`, and Temurin 17 matches CI. A non-17 JDK (e.g. GraalVM) can fail the AGP `jlink` step.
69−- Android SDK + emulator tooling
70−- Node.js 20+ for transcript bundle builds
45+**NEVER read API keys from `process.env` as a fallback for provider authentication.** API keys must come only from values the user explicitly configured in Nimbalyst settings (the electron-store `apiKeys` object or project-level overrides).
7146  
72−### Commands
47+Past incident: a user had `ANTHROPIC_API_KEY` in a `.env` file for unrelated work. Nimbalyst silently picked it up via `process.env`, auto-persisted it, and billed the user's personal Anthropic account $100+ instead of their Nimbalyst subscription.
7348  
74−From the repository root the npm scripts wrap the Gradle tasks:
49+- No env fallbacks in `getApiKeyForProvider` — only `globalApiKeys[provider]` or project-level overrides
50+- No auto-import into the settings store
51+- Provider availability checks must only consider explicitly-stored keys
7552  
76−```bash
77−npm run android:build:transcript # build the transcript bundle
78−npm run android:test:unit # ./gradlew :app:testDebugUnitTest
79−npm run android:assemble:debug # ./gradlew :app:assembleDebug
80−npm run android:assemble:release # ./gradlew :app:assembleRelease
81−npm run android:bundle:release # ./gradlew :app:bundleRelease
82−```
53+If you are tempted to add `|| process.env.SOME_API_KEY` as a convenience fallback, **stop**.
8354  
84−To invoke Gradle directly, point `JAVA_HOME` at a Temurin 17 install (no hard-coded user path):
55+### Personal JWT vs Team JWT — Never Interchange Them
8556  
86−```bash
87−cd packages/android
88−JAVA_HOME=/path/to/temurin-17 ./gradlew :app:assembleDebug
89−JAVA_HOME=/path/to/temurin-17 ./gradlew :app:testDebugUnitTest
57+Stytch B2B gives a user a **different member id per org**. The **personal JWT** (`getPersonalSessionJwt()` / `personalUserId`) is for **personal sync ONLY** (the personal index room + session/prompt/draft/settings sync to the **mobile app**). The **team JWT** (`getSessionJwt()` / `getOrgScopedJwt(orgId)`) authorizes **ALL team collaboration** (tracker rooms, schema sync, document rooms, team room, project-access gate). Conflating them is this codebase's most-repeated sync bug.
58+ 
59+Use the branded types in `packages/runtime/src/auth/jwtScopes.ts` so a mix-up is a compile error. When "a second client can't see shared data", first check it's actually authenticated (an expired session is silently logged out → no team JWT).
60+ 
61+### Database Access Rules
62+ 
63+**Nimbalyst currently supports BOTH PGLite and better-sqlite3.** The migration is in progress; both backends are active in the codebase and the user's machine may be running either. Never write code that assumes one backend. Anywhere you touch the database — schema, queries, JSON handling, write paths — read [packages/electron/DATABASE.md](./packages/electron/DATABASE.md) for the divergent behaviors first.
64+ 
65+The biggest gotcha: **JSONB sub-extraction (`data->'someKey'`) returns a parsed object on PGLite but a JSON string on SQLite.** Either select the whole `data` column and parse it, or defensively parse the sub-extracted value with the standard `typeof x === 'string' ? JSON.parse(x) : x` idiom. A real bug from this divergence corrupted tracker `labelsMap` rows on 2026-06-02 because `applyRemoteItem` trusted the sub-extraction was already an object.
66+ 
67+**NEVER directly open or query the database files using Node.js or command-line tools.** PGLite at `~/Library/Application Support/@nimbalyst/electron/pglite-db` uses PID-based locking; better-sqlite3 takes its own exclusive lock. In both cases, opening from a second process risks corruption.
68+ 
69+**ALWAYS use the MCP database query tool instead:**
70+- Use `mcp__nimbalyst-extension-dev__database_query` for all database queries
71+- NEVER use `node -e "const { PGlite } = require(...)"` or sqlite CLI
72+ 
73+**For sync/collab bugs, local PGLite ≠ server collab state.** `tracker_body_cache`, `documents`, and other sync-related tables only reflect the local side. The authoritative state for shared trackers/documents lives in Cloudflare Workers (`packages/collabv3/` DurableObjects) and must be inspected separately via `wrangler tail` against the prod sync worker, or via wrangler-backed E2E tests (`tracker-content-collab.spec.ts` / `tracker-sync-collab.spec.ts` patterns, `RUN_COLLAB_TESTS=1`, `document-sync:open-test` IPC for Stytch bypass). Confirming "the body is in PGLite" is not the same as confirming "the body is on the server." See `feedback_local_state_vs_server_state.md`.
74+ 
75+### Always Run Your Own Observation Commands — Don't Push Logs/Curl/Tail to the User
76+ 
77+**Never ask the user to run `curl`, `wrangler tail`, `tail -f`, `gh` commands, or paste logs.** The agent has direct tool access to all of these.
78+ 
79+- Logs: `mcp__nimbalyst-extension-dev__get_main_process_logs` and `get_renderer_debug_logs`
80+- Database: `mcp__nimbalyst-extension-dev__database_query`
81+- HTTP: `Bash` with `curl` — the agent runs it
82+- Cloudflare workers: `Bash` with `wrangler tail` — the agent runs it (long-running tails can use `run_in_background`)
83+- GitHub: `Bash` with `gh`
84+- Runtime DOM / renderer state: `mcp__nimbalyst-extension-dev__renderer_eval`
85+ 
86+Past incident: session `702519e3` spent 23 turns debugging a Stytch JWKS rotation because the agent kept handing back commands for the user to run. The user finally said "run tail yourself!" and "you curl it!". If you catch yourself drafting "could you run X and paste the output?", stop and run X.
87+ 
88+Detailed patterns: [DEBUGGING_LOGS.md](./docs/DEBUGGING_LOGS.md).
89+ 
90+### End-to-End Verification Before Declaring Victory
91+ 
92+For any bug whose verification requires a `/restart` or a user manually exercising a UI flow, the **first** deliverable is a failing test that the fix must make pass. Never announce "fixed" before observing the bug go from broken to working — either via a test that flips red→green, or via logs showing the failing step now succeeding. See [end-to-end-verification.md](./.claude/rules/end-to-end-verification.md).
93+ 
94+Past incident: the 2026-05-20 tracker-body workstream announced "fixed" at least four times before the user finally said "you're killing me." Each announcement was based on "the code path looks right" or "tests pass," neither of which is the same as "the user can open the tracker and see the body."
95+ 
96+## Codebase Overview
97+ 
98+Nimbalyst is an extensible, AI-native workspace that supports multiple editor types through a unified extension system. While it originated as a Lexical-based markdown editor, the architecture is evolving toward a fully pluggable model where **all editors** — Lexical, Monaco, spreadsheets, diagrams, custom visual editors — are provided through extensions.
99+ 
100+This monorepo contains the Electron desktop app, runtime services (including the Lexical editor), extension SDK, native iOS app, and mobile support via Capacitor.
101+ 
102+## Extension Architecture
103+ 
104+See [EXTENSION_ARCHITECTURE.md](./docs/EXTENSION_ARCHITECTURE.md) for the EditorHost contract, supported editor types (Monaco, Lexical, custom React), the manifest format, and extension development guidelines.
105+ 
106+## Monorepo Structure
107+ 
90108 ```
109+packages/
110+ electron/ # Desktop app (Electron)
111+ runtime/ # Cross-platform runtime services (AI, sync, Lexical editor)
112+ ios/ # Native iOS app (SwiftUI)
113+ core/ # Shared utilities
114+ collabv3/ # Collaboration server (Cloudflare Workers)
115+ extension-sdk/ # Extension development kit
116+ extensions/ # Built-in extensions
117+```
91118  
92−### Play Store screenshots and video
119+- **Install**: `npm install` at repository root
120+- **npm workspaces** (not pnpm); packages reference each other via workspace protocol
121+- **Preserve `peer: true` flags in package-lock.json** — Some `npm install` configurations strip these flags, breaking CI for optional native dependencies (e.g., esbuild platform binaries). Investigate before committing if you see them disappearing.
93122  
94−`npm run android:screenshots` and `npm run android:walkthrough` drive an emulator against the debug-only screenshot mode in `app/src/debug/java/com/nimbalyst/app/screenshots/` (inert stub in `app/src/release/`). Never move that code into `src/main` — it seeds demo data and a fake paired state. See [ANDROID_MARKETING_SCREENSHOTS.md](../../docs/ANDROID_MARKETING_SCREENSHOTS.md).
123+Package-specific docs: `/packages/electron/CLAUDE.md`, `/packages/runtime/CLAUDE.md`, `/packages/ios/CLAUDE.md`, `/packages/collabv3/CLAUDE.md`.
95124  
96−### Builds, signing, and CI
125+## Development Commands
97126  
98−- The `google-services` plugin is applied only when `app/google-services.json` is present, so a build without it succeeds and push stays inert until the file is added.
99−- CI can inject Firebase config from the optional `ANDROID_GOOGLE_SERVICES_JSON_BASE64` GitHub secret by decoding it to `app/google-services.json` before the Gradle build.
100−- The release `signingConfig` reads the keystore path and credentials from environment variables: `NIMBALYST_ANDROID_KEYSTORE`, `NIMBALYST_ANDROID_KEYSTORE_PASSWORD`, `NIMBALYST_ANDROID_KEY_ALIAS`, `NIMBALYST_ANDROID_KEY_PASSWORD`. When the keystore is absent the release build is simply unsigned. Minification stays off (signed is not the same as minified).
101−- CI builds both the APK and Play-ready AAB via `.github/workflows/android-build.yml`, which supplies the keystore and signing secrets to produce signed release artifacts when secrets are present. CI also decodes `google-services.json` from the `ANDROID_GOOGLE_SERVICES_JSON_BASE64` secret and fails a signed build if that secret is missing, so a signed AAB never ships with push silently inert.
102−- To build a signed release locally, run `npm run android:bundle:signed` (wraps `scripts/android-bundle-signed.sh`). It pulls all signing secrets from the 1Password item `Nimbalyst Android Signing` (Nimbalyst vault) at build time via `op read`: the upload keystore is fetched to a temp file deleted on exit, and passwords/alias are injected into the Gradle env only. Never commit a keystore — `*.jks`/`*.keystore` are gitignored.
127+**Electron app:**
128+- Start dev: `cd packages/electron && npm run dev` (user runs this — don't do it yourself)
129+- Build for Mac: `npm run build:mac:local` or `npm run build:mac:notarized`
130+- Main process log: `~/Library/Application Support/@nimbalyst/electron/logs/main.log`
103131  
104−Open `packages/android/` in Android Studio, not the repo root.
132+**Testing:**
133+- Unit: `npm run test:unit` (vitest), or `npm run test:unit:ui`
134+- E2E: see [E2E_TESTING.md](./docs/E2E_TESTING.md)
105135  
106−## Agent Guidance
136+**Marketing screenshots & videos:** See [MARKETING_SCREENSHOTS.md](./docs/MARKETING_SCREENSHOTS.md). Quick: `cd packages/electron && npm run marketing:screenshots` (requires dev server on port 5273).
107137  
108−- Read the root `CLAUDE.md` before changing this package.
109−- Prefer following iOS behavior and naming when implementing cross-platform mobile features.
110−- Do not commit secrets or local machine config such as:
111− - `app/google-services.json`
112− - `local.properties`
113− - build outputs
114−- If Android Studio reports AGP incompatibility, the correct fix is usually to update Android Studio rather than downgrade AGP/Kotlin.
115−- When changing sync protocol behavior, inspect the matching iOS code paths in this repo and the collab server code paths in the sibling `nimbalyst-collab` repository before editing.
116−- When changing transcript bridge behavior, update or add Android tests in `app/src/test/` where possible.
138+**Multiple dev instances** (for collab/sync testing): `cd packages/electron && npm run dev:user2` uses an isolated `NIMBALYST_USER_DATA_DIR`, `VITE_PORT=5274`, and `--outDir=out2` to prevent file-watcher cross-talk. Worktrees auto-derive a per-worktree userData dir via `crystal-run.sh`.
117139  
118−## Important Files
140+**Other packages:** iOS — `npm run ios:test:swift`, `npm run ios:build:transcript`. Collab server — `npm run collabv2:dev`, `npm run collabv2:deploy`.
119141  
120−| File | Purpose |
142+## Releases
143+ 
144+See [RELEASING.md](./RELEASING.md). Use `/release-alpha [patch|minor|major]`. All release notes go in the `[Unreleased]` section of `CHANGELOG.md`; the script creates versioned entries and annotated git tags.
145+ 
146+## Cross-Cutting Patterns
147+ 
148+- **Error handling** — fail fast, validate at boundaries, workspace-scoped IPC takes `workspacePath` explicitly. See [ERROR_HANDLING.md](./docs/ERROR_HANDLING.md).
149+- **Naming conventions** — `camelCase` for wire protocol/JSON; `snake_case` only for SQL columns. See [NAMING_CONVENTIONS.md](./docs/NAMING_CONVENTIONS.md).
150+- **React DOM markers** — Tailwind utilities don't replace semantic class names. Every meaningful component needs a stable kebab-case class on its root. See [REACT_DOM_MARKERS.md](./docs/REACT_DOM_MARKERS.md).
151+ 
152+## Data Persistence
153+ 
154+The app uses **PGLite** (PostgreSQL in WebAssembly) for all data storage.
155+ 
156+- **Never use `localStorage` in the renderer.** Use app-settings store (global), workspace-settings store (per-project), or PGLite (complex data like AI sessions/document history).
157+- **All database timestamps must use `TIMESTAMPTZ`.** Never create `TIMESTAMP` (without timezone) columns; migrate legacy tables.
158+ 
159+See [DATABASE.md](./packages/electron/DATABASE.md) for tables, locations, shutdown rules, and timestamp handling.
160+ 
161+## Transcript Storage
162+ 
163+Two-tier architecture — `ai_agent_messages` (raw append-only log, sole source of truth) → `ai_transcript_events` (canonical, provider-agnostic, derived). The `TranscriptTransformer` is the single writer of canonical events; providers only write raw. See [TRANSCRIPT_ARCHITECTURE.md](./docs/TRANSCRIPT_ARCHITECTURE.md).
164+ 
165+## Documentation Reference
166+ 
167+**Read the relevant doc in its entirety before making changes in that area.** These contain authoritative patterns, anti-patterns, and architectural decisions.
168+ 
169+| File | Read when… |
121170 | --- | --- |
122−| `app/src/main/java/com/nimbalyst/app/NimbalystApplication.kt` | App-level dependency setup and startup wiring |
123−| `app/src/main/java/com/nimbalyst/app/MainActivity.kt` | Activity entry point and deep-link handling |
124−| `app/src/main/java/com/nimbalyst/app/ui/NimbalystAndroidApp.kt` | Root Compose app shell and navigation |
125−| `app/src/main/java/com/nimbalyst/app/sync/SyncManager.kt` | Core mobile sync lifecycle and message handling |
126−| `app/src/main/java/com/nimbalyst/app/sync/SyncProtocol.kt` | Android wire protocol types |
127−| `app/src/main/java/com/nimbalyst/app/crypto/CryptoManager.kt` | Encryption and key derivation |
128−| `app/src/main/java/com/nimbalyst/app/data/NimbalystDatabase.kt` | Room database definition |
129−| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptWebView.kt` | WebView transcript host |
130−| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptBridge.kt` | JS/native bridge handler |
131−| `src/transcript/main.tsx` | Shared transcript app entry point for Android |
171+| [EXTENSION_ARCHITECTURE.md](./docs/EXTENSION_ARCHITECTURE.md) | Working on extensions, creating editors, modifying editor↔host communication, adding new editor types. |
172+| [IPC_LISTENERS.md](./docs/IPC_LISTENERS.md) | Adding IPC events, debugging stale closures / race conditions in event handling, or seeing `MaxListenersExceededWarning`. |
173+| [IPC_GUIDE.md](./docs/IPC_GUIDE.md) | Writing main-process IPC handlers, adding `electronAPI` methods, or debugging main↔renderer IPC. |
174+| [EDITOR_STATE.md](./docs/EDITOR_STATE.md) | Working on editor components or TabEditor infrastructure; debugging editor state. |
175+| [JOTAI.md](./docs/JOTAI.md) | Working with Jotai atoms, debugging UI/state divergence, or adding new atoms. |
176+| [STATE_PERSISTENCE.md](./docs/STATE_PERSISTENCE.md) | Adding fields to any persisted state, or debugging "Cannot read properties of undefined" on app load. |
177+| [UI_PATTERNS.md](./docs/UI_PATTERNS.md) | Writing UI components, styling with CSS/Tailwind, or adding responsive behavior. |
178+| [ERROR_HANDLING.md](./docs/ERROR_HANDLING.md) | Writing IPC handlers or service methods that take required parameters or handle workspace state. |
179+| [NAMING_CONVENTIONS.md](./docs/NAMING_CONVENTIONS.md) | Designing wire protocols, sync code, or SQL schemas. |
180+| [AI_PROVIDER_TYPES.md](./docs/AI_PROVIDER_TYPES.md) | Working on AI integration, adding providers, or modifying model selection. |
181+| [TRANSCRIPT_ARCHITECTURE.md](./docs/TRANSCRIPT_ARCHITECTURE.md) | Working on transcript rendering, parsers, the canonical event pipeline, or mobile transcript sync. |
182+| [CONTEXT_WINDOW_USAGE_TRACKING.md](./docs/CONTEXT_WINDOW_USAGE_TRACKING.md) | Working on context-usage display, token tracking, or `ClaudeCodeProvider` streaming. |
183+| [INTERNAL_MCP_SERVERS.md](./docs/INTERNAL_MCP_SERVERS.md) | Adding MCP server functionality or new tools for AI agents. |
184+| [CUSTOM_TOOL_WIDGETS.md](./docs/CUSTOM_TOOL_WIDGETS.md) | Creating visual displays for MCP tool results or customizing tool rendering. |
185+| [INTERACTIVE_PROMPTS.md](./docs/INTERACTIVE_PROMPTS.md) | Working on durable prompts (AskUserQuestion, ExitPlanMode, GitCommitProposal, ToolPermission). |
186+| [WORKTREES.md](./docs/WORKTREES.md) | Working on worktree features, session isolation, or session↔worktree linkage. |
187+| [SESSION_HIERARCHY.md](./docs/SESSION_HIERARCHY.md) | Creating/parenting sessions or debugging session grouping in the left pane. |
188+| [HELP_WALKTHROUGHS.md](./docs/HELP_WALKTHROUGHS.md) | Adding help tooltips, creating walkthroughs, or modifying help content. |
189+| [REACT_DOM_MARKERS.md](./docs/REACT_DOM_MARKERS.md) | Working on React UI, adding components, or improving testability/devtools navigation. |
190+| [WALKTHROUGHS.md](./docs/WALKTHROUGHS.md) | Creating multi-step walkthroughs or debugging walkthrough flow. |
191+| [E2E_TESTING.md](./docs/E2E_TESTING.md) | Writing/debugging E2E tests, or running them as an AI agent (especially in worktrees). |
192+| [DIALOGS.md](./docs/DIALOGS.md) | Adding or modifying modal dialogs. |
193+| [AGENT_PERMISSIONS.md](./docs/AGENT_PERMISSIONS.md) | Working on agent permissions, approval flows, or runtime permission checks. |
194+| [ANALYTICS_GUIDE.md](./docs/ANALYTICS_GUIDE.md) | Adding/modifying PostHog events, or using PostHog MCP tools. |
195+| [POSTHOG_EVENTS.md](./docs/POSTHOG_EVENTS.md) | Adding, modifying, or removing any PostHog analytics event — keep this in sync. |
196+| [POSTHOG_MCP_INTEGRATION.md](./docs/POSTHOG_MCP_INTEGRATION.md) | Using PostHog MCP tools or extending PostHog functionality. |
197+| [THEMING.md](./packages/electron/THEMING.md) | Working on themes or color schemes. |
198+| [RELEASING.md](./RELEASING.md) | Preparing a release or debugging release scripts. |
199+| [MARKETING_SCREENSHOTS.md](./docs/MARKETING_SCREENSHOTS.md) | Adding marketing screenshots/videos or modifying capture choreography. |
200+| [ANDROID_MARKETING_SCREENSHOTS.md](./docs/ANDROID_MARKETING_SCREENSHOTS.md) | Capturing Play Store screenshots or the reviewer screencast for the Android app. |
201+| [FILE_WATCHING_AND_CHANGE_TRACKING.md](./docs/FILE_WATCHING_AND_CHANGE_TRACKING.md) | Working on file watchers, AI change detection, diff display, or the FilesEditedSidebar. |
202+| [WEEKLY_DASHBOARD.md](./docs/WEEKLY_DASHBOARD.md) | Adding/modifying insights on the Weeklys PostHog dashboard. |
203+| [VOICE_MODE.md](./docs/VOICE_MODE.md) | Working on voice mode, voice-agent prompts, audio pipeline, or session lifecycle. |
204+| [TRACKER_WORKFLOWS.md](./docs/TRACKER_WORKFLOWS.md) | Creating decision or bug tracker items as part of a fix or design decision. |
205+| [ARCHITECTURE_DIAGRAMS.md](./docs/ARCHITECTURE_DIAGRAMS.md) | Making any architectural decision — create an Excalidraw diagram. |
206+| [DEBUGGING_LOGS.md](./docs/DEBUGGING_LOGS.md) | Investigating bugs — use the log access tools, don't ask the user to paste logs. |
207+| [MAIN_PROCESS_INIT.md](./packages/electron/MAIN_PROCESS_INIT.md) | Working on Electron main-process bootstrap, singleton init, or IPC handler registration. |
208+| [DATABASE.md](./packages/electron/DATABASE.md) | Working with PGLite tables, shutdown, or timestamp handling. |
209+ 
210+## AI Features (quick reference)
211+ 
212+- **AI Chat Panel**: multi-provider (Claude, OpenAI, LM Studio, Claude Code), document-aware; Cmd+Shift+A
213+- **Session Manager**: global view (Cmd+Alt+S); search, export, delete
214+- **Model Configuration**: dynamic from provider APIs; no hardcoded models
215+- **Git Worktrees**: isolated AI coding sessions via "New Worktree" button
216+ 
217+## Tracker Workflows
218+ 
219+When choosing between alternatives (libraries, patterns, deciding NOT to do something), log a **decision** tracker item. When fixing a bug, ensure a **bug** tracker item exists before writing fix code. See [TRACKER_WORKFLOWS.md](./docs/TRACKER_WORKFLOWS.md) for the exact `tracker_create` calls and lifecycle.
220+ 
221+## Architecture Diagrams for Decisions
222+ 
223+Whenever an architectural change is proposed, create an Excalidraw diagram in `nimbalyst-local/architecture/` and share the diagram file/link in the conversation. Use `capture_editor_screenshot` only when visual verification is needed or the user explicitly asks for an inline image. See [ARCHITECTURE_DIAGRAMS.md](./docs/ARCHITECTURE_DIAGRAMS.md).
224+ 
225+## Verifying Development Mode
226+ 
227+Before making code changes, use `mcp__nimbalyst-extension-dev__get_environment_info` to verify Nimbalyst is running in dev mode. If the user is running a packaged build, code changes won't take effect — tell them to start the dev server.
228+ 
229+## Debugging with Log Access Tools
230+ 
231+See the Critical Rules block above ("Always Run Your Own Observation Commands"). Detailed patterns: [DEBUGGING_LOGS.md](./docs/DEBUGGING_LOGS.md).
232+ 
233+## General Development Guidelines
234+ 
235+- **Never use emojis** — not in commits, code, or documentation, unless explicitly requested
236+- **Never use overly enthusiastic phrases** ("Perfect!", "Terrific!", etc.)
237+- **Never commit changes unless explicitly asked**
238+- **Never commit files under `nimbalyst-local/`** — gitignored, local-only working files
239+- **Never provide time or effort estimates**
240+- **Don't disable tests without asking first**
241+- **Don't run `npm run dev` yourself** — user does that
242+- **Never release without being explicitly instructed**
243+- **Don't `git reset` or `git add -A` without asking**
244+- **Don't add `Co-Authored-By` lines to commit messages**
245+- **Never restart Nimbalyst without explicit permission** — always ask before `restart_nimbalyst`
246+- **Never mark work done before the user approves it — but a commit IS their approval.** Until the work is committed, set tracker items to `in-review` and session phase to `validating`, never `done` / `complete`. Once the user commits the work, they have reviewed it and agreed it's finished: put a closing reference (`Fixes NIM-123`) in the commit message and the item closes itself; also set session phase to `complete`. Do not leave finished, committed work parked in `in-review`. `approved` on the review lane remains human-only.
247+ 
248+**Keyboard Shortcuts**: when adding or modifying shortcuts, update `KeyboardShortcutsDialog.tsx`.
249+ 
250+## Support
251+ 
252+User support docs live in `support/`. Notable: `force-restore-database-backup.md` for manual database restore.
253+if i ask you to propose a commit, first update the `CHANGELOG.md` (at the repo root) and include it in the commit proposal
254+if a commit is intended to fix a github issue, include the issue number and a closing reference in the commit message when appropriate (fixes #123 or closes #123)
132255  
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack