amy-expert
amy CLI in cli/: adding an `amy <verb>` command, Main.kt dispatch, Output.emit/--json contract, exit codes 0/1/2/124, ~/.amy…
Account, LocalCache, User/Note and feeds in commons/model + commons/feeds: per-user XState objects (.flow), FeedFilter/AdditiveFeedFilter, FeedViewModel. Use when adding an account setting or list, a feed, or choosing LocalCache vs account flows.
Claude Code
$ npx -y skills add vitorpamplona/amethyst --skill account-state --agent claude-code
Codex
$ npx skills add vitorpamplona/amethyst --skill account-state -a codex -y
Cursor
$ npx skills add vitorpamplona/amethyst --skill account-state -a cursor -y
Gemini CLI
$ npx skills add vitorpamplona/amethyst --skill account-state -a gemini-cli -y
GitHub Copilot
$ npx skills add vitorpamplona/amethyst --skill account-state -a github-copilot -y
OpenCode
$ npx skills add vitorpamplona/amethyst --skill account-state -a opencode -y
Windsurf
$ npx skills add vitorpamplona/amethyst --skill account-state -a windsurf -y
name: account-state description: Account, LocalCache, User/Note and feeds in commons/model + commons/feeds: per-user XState objects (.flow), FeedFilter/AdditiveFeedFilter, FeedViewModel. Use when adding an account setting or list, a feed, or choosing LocalCache vs account flows.
The client state backbone, all headless in `commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/` (abbreviated `commons/…/` below):
seen. All logic is in `model/cache/EventCache.kt` (an open class so tests can build their own); Android and Desktop both use the `LocalCache` singleton (`AppModules.kt`, `DesktopAppModules.kt`).
(`kind3FollowList`, `nip65RelayList`, `muteList`, `bookmarkState`, …), each exposing `.flow`.
`FeedContentState` / `FeedViewModel` keeps the result live.
relay EVENT ─► EventCache.justConsume ─► Note/User updated, indexes, live.newNote(...)
│ │
▼ ▼
XState pins its addressable note, LocalCacheFlow.newEventBundles
note.flow().metadata → XState.flow → FeedViewModel → FeedFilter.applyFilter
│ │
└────────► ViewModels / composables ◄┘Pattern, copied from `model/nip51Lists/muteList/MuteListState.kt`:
`LocalCache`'s `notes` / `addressables` are `LargeSoftCache` (weak references), so the state object's field is what keeps its note from being collected.
through a per-feature `…DecryptionCache` such as `MuteListDecryptionCache`, held next to it in `Account` as `muteListDecryptionCache`) → fall back to the `AccountSettings` backup (`settings.backupMuteList`) when the event is absent → `stateIn(scope, SharingStarted.Eagerly, …)`.
`MuteListState.hideUser(pubkey)` returns a `MuteListEvent`; `Account.hideUser` does `sendMyPublicAndPrivateOutbox(muteList.hideUser(pubkeyHex))`. A state change you never send is invisible to every other client and is lost when the next relay copy arrives.
Full catalog of properties, classes, kinds and packages: `references/account-state-flow.md`.
1. Persisted as a Nostr event? Pick the kind (NIP-51 list, NIP-78 app data, a relay list) and make sure quartz has the event class (see quartz's `nostr-expert` skill; a new one is a quartz change, built here with `-PquartzDir=../quartz`). 2. Add `commons/…/model/nipXX…/XState.kt` modeled on `MuteListState` (encrypted) or `nip51Lists/BookmarkListState.kt` (plain): pin, flow, suspend mutation helpers that build the event with the quartz class and sign it. 3. Instantiate it as a `val` in `Account.kt` (plus its `DecryptionCache` if private) and add an `Account` method that sends the returned event (`sendMyPublicAndPrivateOutbox`, `sendAutomatic`, …). If the list must survive relay loss, add a `backupX` field to `AccountSettings`. 4. Make sure `EventCache.justConsumeInnerInner` lists the event class — a kind that is not listed falls into the `else` branch and is **rejected**, so the state object never sees it, even though `EventFactory` parses it. Replaceable kinds without custom logic go in the `consumeBaseReplaceable` group. 5. Relay subscription: `relay-client`. UI: settings screens are shared in `commonsUI/…/commons/ui/screen/loggedIn/settings/`; strings follow the AGENTS.md *Strings* rules.
| You render… | Read | |---|---| | one note / user you have an id for | `LocalCache.getOrCreateNote(id)` / `getOrCreateUser(pubkey)`, then `note.flow().metadata` (or `replies`, `reactions`, `zaps`, …) | | "my follows / mutes / relays / bookmarks" | `account.kind3FollowList.flow`, `account.muteList.flow`, `account.nip65RelayList.flow`, `account.hiddenUsers.flow`, … | | a list of notes | a `FeedFilter` + `FeedContentState` / `FeedViewModel` — `references/feeds.md` | | ad-hoc filter results | `LocalCache.observeNotes(filter: Filter): Flow<List<Note>>` |
Never scan `LocalCache.notes` inside a composable.
Bases are in `commons/…/feeds/`: `IFeedFilter` / `FeedFilter<T>` (`feed()`, `feedKey()`, `limit()` default 500, `showHiddenKey()`), `AdditiveFeedFilter<T>` (adds `applyFilter` + `sort` for incremental merges), `AdditiveComplexFeedFilter<T, U>` (incoming type differs from row type, e.g. `home/dal/HomeLiveFilter.kt`), `ChangesFlowFilter<T>`, `FilterByListParams`, `DefaultFeedOrder` / `sortedByDefaultFeedOrder()`, `FeedContentState`, `FeedState`. Concrete filters are **shared**, in `commons/…/<feature>/dal/` (`home/dal/`, `hashtag/dal/`, `notifications/dal/`, ~55 of them). The account's main-tab feeds are long-lived `FeedContentState`s in `viewmodels/AccountFeedContentStates.kt` (owned by `BaseAccountViewModel.feedStates`); per-screen feeds use a `FeedViewModel` subclass. Recipe, real code and traps: `references/feeds.md`.
derives its flows from the same cache. Filters that depend on the account must put the account pubkey in `feedKey()`.
amy CLI in cli/: adding an `amy <verb>` command, Main.kt dispatch, Output.emit/--json contract, exit codes 0/1/2/124, ~/.amy…
Android shim in `amethyst/`: MainActivity, AndroidManifest/intent filters, :napplet process, play/fdroid flavors, R8 +…
NostrSigner in Amethyst: NostrSignerInternal (local key), NostrSignerRemote (NIP-46 bunker), NostrSignerExternal (NIP-55 Android…
Shared Compose UI in `commonsUI/`: screens, components, the nav shell (`ScreenLayoutSpec`, `AppShellLayout`, Navigation 3 back…
Desktop JVM shim in desktopApp/: AmethystDesktop window, DesktopMenuBar, platform ports, OS keyring; :desktopApp:run, packaging.…
Translation audits of `strings.xml` locales in `commonsUI/.../composeResources` and `amethyst/src/main/res`: missing or…
Decompile Android APK, XAPK, JAR, and AAR files using jadx or Fernflower/Vineflower. Reverse engineer Android apps, extract HTTP…