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-packages-ios-claude

Comparison

A · CLAUDE.md · nimbalyst/nimbalystB · CLAUDE.md · nimbalyst/nimbalyst
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections4111016%
Commands0820%
Section tags33430%

What each file covers

Sections

4 shared · 11 only in A · 10 only in B
  • − 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
  • + 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
  •   Package Structure
  •   Development
  •   Prerequisites
  •   Commands

Commands

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

Section tags

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

Line diff

+89 added−97 removed35 unchanged26.5% 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 · 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 
@@ −1 +1 @@
1−# Android Package (Native Android App)
1+# iOS Package (Native iOS App)
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+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.
44  
5−## Overview
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.
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− 
177 ## Package Structure
188  
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
359 ```
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)
3624  
37−## Key Architecture Rules
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
3829  
39−### Transcript
30+ CryptoCompatibility/ # CommonCrypto bridging header for PBKDF2 key derivation
4031  
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.
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
4436  
45−### Sync and Encryption
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+```
4641  
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.
42+## Key Architecture Decisions
5043  
51−### Persistence
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
5249  
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.
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`
5655  
57−### Firebase / Notifications
56+### iPad Support
57+- `NavigationSplitView` for regular size class (sidebar + detail)
58+- `NavigationStack` for compact size class (iPhone)
5859  
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− 
6360 ## Development
6461  
6562 ### 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− 
7267 ### 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
7372  
74−From the repository root the npm scripts wrap the Gradle tasks:
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+```
7578  
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+ 
7682 ```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
83+npm run ios:build:transcript
8284 ```
8385  
84−To invoke Gradle directly, point `JAVA_HOME` at a Temurin 17 install (no hard-coded user path):
86+Output: `dist-transcript/transcript.html` + `dist-transcript/assets/` (JS bundle + Material Symbols font).
8587  
88+After building, copy the output to Xcode resources:
8689 ```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
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/
9093 ```
9194  
92−### Play Store screenshots and video
95+**CRITICAL: React hooks rules in `src/transcript/main.tsx`**
9396  
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).
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.
9598  
96−### Builds, signing, and CI
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.
97104  
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.
105+## Key Files
103106  
104−Open `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− 
120107 | 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 |
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)
132124  
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