/account-state
Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`, `muteList`, `bookmarkState`, each exposing a `.flow` StateFlow), `LocalCache` (the object-level event store backed by
$ npx -y skills add vitorpamplona/amethyst --skill account-state --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/account-state
Context preview
The summary Claude sees to decide when to auto-load this skill.
Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`, `muteList`, `bookmarkState`, each exposing a `.flow` StateFlow), `LocalCache` (the object-level event store backed by
SKILL.md
account-state.SKILL.mdname: account-state
description: Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`, `muteList`, `bookmarkState`, each exposing a `.flow` StateFlow), `LocalCache` (the object-level event store backed by `LargeCache`), `User`/`Note` model classes, or any ViewModel that reads user-specific state. Covers how account events cascade from relay arrival to UI state, how to add a new account-scoped setting, and when to read from `LocalCache` vs subscribe to a StateFlow.
Account & Local Cache State
The backbone of Amethyst's client state: one `Account` per signed-in user, plus the singleton `LocalCache` that holds every `Note` and `User` the client has seen.
When to Use This Skill
- Working on `amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt`
- Working on `amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt`
- Adding a new account-scoped setting (mutes, bookmarks, custom relay lists, private lists)
- Reading/writing user metadata (`User`) or note state (`Note`)
- Deciding whether to query `LocalCache` vs subscribe to an `Account` StateFlow
Mental Model
Relay frame ──► LocalCache.insertOrUpdateNote() ──► LocalCacheFlow emits change
│
▼
Account state objects pin the relevant addressable notes
│
▼
State-object `.flow` updates (kind3FollowList, nip65RelayList, muteList, …)
│
▼
ViewModels collect
│
▼
Composables render`LocalCache` is the event store. `Account` is the *derived* per-user view (follow list, relays, mutes, emojis, bookmarks, etc.). UI listens to the `.flow` of `Account`'s state objects, not directly to `LocalCache`, except for note-level rendering.
Key Files
`Account.kt` (singleton-per-session)
- `class Account(...)` — holds 50+ **state objects**, one per feature, each wired to a specific Nostr kind:
- `kind3FollowList = Kind3FollowListState(...)` ← NIP-02 ContactList (kind 3)
- `nip65RelayList = Nip65RelayListState(...)` ← NIP-65 RelayList (kind 10002), plus siblings `dmRelayList`, `searchRelayList`, `blockedRelayList`, `trustedRelayList`, `proxyRelayList`, `broadcastRelayList`, `indexerRelayList`, …
- `muteList = MuteListState(...)` ← NIP-51 MuteList (kind 10000)
- `bookmarkState = BookmarkListState(...)` ← NIP-51 Bookmarks (kind 10003), plus `labeledBookmarkLists`, `pinState`, `interestSets`, `peopleLists`, `followLists`, `hashtagList`, `geohashList`, `communityList`, `emoji`, `blossomServers`, …
- Derived/merged views: `hiddenUsers`, `allFollows`, `homeRelays`, `outboxRelays`, `dmRelays`, `notificationRelays`, `trustedRelays`, and the `live*FollowListsPerRelay` outbox loaders.
- **The pattern:** each `XState` class pins its addressable note via `cache.getOrCreateAddressableNote(address)` (a long-term reference so GC/eviction can't drop it), exposes `val flow: StateFlow<…>` derived from the note's metadata flow (decrypted through a per-feature `DecryptionCache`, with backup fallback from `AccountSettings`, `stateIn(scope, Eagerly, …)`), and offers suspend mutation helpers (e.g. `MuteListState.hideUser(pubkey)`) that build the updated signed event. Consumers read `account.muteList.flow`, never a raw `MutableStateFlow` on `Account`.
- Encrypted lists pair the state object with a `DecryptionCache` sibling (`muteListDecryptionCache`, `peopleListDecryptionCache`, …) so NIP-44 decryption results are cached per event.
- UI reads via `collectAsStateWithLifecycle` on Android and `collectAsState` on Desktop.
- Sibling files per feature live alongside: `AccountSettings.kt`, `AccountSyncedSettings.kt`, plus per-NIP state classes under `model/nip02FollowLists/`, `model/nip51Lists/`, `model/nip65RelayList/`, etc.
`LocalCache.kt`
- `object LocalCache : ILocalCache, ICacheProvider` — the singleton event store.
- Primary structures (all `LargeCache` — see `nostr-expert/references/large-cache.md`):
- `notes: LargeCache<HexKey, Note>` — every seen event (regular + addressable + replaceable) keyed by id or d-address.
- `users: LargeCache<HexKey, User>` — every seen pubkey, lazily populated.
- `addressables: LargeCache<Address, Note>` — secondary index for `kind:pubkey:d-tag` lookups.
- `channels`, `deletionIndex`, `hashtagIndex`, …
- `LocalCacheFlow` emits coarse-grained "something changed, recheck" signals. Fine-grained reactivity lives in `Account`'s per-kind StateFlows.
- Eviction is driven by `MemoryTrimmingService` (android service) under pressure.
Model classes
- `User.kt` — mutable profile holder. Contains metadata, follow/follower counts, relay lists, liveset of notes authored.
- `Note.kt` — mutable note holder. Contains the underlying `Event`, replies, reactions, zaps. Mutation via `addReply`, `addReaction`, `addZap`, emitted on `Note.flowSet` flows.
- `Constants.kt` — DEFAULT_RELAYS, magic kinds/limits not covered by quartz.
Adding a New Account-Scoped Setting
Typical recipe:
1. If the setting is persisted as a Nostr event, pick the right kind (e.g. NIP-51 list, NIP-78 app-specific data, NIP-65 relay list). 2. Add a model folder under `amethyst/.../model/nipXX…/` with an `XState` class modeled on an existing one (`MuteListState` for an encrypted list, `BookmarkListState` for a plain one):
- Pin the addressable note: `val xNote = cache.getOrCreat
Read more
name: account-state description: Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`, `muteList`, `bookmarkState`, each exposing a `.flow` StateFlow), `LocalCache` (the object-level event store backed by `LargeCache`), `User`/`Note` model classes, or any ViewModel that reads user-specific state. Covers how account events cascade from relay arrival to UI state, how to add a new account-scoped setting, and when to read from `LocalCache` vs subscribe to a StateFlow.
Account & Local Cache State
The backbone of Amethyst's client state: one `Account` per signed-in user, plus the singleton `LocalCache` that holds every `Note` and `User` the client has seen.
When to Use This Skill
- Working on `amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt`
- Working on `amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt`
- Adding a new account-scoped setting (mutes, bookmarks, custom relay lists, private lists)
- Reading/writing user metadata (`User`) or note state (`Note`)
- Deciding whether to query `LocalCache` vs subscribe to an `Account` StateFlow
Mental Model
Relay frame ──► LocalCache.insertOrUpdateNote() ──► LocalCacheFlow emits change
│
▼
Account state objects pin the relevant addressable notes
│
▼
State-object `.flow` updates (kind3FollowList, nip65RelayList, muteList, …)
│
▼
ViewModels collect
│
▼
Composables render`LocalCache` is the event store. `Account` is the *derived* per-user view (follow list, relays, mutes, emojis, bookmarks, etc.). UI listens to the `.flow` of `Account`'s state objects, not directly to `LocalCache`, except for note-level rendering.
Key Files
`Account.kt` (singleton-per-session)
- `class Account(...)` — holds 50+ **state objects**, one per feature, each wired to a specific Nostr kind:
- `kind3FollowList = Kind3FollowListState(...)` ← NIP-02 ContactList (kind 3)
- `nip65RelayList = Nip65RelayListState(...)` ← NIP-65 RelayList (kind 10002), plus siblings `dmRelayList`, `searchRelayList`, `blockedRelayList`, `trustedRelayList`, `proxyRelayList`, `broadcastRelayList`, `indexerRelayList`, …
- `muteList = MuteListState(...)` ← NIP-51 MuteList (kind 10000)
- `bookmarkState = BookmarkListState(...)` ← NIP-51 Bookmarks (kind 10003), plus `labeledBookmarkLists`, `pinState`, `interestSets`, `peopleLists`, `followLists`, `hashtagList`, `geohashList`, `communityList`, `emoji`, `blossomServers`, …
- Derived/merged views: `hiddenUsers`, `allFollows`, `homeRelays`, `outboxRelays`, `dmRelays`, `notificationRelays`, `trustedRelays`, and the `live*FollowListsPerRelay` outbox loaders.
- **The pattern:** each `XState` class pins its addressable note via `cache.getOrCreateAddressableNote(address)` (a long-term reference so GC/eviction can't drop it), exposes `val flow: StateFlow<…>` derived from the note's metadata flow (decrypted through a per-feature `DecryptionCache`, with backup fallback from `AccountSettings`, `stateIn(scope, Eagerly, …)`), and offers suspend mutation helpers (e.g. `MuteListState.hideUser(pubkey)`) that build the updated signed event. Consumers read `account.muteList.flow`, never a raw `MutableStateFlow` on `Account`.
- Encrypted lists pair the state object with a `DecryptionCache` sibling (`muteListDecryptionCache`, `peopleListDecryptionCache`, …) so NIP-44 decryption results are cached per event.
- UI reads via `collectAsStateWithLifecycle` on Android and `collectAsState` on Desktop.
- Sibling files per feature live alongside: `AccountSettings.kt`, `AccountSyncedSettings.kt`, plus per-NIP state classes under `model/nip02FollowLists/`, `model/nip51Lists/`, `model/nip65RelayList/`, etc.
`LocalCache.kt`
- `object LocalCache : ILocalCache, ICacheProvider` — the singleton event store.
- Primary structures (all `LargeCache` — see `nostr-expert/references/large-cache.md`):
- `notes: LargeCache<HexKey, Note>` — every seen event (regular + addressable + replaceable) keyed by id or d-address.
- `users: LargeCache<HexKey, User>` — every seen pubkey, lazily populated.
- `addressables: LargeCache<Address, Note>` — secondary index for `kind:pubkey:d-tag` lookups.
- `channels`, `deletionIndex`, `hashtagIndex`, …
- `LocalCacheFlow` emits coarse-grained "something changed, recheck" signals. Fine-grained reactivity lives in `Account`'s per-kind StateFlows.
- Eviction is driven by `MemoryTrimmingService` (android service) under pressure.
Model classes
- `User.kt` — mutable profile holder. Contains metadata, follow/follower counts, relay lists, liveset of notes authored.
- `Note.kt` — mutable note holder. Contains the underlying `Event`, replies, reactions, zaps. Mutation via `addReply`, `addReaction`, `addZap`, emitted on `Note.flowSet` flows.
- `Constants.kt` — DEFAULT_RELAYS, magic kinds/limits not covered by quartz.
Adding a New Account-Scoped Setting
Typical recipe:
1. If the setting is persisted as a Nostr event, pick the right kind (e.g. NIP-51 list, NIP-78 app-specific data, NIP-65 relay list). 2. Add a model folder under `amethyst/.../model/nipXX…/` with an `XState` class modeled on an existing one (`MuteListState` for an encrypted list, `BookmarkListState` for a plain one):
- Pin the addressable note: `val xNote = cache.getOrCreat
Other skills on amethyst.
- /amy-expert
Patterns for extending `amy`, the Amethyst CLI in `cli/`. Use when adding an `amy <verb>` command, touching files under `cli/src/main/kotlin/…/cli/`, wiring a new subcommand into `Main.kt`, writing an interop test script that drives Amy, or extracting logic out of `amethyst/`
Open skill - /android-expert
Android platform patterns for the `amethyst/` module. Use when working with (1) Android navigation (Navigation Compose, type-safe routes, bottom nav), (2) runtime permissions (camera, notifications, biometrics), (3) platform APIs (Intent, Context, Activity, ContentResolver), (4)
Open skill - /auth-signers
Signer abstraction patterns in Amethyst. Use when working with event signing, choosing between a local keypair (`NostrSignerInternal`), a remote NIP-46 bunker signer (`NostrSignerRemote`), or a NIP-55 Android external-app signer (`NostrSignerExternal`). Covers the abstract
Open skill - /compose-expert
Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector
Open skill - /compose-modifier-and-layout-style
Use when writing or reviewing Jetpack Compose layout APIs, modifier parameters, modifier chain construction, hardcoded root layout decisions, or layout wrappers around a single conditional. Technique-layer skill — complements the codebase-specific compose-expert.
Open skill - /compose-recomposition-performance
Use when investigating Jetpack Compose recomposition performance, skippable/restartable composables, composables.txt or compiler reports, Layout Inspector recomposition counts, or frame-rate State reads in composition vs layout/draw, and it is not yet clear whether the cause is
Open skill

