From ac519e0471b290336ebdc5936d7b19071a66a760 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 19:31:37 +0000 Subject: [PATCH] Document desktop multi-account layout and remove-app in CLAUDE.md --- CLAUDE.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8b1616f7..e50a6491 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,13 +51,13 @@ All three paths converge on `Account.sign()` / encrypt/decrypt methods backed by ### Desktop bunker app (`:desktop` + `:shared`) -The desktop app only implements the NIP-46/bunker path (paths 1–2 above are Android-only concepts — no intent scheme or ContentProvider on desktop) and is intentionally **not** a port of the full Android app: single account, no Tor/proxy support yet. It does have its own multi-screen UI (home, connect, connected apps + per-app permission editor, activity log, settings) mirroring Android's look and key bunker-relevant flows — see below. +The desktop app only implements the NIP-46/bunker path (paths 1–2 above are Android-only concepts — no intent scheme or ContentProvider on desktop) and is intentionally **not** a port of the full Android app: no Tor/proxy support yet. It supports multiple local accounts (switch/add/logout, see below) and has its own multi-screen UI (home, connect, connected apps + per-app permission editor, activity log, settings) mirroring Android's look and key bunker-relevant flows — see below. - `shared/src/commonMain/.../BunkerSigningEngine.kt` — decrypts an incoming kind-24133 event, resolves the requesting app's display name (from NIP-46 `connect` client metadata via `BunkerClientMetadata`, or an optional `appNameLookup` fallback for later requests), checks a `BunkerPermissionStore` for an auto-accept/reject rule (falling back to a `BunkerApprovalPort` prompt), performs the sign/nip04/nip44 operation via `BunkerSigner` (wraps Quartz's `NostrSignerInternal`), and returns the signed response event. This is new code written for the desktop use case — it does **not** replace or get called by `:app`'s `BunkerRequestUtils`/`EventNotificationConsumer`, which keep using their existing Room/Context-coupled implementation directly (rewiring the shipping Android signing path onto shared code was judged higher regression risk than the desktop use case warranted). -- `shared/.../SecureCryptoHelper.kt` is `expect`/`actual`: the desktop `actual` (`desktopMain`) stores an AES-256 master key in the OS keychain via `java-keyring` (Windows Credential Manager / macOS Keychain / Linux Secret Service — requires a running Secret Service provider, e.g. gnome-keyring, on Linux) and AES-GCM-encrypts secrets at rest with it, mirroring the shape of the Android `actual` (Keystore-backed, itself a from-scratch mirror of `:app`'s own `SecureCryptoHelper.kt` — not wired in, kept for parity/future adoption). -- `desktop/src/main/kotlin/.../data/` — `AccountStore` (generate/import the single desktop account, persisted encrypted), `SqliteBunkerPermissionStore`/`SqliteBunkerHistoryLogger`/`RelayStore`/`SettingsStore` (plain JDBC against `org.xerial:sqlite-jdbc`, schema created on first run under `~/.amber-bunker/`, not Room). Permissions and history are queryable per-app (`permissionsFor`/`deletePermission`, `recentHistoryFor`) for the app-detail screen, not just the account-wide `revokeAll`/`connectedApps` used at first. +- `shared/.../SecureCryptoHelper.kt` is `expect`/`actual`: the desktop `actual` (`desktopMain`) stores an AES-256 master key in the OS keychain via `java-keyring` (Windows Credential Manager / macOS Keychain / Linux Secret Service — requires a running Secret Service provider, e.g. gnome-keyring, on Linux) and AES-GCM-encrypts secrets at rest with it, mirroring the shape of the Android `actual` (Keystore-backed, itself a from-scratch mirror of `:app`'s own `SecureCryptoHelper.kt` — not wired in, kept for parity/future adoption). One master key is reused to encrypt every stored account's key. +- `desktop/src/main/kotlin/.../data/` — `AccountStore` manages multiple local accounts under `~/.amber-bunker/accounts//{account.key,bunker.db}`, each key persisted encrypted, plus a top-level `active_account` pointer file (`setActive`/`activeAccount` use an atomic temp-file-then-rename). `migrateLegacyLayoutIfNeeded()` is called once at startup to move a pre-multi-account flat `~/.amber-bunker/{account.key,bunker.db}` layout into the new per-account form — idempotent, and aborts without touching any file if the legacy key can't be decrypted. `SqliteBunkerPermissionStore`/`SqliteBunkerHistoryLogger`/`RelayStore`/`SettingsStore` are plain JDBC against `org.xerial:sqlite-jdbc` (not Room), opened per-account via `BunkerDatabase.open(pubKeyHex)`, schema created on first run. Permissions and history are queryable per-app (`permissionsFor`/`deletePermission`, `recentHistoryFor`) for the app-detail screen, and `SqliteBunkerHistoryLogger.removeApp` drops a connected app's record (its history is kept for the activity log's audit trail). - `desktop/src/main/kotlin/.../relay/BunkerRelayConnection.kt` — subscribes to kind-24133 events addressed to the account pubkey using Quartz's own `NostrClient` + `BasicOkHttpWebSocket` (both resolve from the multiplatform `com.vitorpamplona.quartz:quartz` coordinate's `quartz-jvm` variant — no vendored crypto or websocket code was needed), publishes engine responses back via `publishAndConfirm`, and exposes `connectedRelays: StateFlow>` for the Home screen's connection status. -- `desktop/src/main/kotlin/.../ui/AppShell.kt` — the app's shell: a `NavigationRail` + hand-rolled navigation (`ui/nav/Screen.kt` sealed class; no KMP navigation-compose library exists yet) across `HomeScreen`/`ConnectScreen`/`ConnectedAppsScreen`/`AppDetailScreen`/`ActivityScreen`/`SettingsScreen`, plus the `ApprovalDialog` overlay fed by `DesktopApprovalPort`'s pending-request queue. `ui/theme/DesktopTheme.kt` ports Amber's exact warm color scheme/shapes from `app/.../ui/theme/Theme.kt` (Compose Desktop has no reliable `isSystemInDarkTheme()`, so theme mode is a manual Light/Dark/System toggle persisted via `SettingsStore`, System defaulting to Light). +- `desktop/src/main/kotlin/.../ui/BunkerApp.kt` owns the account list/active pointer (running the legacy-layout migration once at startup) and passes the active account's pubkey into `AppShell`, which loads that account's key and (re)builds its signing/relay/DB stack keyed on the pubkey — switching accounts tears down and rebuilds that whole stack. `ui/AppShell.kt` is the app's shell: a `NavigationRail` + hand-rolled navigation (`ui/nav/Screen.kt` sealed class; no KMP navigation-compose library exists yet) across `HomeScreen`/`ConnectScreen`/`ConnectedAppsScreen`/`AppDetailScreen`/`ActivityScreen`/`SettingsScreen`, plus the `ApprovalDialog` overlay fed by `DesktopApprovalPort`'s pending-request queue and an `AccountSwitcherDialog` (opened from the Home screen's avatar) for switching/adding/logging out of accounts, backed by a shared `ui/components/ConfirmDialog.kt` for destructive confirmations (logout, remove app). `ui/theme/DesktopTheme.kt` ports Amber's exact warm color scheme/shapes from `app/.../ui/theme/Theme.kt` (Compose Desktop has no reliable `isSystemInDarkTheme()`, so theme mode is a manual Light/Dark/System toggle persisted via `SettingsStore`, System defaulting to Light). ### Global state — `Amber.kt` @@ -117,5 +117,7 @@ In other words, the lock controls who can open and navigate the app UI; it does | `shared/.../BunkerSigningEngine.kt` | Desktop's NIP-46 request handler (decrypt → permission check → sign/encrypt → respond) | | `shared/.../SecureCryptoHelper.kt` | `expect`/`actual` at-rest encryption: Android Keystore vs. desktop OS keychain | | `desktop/.../relay/BunkerRelayConnection.kt` | Desktop's kind-24133 relay subscription + response publishing | -| `desktop/.../ui/AppShell.kt` | Desktop's nav rail + screen dispatch + approval dialog overlay | +| `desktop/.../data/AccountStore.kt` | Desktop's multi-account key storage, active-account pointer, legacy-layout migration | +| `desktop/.../ui/BunkerApp.kt` | Desktop's account-list/active-account owner; runs the startup migration | +| `desktop/.../ui/AppShell.kt` | Desktop's nav rail + screen dispatch + approval dialog overlay, keyed on the active account | | `desktop/.../ui/theme/DesktopTheme.kt` | Desktop theme, ported from `app/.../ui/theme/Theme.kt` | \ No newline at end of file