RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

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

Comparison

A · CLAUDE.md · nimbalyst/nimbalystB · CLAUDE.md · nimbalyst/nimbalyst
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections4101116%
Commands0280%
Section tags34330%

What each file covers

Sections

4 shared · 10 only in A · 11 only in B
  • − iOS Package (Native iOS App)
  • − Key Architecture Decisions
  • − Authentication Flow
  • − Data Flow
  • − iPad Support
  • − From monorepo root:
  • − From packages/ios/:
  • − Transcript Bundle
  • − Key Files
  • − Testing
  • + Android Package (Native Android App)
  • + Overview
  • + Key Architecture Rules
  • + Transcript
  • + Sync and Encryption
  • + Persistence
  • + Firebase / Notifications
  • + Play Store screenshots and video
  • + Builds, signing, and CI
  • + Agent Guidance
  • + Important Files
  •   Package Structure
  •   Development
  •   Prerequisites
  •   Commands

Commands

0 shared · 2 only in A · 8 only in B
  • − npm run ios:test:swift
  • − npm run ios:build:transcript
  • + 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

Section tags

3 shared · 4 only in A · 3 only in B
  • − test
  • − security
  • − dependencies
  • − monorepo
  • + code-style
  • + do-not
  • + agent-behaviour
  •   setup
  •   build
  •   architecture

Line diff

+97 added−89 removed35 unchanged26.5% identical
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/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 
@@ −1 +1 @@
1−# iOS Package (Native iOS App)
1+# Android Package (Native Android App)
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+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.
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+## Overview
66  
7+The 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+ 
15+Voice agent features are intentionally out of scope for Android.
16+ 
717 ## Package Structure
818  
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
935 ```
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)
2436  
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
37+## Key Architecture Rules
2938  
30− CryptoCompatibility/ # CommonCrypto bridging header for PBKDF2 key derivation
39+### Transcript
3140  
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
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.
3644  
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−```
45+### Sync and Encryption
4146  
42−## Key Architecture Decisions
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.
4350  
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
51+### Persistence
4952  
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`
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.
5556  
56−### iPad Support
57−- `NavigationSplitView` for regular size class (sidebar + detail)
58−- `NavigationStack` for compact size class (iPhone)
57+### Firebase / Notifications
5958  
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+ 
6063 ## Development
6164  
6265 ### Prerequisites
63−- Xcode 16+
64−- Node.js 20+ (for transcript bundle)
65−- XcodeGen (`brew install xcodegen`)
6666  
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+ 
6772 ### 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
7273  
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−```
74+From the repository root the npm scripts wrap the Gradle tasks:
7875  
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:
81− 
8276 ```bash
83−npm run ios:build:transcript
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
8482 ```
8583  
86−Output: `dist-transcript/transcript.html` + `dist-transcript/assets/` (JS bundle + Material Symbols font).
84+To invoke Gradle directly, point `JAVA_HOME` at a Temurin 17 install (no hard-coded user path):
8785  
88−After building, copy the output to Xcode resources:
8986 ```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/
87+cd packages/android
88+JAVA_HOME=/path/to/temurin-17 ./gradlew :app:assembleDebug
89+JAVA_HOME=/path/to/temurin-17 ./gradlew :app:testDebugUnitTest
9390 ```
9491  
95−**CRITICAL: React hooks rules in `src/transcript/main.tsx`**
92+### Play Store screenshots and video
9693  
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.
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).
9895  
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.
96+### Builds, signing, and CI
10497  
105−## Key Files
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.
106103  
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 |
104+Open `packages/android/` in Android Studio, not the repo root.
119105  
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)
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 |
124132  
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