account-state
Account, LocalCache, User/Note and feeds in commons/model + commons/feeds: per-user XState objects (.flow),…
Shared Compose UI in `commonsUI/`: screens, components, the nav shell (`ScreenLayoutSpec`, `AppShellLayout`, Navigation 3 back stacks), component catalog, `RichTextParser`, Compose API/perf review. Use when writing or reviewing shared UI.
Claude Code
$ npx -y skills add vitorpamplona/amethyst --skill compose-expert --agent claude-code
Codex
$ npx skills add vitorpamplona/amethyst --skill compose-expert -a codex -y
Cursor
$ npx skills add vitorpamplona/amethyst --skill compose-expert -a cursor -y
Gemini CLI
$ npx skills add vitorpamplona/amethyst --skill compose-expert -a gemini-cli -y
GitHub Copilot
$ npx skills add vitorpamplona/amethyst --skill compose-expert -a github-copilot -y
OpenCode
$ npx skills add vitorpamplona/amethyst --skill compose-expert -a opencode -y
Windsurf
$ npx skills add vitorpamplona/amethyst --skill compose-expert -a windsurf -y
name: compose-expert description: Shared Compose UI in `commonsUI/`: screens, components, the nav shell (`ScreenLayoutSpec`, `AppShellLayout`, Navigation 3 back stacks), component catalog, `RichTextParser`, Compose API/perf review. Use when writing or reviewing shared UI.
Where shared Compose UI lives in Amethyst and the house rules for it. What goes in `commons` vs `commonsUI` vs a platform shim is in `AGENTS.md` ("Sharing Philosophy", "Feature Workflow") and `commonsUI/ARCHITECTURE.md`; this skill does not repeat it.
Delegate: Activity / manifest / flavors -> `android-expert`; Window, tray, menu bar -> `desktop-expert`; source-set placement, expect/actual, extracting an Android composable -> `kotlin-multiplatform`; feeds and `Account` state -> `account-state`; Flow and coroutine scopes -> `kotlin-coroutines`.
All paths below are under `commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/`.
| What | Where | |------|-------| | Screens | `ui/screen/loggedIn/<feature>/` (and `ui/screen/` for shared feed views) | | Reusable components | `ui/components/` (catalog: `references/shared-composables-catalog.md`) | | Feed scaffolding | `ui/feeds/` (`RenderFeedContentState`, `RefresheableBox`, `FeedEmpty`, `LoadingFeed`) | | Note rendering | `ui/note/` (`NoteCompose`), rich text in `ui/richtext/` + `ui/components/RichTextViewer.kt` | | Theme | `ui/theme/` (`AmethystColorScheme.kt` palettes + `ColorScheme.*` tokens, `ColorScheme.isLight`, `Shape.kt`, `Type.kt`) | | Window tiers | `ui/layouts/ScreenLayout.kt` (`ScreenLayoutSpec`, `LocalScreenLayout`, `FeedContentMaxWidth`) | | Shell + nav | `ui/app/AmethystApp.kt`, `ui/app/AppRoot.kt`, `ui/navigation/` (see below) | | Platform ports for UI | `ui/platform/AppPlatform.kt` (`LocalAppPlatform`), `ui/components/PlatformPermission.kt` | | Icons | `icons/` (Amethyst glyph font) and `icons/symbols/` (`MaterialSymbols`, `AmethystIcons`) | | Robohash avatars | `robohash/` (`CachedRobohash`, `RobohashAssembler`, `parts/`) | | Strings | `commonsUI/src/commonMain/composeResources/values/strings.xml` -> `Res.string.*` (rules: `AGENTS.md` "Strings") |
The Android app's `AmethystTheme` wrapper is still in `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt`; the palettes it applies are shared.
One shell for every window, chosen by size, not by platform (`AGENTS.md` "Navigation Shell"):
`MainActivity` passes `AndroidAppRoot`; the desktop window (`AmethystDesktop.kt`) passes a `DesktopAppRoot`. `AppRoot` (`ui/app/AppRoot.kt`) is the port each platform implements: `createAccountViewModel`, `rememberScreenLayoutSpec`, `registerDestinations`, `ShellOverlay`, `NavigationEffects`, ...
`hasRoomForNotificationPanel` docks the notification panel. `isLargeScreen` is "not the bottom bar". Read it with `LocalScreenLayout.current`, never with a platform check.
`ui/navigation/bottombars/` holds `AppBottomBar` and `AppNavigationRail`; `ui/navigation/deck/DeckArea.kt` adds the opt-in deck columns on windows of 1,200 dp+.
The app owns its back stack as snapshot state; `NavDisplay` only renders it. No `NavController`, no graph. Full rules in `references/navigation.md`.
`commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/navigation/Routes.kt`. The back stack is saved through kotlinx-serialization, so every route argument must be serializable.
empty, starts at `Route.Home`; `savedTabs`, the root of each bottom-bar tab you left), `TabsModel` or `WindowsModel`. Every navigation is a `NavRequest` the model reads its own way. A `NavStackEntry`'s `contentKey` is `"nav-$id"`: saved state and ViewModels key on it, so the same route opened twice is two screens.
`navBottomBar`, `popBack`, `popUpTo`, each after the keyboard settles (`ImeSettler`).
screens go in `NavDestinations.sharedDestinations` (`ui/navigation/host/SharedDestinations.kt`); Android-only ones in `androidDestinations` in `amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt`. The builder picks the motion and the reading-column cap:
// from SharedDestinations.kt
composableCapped<Route.Home> { HomeScreen(accountViewModel, nav) }
composable<Route.Message> { MessagesScreen(accountViewModel, nav) }
composableFromEnd<Route.Polls> { PollsScreen(accountViewModel, nav) }
composableFromEnd<Route.Browser>(capWidth = false) { BrowserScreen(accountViewModel, nav) }
composableFromBottomArgs<Route.AwardBadge> { AwardBadgeScreen(it.kind, it.pubKeyHex, it.dTag, accountViewModel, nav) }A route nobody registered fails the first time it is opened (the desktop installs an `unavailable` screen instead). Gate a menu item on `canOpen(route)` when a front end may lack it.
in a shared composable, no Android `R.string`. Default label parameters are `StringResource` (see `AddButton` in `ui/components/ActionButtons.kt`).
`ImmutableMap`, `persistentListOf`), an `api` dependency of `commonsUI`. The parser output (`RichTextViewerState`, `ParagraphState`) and the platform ports already use them; pass those, not `List`, int
Account, LocalCache, User/Note and feeds in commons/model + commons/feeds: per-user XState objects (.flow),…
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…
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…