/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)
$ npx -y skills add vitorpamplona/amethyst --skill android-expert --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
/android-expert
Context preview
The summary Claude sees to decide when to auto-load this skill.
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)
SKILL.md
android-expert.SKILL.mdname: android-expert
description: 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) Material3 theming and edge-to-edge UI, (5) AndroidManifest.xml and intent filters, (6) Proguard/R8 and APK optimization, (7) Android lifecycle (ViewModel, collectAsStateWithLifecycle), (8) Coil image loading. Delegates shared composables to compose-expert, build files to gradle-expert, and KMP structure to kotlin-multiplatform.
android-expert
Android platform expertise for Amethyst Multiplatform project. Covers Compose Navigation, Material3, permissions, lifecycle, and Android-specific patterns in KMP architecture.
When to Use
Auto-invoke when working with:
- Android navigation (Navigation Compose, routes, bottom nav)
- Runtime permissions (camera, notifications, biometric)
- Platform APIs (Intent, Context, Activity)
- Material3 theming and edge-to-edge UI
- Android build configuration (Proguard, APK optimization)
- AndroidManifest.xml configuration
- Android lifecycle (ViewModel, collectAsStateWithLifecycle)
Core Mental Model
**Single Activity Architecture + Compose Navigation**
MainActivity (Single Entry Point)
├── enableEdgeToEdge()
├── AmethystTheme { }
└── NavHost
├── Route.Home → HomeScreen
├── Route.Profile(id) → ProfileScreen
└── Route.Settings → SettingsScreen
Intent Filters (11+)
├── ACTION_MAIN (launcher)
├── ACTION_SEND (share)
├── ACTION_VIEW (deep links: nostr://, https://...)
└── NFC_ACTION_NDEF_DISCOVERED**Key Principles:** 1. **Type-Safe Navigation** - @Serializable routes, no strings 2. **Declarative Permissions** - Request contextually with Accompanist 3. **Edge-to-Edge + Insets** - Scaffold handles system bars 4. **ViewModel + Flow → State** - Survive config changes 5. **Platform Isolation** - Android code in `amethyst/` module or `androidMain/`
Architecture Overview
Module Structure
amethyst/ # Android app module
├── src/
│ ├── main/
│ │ ├── java/com/vitorpamplona/amethyst/
│ │ │ ├── ui/
│ │ │ │ ├── MainActivity.kt # Entry point
│ │ │ │ ├── navigation/
│ │ │ │ │ ├── AppNavigation.kt # NavHost
│ │ │ │ │ ├── routes/Routes.kt # @Serializable routes
│ │ │ │ │ └── bottombars/AppBottomBar.kt
│ │ │ │ ├── screen/ # 80+ screens
│ │ │ │ └── theme/Theme.kt # Material3 theme
│ │ │ └── Amethyst.kt # Application class
│ │ └── AndroidManifest.xml # Permissions, intent filters
│ └── androidMain/ # KMP Android source set
│ └── kotlin/ # Platform-specific code
└── build.gradle # Android config
1. Type-Safe Navigation
Pattern: @Serializable Routes
**Best Practice (Navigation 2.8.0+):**
// Routes.kt - Define all routes with type safety
@Serializable
sealed class Route {
@Serializable object Home : Route()
@Serializable object Search : Route()
@Serializable data class Profile(val pubkey: String) : Route()
@Serializable data class Note(val noteId: String) : Route()
@Serializable data class Thread(val noteId: String) : Route()
}
// AppNavigation.kt - NavHost setup
@Composable
fun AppNavigation(
navController: NavHostController,
accountViewModel: AccountViewModel
) {
NavHost(
navController = navController,
startDestination = Route.Home,
enterTransition = { fadeIn(animationSpec = tween(200)) },
exitTransition = { fadeOut(animationSpec = tween(200)) }
) {
composable<Route.Home> {
HomeScreen(accountViewModel, navController)
}
composable<Route.Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Route.Profile>()
ProfileScreen(profile.pubkey, accountViewModel, navController)
}
composable<Route.Note> { backStackEntry ->
val note = backStackEntry.toRoute<Route.Note>()
NoteScreen(note.noteId, accountViewModel, navController)
}
}
}Navigation Manager Pattern
**Amethyst Pattern (`Nav.kt`):**
class Nav(
val controller: NavHostController,
val drawerState: DrawerState,
val scope: CoroutineScope
) {
fun nav(route: Route) {
scope.launch {
controller.navigate(route)
drawerState.close()
}
}
fun newStack(route: Route) {
scope.launch {
controller.navigate(route) {
popUpTo(Route.Home) { inclusive = false }
}
drawerState.close()
}
}
fun popBack() {
controller.popBackStack()
}
}
// Usage in composables
@Composable
fun HomeScreen(nav: Nav) {
Button(onClick = { nav.nav(Route.Profile("npub1...")) }) {
Text("View Profile")
}
}Bottom Navigation
**Material3 Pattern:**
@Composable
fun AppBottomBar(
selectedRoute: Route,
nav: Nav
) {
NavigationBar {
BottomBarItem.entries.forEach { item ->
NavigationBarItem(
selected = selectedRoute::class == item.route::class,
onClick = { nav.nav(item.route) },
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) }
)
}
}
}
enum class BottomBarItem(val route: Route, val icon: ImageVector, val label: String) {
HOME(Route.Home, Icons.Default.Home, "Home"),
MESSAGES(Route.Messages, Icons.Default.Message, "Messages"),
NOTIFICATIONS(Route.Notifications, Icons.Default.Notifications, "Notifications"),
SEARCH(Route.Search, Icons.Default.SeaRead more
name: android-expert description: 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) Material3 theming and edge-to-edge UI, (5) AndroidManifest.xml and intent filters, (6) Proguard/R8 and APK optimization, (7) Android lifecycle (ViewModel, collectAsStateWithLifecycle), (8) Coil image loading. Delegates shared composables to compose-expert, build files to gradle-expert, and KMP structure to kotlin-multiplatform.
android-expert
Android platform expertise for Amethyst Multiplatform project. Covers Compose Navigation, Material3, permissions, lifecycle, and Android-specific patterns in KMP architecture.
When to Use
Auto-invoke when working with:
- Android navigation (Navigation Compose, routes, bottom nav)
- Runtime permissions (camera, notifications, biometric)
- Platform APIs (Intent, Context, Activity)
- Material3 theming and edge-to-edge UI
- Android build configuration (Proguard, APK optimization)
- AndroidManifest.xml configuration
- Android lifecycle (ViewModel, collectAsStateWithLifecycle)
Core Mental Model
**Single Activity Architecture + Compose Navigation**
MainActivity (Single Entry Point)
├── enableEdgeToEdge()
├── AmethystTheme { }
└── NavHost
├── Route.Home → HomeScreen
├── Route.Profile(id) → ProfileScreen
└── Route.Settings → SettingsScreen
Intent Filters (11+)
├── ACTION_MAIN (launcher)
├── ACTION_SEND (share)
├── ACTION_VIEW (deep links: nostr://, https://...)
└── NFC_ACTION_NDEF_DISCOVERED**Key Principles:** 1. **Type-Safe Navigation** - @Serializable routes, no strings 2. **Declarative Permissions** - Request contextually with Accompanist 3. **Edge-to-Edge + Insets** - Scaffold handles system bars 4. **ViewModel + Flow → State** - Survive config changes 5. **Platform Isolation** - Android code in `amethyst/` module or `androidMain/`
Architecture Overview
Module Structure
amethyst/ # Android app module ├── src/ │ ├── main/ │ │ ├── java/com/vitorpamplona/amethyst/ │ │ │ ├── ui/ │ │ │ │ ├── MainActivity.kt # Entry point │ │ │ │ ├── navigation/ │ │ │ │ │ ├── AppNavigation.kt # NavHost │ │ │ │ │ ├── routes/Routes.kt # @Serializable routes │ │ │ │ │ └── bottombars/AppBottomBar.kt │ │ │ │ ├── screen/ # 80+ screens │ │ │ │ └── theme/Theme.kt # Material3 theme │ │ │ └── Amethyst.kt # Application class │ │ └── AndroidManifest.xml # Permissions, intent filters │ └── androidMain/ # KMP Android source set │ └── kotlin/ # Platform-specific code └── build.gradle # Android config
1. Type-Safe Navigation
Pattern: @Serializable Routes
**Best Practice (Navigation 2.8.0+):**
// Routes.kt - Define all routes with type safety
@Serializable
sealed class Route {
@Serializable object Home : Route()
@Serializable object Search : Route()
@Serializable data class Profile(val pubkey: String) : Route()
@Serializable data class Note(val noteId: String) : Route()
@Serializable data class Thread(val noteId: String) : Route()
}
// AppNavigation.kt - NavHost setup
@Composable
fun AppNavigation(
navController: NavHostController,
accountViewModel: AccountViewModel
) {
NavHost(
navController = navController,
startDestination = Route.Home,
enterTransition = { fadeIn(animationSpec = tween(200)) },
exitTransition = { fadeOut(animationSpec = tween(200)) }
) {
composable<Route.Home> {
HomeScreen(accountViewModel, navController)
}
composable<Route.Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Route.Profile>()
ProfileScreen(profile.pubkey, accountViewModel, navController)
}
composable<Route.Note> { backStackEntry ->
val note = backStackEntry.toRoute<Route.Note>()
NoteScreen(note.noteId, accountViewModel, navController)
}
}
}Navigation Manager Pattern
**Amethyst Pattern (`Nav.kt`):**
class Nav(
val controller: NavHostController,
val drawerState: DrawerState,
val scope: CoroutineScope
) {
fun nav(route: Route) {
scope.launch {
controller.navigate(route)
drawerState.close()
}
}
fun newStack(route: Route) {
scope.launch {
controller.navigate(route) {
popUpTo(Route.Home) { inclusive = false }
}
drawerState.close()
}
}
fun popBack() {
controller.popBackStack()
}
}
// Usage in composables
@Composable
fun HomeScreen(nav: Nav) {
Button(onClick = { nav.nav(Route.Profile("npub1...")) }) {
Text("View Profile")
}
}Bottom Navigation
**Material3 Pattern:**
@Composable
fun AppBottomBar(
selectedRoute: Route,
nav: Nav
) {
NavigationBar {
BottomBarItem.entries.forEach { item ->
NavigationBarItem(
selected = selectedRoute::class == item.route::class,
onClick = { nav.nav(item.route) },
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) }
)
}
}
}
enum class BottomBarItem(val route: Route, val icon: ImageVector, val label: String) {
HOME(Route.Home, Icons.Default.Home, "Home"),
MESSAGES(Route.Messages, Icons.Default.Message, "Messages"),
NOTIFICATIONS(Route.Notifications, Icons.Default.Notifications, "Notifications"),
SEARCH(Route.Search, Icons.Default.SeaOther skills on amethyst.
- /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
Open skill - /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 - /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

