From 55cbb72a992dbdb3485491ae098b4e1cd505dc0c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 20:18:34 +0000 Subject: [PATCH] docs(napplets): document the two-process model for future contributors/AIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a heads-up to the Amethyst Application KDoc and .claude/CLAUDE.md that the app runs in two OS processes (main + :napplet), that Android reuses the single Application class in both, and that statics/objects (Amethyst.instance, LocalCache, NappletLaunchRegistry) are per-process — so code must not assume `instance` exists off the main process or share singletons across the boundary. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde --- .claude/CLAUDE.md | 26 ++++++++++++++++++ .../com/vitorpamplona/amethyst/Amethyst.kt | Bin 5271 -> 6338 bytes 2 files changed, 26 insertions(+) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 3ee4d735e9..7394c3a74b 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -84,6 +84,32 @@ amethyst/ module's `plans/YYYY-MM-DD-.md` (e.g. `cli/plans/`, `commons/plans/`). The global `docs/plans/` folder is frozen — don't add new plans there. +## Android Runtime Processes (IMPORTANT — the app runs in TWO processes) + +The Android app is **not single-process**. Android instantiates the one `Amethyst` +Application class (there is no per-process Application in the manifest) in **both**: + +- **main** — the normal app: UI, account, signer, `LocalCache`, relay client. + `Amethyst.instance` (`AppModules`) is built here. +- **`:napplet`** — the sandboxed WebView host for NIP-5D napplets / NIP-5A nSites + (`NappletHostActivity`, declared `android:process=":napplet"`). It holds **no** + account or keys; `Amethyst.onCreate()` early-returns here so `Amethyst.instance` + is **left unset** (touching it throws `UninitializedPropertyAccessException`). + +Consequences — don't get caught assuming one process: + +- **Processes don't share memory.** Every `object`/companion/`static` is a + *separate copy per process*: `LocalCache` (an `object`), `NappletLaunchRegistry`, + etc. The populated `LocalCache` lives only in **main**; the sandbox neither + builds nor should reference it (a stray reference would lazily create a second, + empty cache there). +- **Don't assume `Amethyst.instance` exists.** Any code reachable from `:napplet` + (the host activity, content server, or an Application lifecycle callback like + `onTrimMemory`) must guard on the process and never reach for `instance`. +- **Cross-process state goes over Messenger IPC**, never a shared singleton — this + is why the broker (main) owns `NappletLaunchRegistry` and the sandbox only relays + an opaque token. See `amethyst/plans/2026-06-22-napplet-nsite-security.md`. + ## Tech Stack Exact versions live in `gradle/libs.versions.toml` (the source of truth — check diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index fb43052698c6a5cebe55a7b44417be4d677c5f5d..432ca4618dca616c1942bbfc008d3953b53cfe92 100644 GIT binary patch delta 1087 zcmY*YOK#La5M5=%f-{t|A!*Hc1QMH(Sd5ZIM#@hlkVQ0-d)hU26T8dmZfD|^IRp|% zfF%+);3%AcBT(g!2wS)7)%(@g<6p0Td_U+GO1%IDw*|t=Wu9yX_p7=tt)`h83a)OT_GB;w3`)&FyKKS|XzN1ME?ONi3;;n_2&U8_ zK*t!UO#!syH>{RCAwzRsTWRZ*g5RIN$P+m7l`i}3M<3R_#u#c`oI$X;LqCJ`oQy6{ z7!A`Icr10xTP(VGoWT7+!{A2X0ggoF1$9|dV%Qu5>)JN9j8LFQQFc%6#3Pg5Bom=@ z=~#P-F?~iWu-q*@VFq`&{b+Fy1%(JZ@^HDnp1(bXaVWrI__6|bW7!OW?U+e=IiQ~L z+{#4zXk*I)GGvQ9qkYDi+Aw-~ zGo&&EThP8AiF*Qelx?Xa_elGgbc`sVN?lJChK$^+2%(T)E3C-3OEv%>(YKJ&_7j?z zE(}$*c6=d=lN#O{SJo&$c0zJiht{Z2H_n6xdeVg``(;cXQ%!GRehU!-Ao201( zPJ1eRC=HAJS7E>>24Tl`{nf}8IhJW~IKe>%uYyRU8exj?Xf