Skip to content
Mobile
Skill

/compose-expert

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.

From plugin
amethyst
1.6k12 skills

Claude Code

$ npx -y skills add vitorpamplona/amethyst --skill compose-expert --agent claude-code

SKILL.md

compose-expert.SKILL.md
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.

compose-expert

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`.

Where things live

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.

The shell and window tiers

One shell for every window, chosen by size, not by platform (`AGENTS.md` "Navigation Shell"):

  • `AmethystApp` (`ui/app/AmethystApp.kt`) is the root both front ends call. Android's

`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`, ...

  • `ScreenLayoutSpec.navigationStyle` is `BOTTOM_BAR`, `NAV_RAIL` or `PERMANENT_DRAWER`;

`hasRoomForNotificationPanel` docks the notification panel. `isLargeScreen` is "not the bottom bar". Read it with `LocalScreenLayout.current`, never with a platform check.

  • `AppShellLayout` (`ui/navigation/shell/`) lays out the bar / rail / drawer;

`ui/navigation/bottombars/` holds `AppBottomBar` and `AppNavigationRail`; `ui/navigation/deck/DeckArea.kt` adds the opt-in deck columns on windows of 1,200 dp+.

Navigation 3

The app owns its back stack as snapshot state; `NavDisplay` only renders it. No `NavController`, no graph. Full rules in `references/navigation.md`.

  • Routes: `@Serializable sealed class Route` in

`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.

  • `NavBackStacks` (same package) holds the window's `NavModel`: `StackModel` (`stack`, never

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.

  • `Nav` (`ui/navigation/navs/Nav.kt`) implements `INav`: `nav`, `navDrawer`, `newStack`,

`navBottomBar`, `popBack`, `popUpTo`, each after the keyboard settles (`ImeSettler`).

  • Register a screen once with a builder from `ui/navigation/host/NavDestinations.kt`. Shared

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.

Composable conventions that differ from the default

  • **Strings come from `Res`**, read with `stringRes(Res.string.x)`; no literal user-visible text

in a shared composable, no Android `R.string`. Default label parameters are `StringResource` (see `AddButton` in `ui/components/ActionButtons.kt`).

  • **UI-state collections are `kotlinx.collections.immutable`** (`ImmutableList`,

`ImmutableMap`, `persistentListOf`), an `api` dependency of `commonsUI`. The parser output (`RichTextViewerState`, `ParagraphState`) and the platform ports already use them; pass those, not `List`, int

Read more
@vitorpamplona
Ships withamethyst

Nostr client for Android

Stars on GitHub

Momentum

stars in total
1.6k
Star history of vitorpamplona/amethyst on GitHub, a chart by star-history.com

Other skills on amethyst.

amy-expert

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…

android-expert

android-expert

Android shim in `amethyst/`: MainActivity, AndroidManifest/intent filters, :napplet process, play/fdroid flavors, R8 +…

auth-signers

auth-signers

NostrSigner in Amethyst: NostrSignerInternal (local key), NostrSignerRemote (NIP-46 bunker), NostrSignerExternal (NIP-55 Android…

desktop-expert

desktop-expert

Desktop JVM shim in desktopApp/: AmethystDesktop window, DesktopMenuBar, platform ports, OS keyring; :desktopApp:run, packaging.…

More skills from other plugins.

deep-code-review

deep-code-review

In-depth design-focused code review - understands codebase context before evaluating PR changes, posts structured feedback to…

expo1d53k
@expo@expoInstall Skill
modlens

modlens

Plug-in vision for text-only models. Hard rule: when a file path or URL with an image extension (.png, .jpg, .jpeg, .webp, .gif,…

modlens2w4.6k
@liustack@liustackInstall Skill
appllama-app-design-skill

appllama-app-design-sk…

Build native-feeling, benchmark-quality mobile app screens (Expo / React Native). Use when designing or implementing any mobile…

appllama2mo3.9k
@appllama@appllamaInstall Skill

Questions about compose-expert.

Which coding agents can use compose-expert?
compose-expert installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.