Files
Claude 5d83b163eb refactor: Marmot leave, permissions, timeline and edit rules from marmot-kmp
- Leaving uses MarmotManager.leave in the app and amy: the heir is chosen
  among the members (a listed admin who already left no longer counts), the
  last member of a group can leave (it used to throw), and amy no longer
  publishes the step-down commit a second time.
- The member menu and revokeMarmotGroupAdmin use MarmotGroupPermissions, so
  demotion is checked against the members rather than the list's length.
- The feed asks MarmotAppEvent.isTimelineRow and the edit overlay asks
  MarmotMessageEdit instead of keeping their own copies of those rules.
- The media policy is built with EncryptedMediaPolicyV2.forBlossom, so amy's
  set-policy accepts a base URL without a trailing slash too.
- Group creation moves from CreateGroupScreen into
  BaseAccountViewModel.createMarmotGroup, and drops the profile commit that
  repeated the name already set at epoch 0.
- Removed: the unused commons viewmodels.MarmotGroupFeedViewModel and the
  MIP-04 send path nothing reached.
- cordn: the exposure card and amy stop calling the deprecated
  differsFromMarmot; CordnRuntimeTest moves to commons/src/jvmTest.
- AGENTS.md keeps a short version of the two-process rules; the detail moves
  to android-expert's references (31.6 KB of Codex's 32 KB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSSNWvwXdNnNwYnLoEvNcq
2026-10-25 01:26:36 +00:00

33 lines
1.9 KiB
Markdown

# The two Android processes
Moved from `AGENTS.md`, which keeps the short version.
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`).
The sandbox runtime lives in its own module **`:nappletHost`** (depends on
`:commons`, `:commonsUI` — the shell's web contract and Compose resources — and
quartz; **never** `:amethyst`) so it *cannot* import
`Amethyst`/`LocalCache`/`Account` — the broker-side (signer, gateways, registry)
stays in `:amethyst` and the two halves talk over Messenger IPC.
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/archive/2026-06-22-napplet-nsite-security.md`.