From 0d107904ad30bc364fe4ceac9a9d2998d1dbd992 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Wed, 1 Jul 2026 09:28:02 -0300 Subject: [PATCH] Expand AGENTS.md with toolchain, flavors, architecture, and CI gotchas --- AGENTS.md | 70 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c092e1bf..335425fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,23 +1,63 @@ -# Repository instructions for Codex +# Repository instructions for Codex / OpenCode -## Codex Web environment +Amber is a single-module Android app (`:app`, package `com.greenart7c3.nostrsigner`) — a Nostr event signer (NIP-46 / NIP-55). Despite top-level `lib/`, `commonMain/`, `androidMain/` dirs, `settings.gradle.kts` includes only `:app`; those are not separate Gradle modules. -Use the committed setup scripts when creating a Codex Web cloud environment: +## Toolchain -- Setup script: `bash .codex/setup.sh` -- Maintenance script: `bash .codex/maintenance.sh` - -The setup script installs or verifies Java 21, bootstraps the Android command-line SDK when needed, installs Android API 36/build-tools 36.0.0, accepts SDK licenses, and warms Gradle dependencies for the default free debug unit-test sources. +- JDK 21 required (source/target compat 21). CI and `.codex/setup.sh` use Temurin 21. +- `compileSdk = 37`, `minSdk = 26`, R8 full mode enabled, Gradle parallel + configuration-cache. +- Room schemas are exported to `app/schemas` via KSP (`room.schemaLocation`). ## Build and validation commands -- `./gradlew ktlintCheck` — Kotlin style check. -- `./gradlew test --no-daemon` — JVM unit tests for all variants. -- `./gradlew assembleDebug --no-daemon` — debug APK build for both product flavors. -- `./gradlew assembleRelease --no-daemon` — release APK build; only expect signing when `SIGN_RELEASE` and `keystore.properties` are present. +- `./gradlew ktlintCheck` — Kotlin style check (pre-commit hook runs this). +- `./gradlew ktlintFormat` — auto-fix formatting issues. +- `./gradlew test --no-daemon` — JVM unit tests for all variants (pre-push hook runs `./gradlew test`). +- Run one test: `./gradlew :app:testFreeDebugUnitTest --tests "com.greenart7c3.nostrsigner.SomeTest"` (or `--tests "*SomeTest.method"`). +- `./gradlew assembleDebug --no-daemon` — debug APK for both product flavors. +- `./gradlew assembleRelease --no-daemon` — release APK; signing only activates when env `SIGN_RELEASE` is set **and** `keystore.properties` exists. CI otherwise `touch`es an empty one. +- `./build.sh ` — builds free + offline release APKs/AABs into `~/release/` and calls `generate_manifest.sh`. -## Project notes +Required order when pushing: `ktlintCheck` → `test` → build (mirrored by the hooks, but run them directly; do not rely on hooks). -- The `free` flavor is the default online build. -- The `offline` flavor must not introduce network-only behavior. -- Git hooks are installed by the root Gradle `preBuild` wiring; do not rely on hooks as a substitute for running checks directly. +## Product flavors (dimension `version`) + +| Flavor | Notes | +|--------|-------| +| `free` (default) | Online: OkHttp, Coil, kmptor, relay connectivity. Owns `INTERNET`/network permissions. | +| `offline` | No network stack. `app/src/offline/AndroidManifest.xml` **removes** `INTERNET`/`CHANGE_NETWORK_STATE`/`ACCESS_NETWORK_STATE` with `tools:node="remove"`. | +| `benchmark` | Mirrors `free` network deps; `applicationIdSuffix=.benchmark`, `versionNameSuffix=-BENCHMARK`; CI builds a signed release per push for side-by-side install. | + +- Guard any network-only code with `BuildFlavorChecker.isOfflineFlavor()`. There is also `BuildConfig.IS_FDROID_BUILD` (false by default; the F-Droid release workflow flips it true and strips `REQUEST_INSTALL_PACKAGES` via `sed`) — use it to disable self-update / Zapstore. +- **Offline permissions gate:** `check-offline-permissions.yml` runs `./gradlew processOfflineDebugManifest` and greps the merged manifest for the three network permissions. Any new dependency that leaks `INTERNET` etc. will fail this check — verify the offline merged manifest when adding network-capable deps. + +## Architecture notes (not obvious from filenames) + +Three request ingestion paths all converge on `Account.sign()` / encrypt-decrypt: +1. `nostrsigner://` / `nostrconnect://` Intent → `SignerActivity` → `IntentUtils` → approval bottom sheet. +2. ContentProvider IPC → `SignerProvider` (synchronous, `runBlocking`). +3. NIP-46 relay kind 24133 → `NotificationSubscription` → `EventNotificationConsumer` → `BunkerRequestUtils`. + +`Amber.kt` (the `Application` class) is the DI container: owns `applicationIOScope`, the Quartz `NostrClient`, `notificationSubscription`, `isStartingAppState` (set during `runMigrations()` — wait on `isStartingAppState.first { !it }`), and `settings.killSwitch` (disconnects all relays when true). + +Per-account isolation: every `npub` gets its own `SharedPreferences` (`prefs_${npub}`), `AppDatabase` (`amber_db_${npub}`), `LogDatabase`, and `HistoryDatabase` — all lazily cached in `ConcurrentHashMap`s in `Amber`. Decrypted keys loaded via `LocalPreferences.loadFromEncryptedStorage()` and cached in `LargeCache`. + +Biometric/PIN lock (`useAuth`/`usePin`, `SecurityScreen`, `BiometricAuthScreen`) is a **UI-only app-launch gate**, not a signing gate. `SignerProvider` and the NIP-46 path never touch it; auto-accept permission rules sign silently on all three paths. Authorization for automatic signing is governed solely by the permission system (`ApplicationEntity` / `ApplicationPermissionsEntity`: `rememberType`, `acceptUntil`/`rejectUntil`, `kind`). Do not wire signing through the biometric prompt. + +See `CLAUDE.md` for the key-files table (verified accurate against the current tree). + +## Conventions + +- ktlint `android_studio` code style (`.editorconfig`); star imports effectively disabled; trailing commas allowed; `@Composable` functions exempt from the function-naming rule. Run `ktlintFormat` rather than hand-formatting. +- Translations live in `app/src/main/res/values-/strings.xml`; `MissingTranslation` lint is intentionally disabled and the shipped locales are pinned by `androidResources.localeFilters` in `app/build.gradle.kts`. +- Git hooks (`git-hooks/pre-commit`, `pre-push`) are auto-installed by the root `build.gradle.kts` `installGitHook` task wired into `:app` `preBuild`. Do not rely on them as a substitute for running checks directly. + +## Codex Web / cloud setup + +Use the committed scripts for cloud environments: +- Setup: `bash .codex/setup.sh` — installs/verifies Java 21, bootstraps Android cmdline SDK, installs API 36 / build-tools 36.0.0, accepts licenses, and prewarms Gradle for `free` debug unit-test sources. +- Maintenance: `bash .codex/maintenance.sh` — refreshes Gradle metadata in cached containers. + +## Reproducibility + +`Dockerfile` + `apkdiff.py` verify reproducible builds: `docker build -t amber-repro --build-arg VERSION=vX.Y.Z --build-arg APK_TYPE=free-arm64-v8a .` then `docker run --rm amber-repro` (expect `APKs match!`).