/kotlin-expert
Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when
$ npx -y skills add vitorpamplona/amethyst --skill kotlin-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
/kotlin-expert
Context preview
The summary Claude sees to decide when to auto-load this skill.
Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when
SKILL.md
kotlin-expert.SKILL.mdname: kotlin-expert
description: Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.
Kotlin Expert
Advanced Kotlin patterns for AmethystMultiplatform. Covers Flow state management, sealed hierarchies, immutability, DSL builders, and inline functions with real codebase examples.
Mental Model
**Kotlin in Amethyst:**
State Management (Hot Flows)
├── StateFlow<T> # Single value, always has value, replays to new subscribers
├── SharedFlow<T> # Event stream, configurable replay, multiple subscribers
└── MutableStateFlow<T> # Private mutable, public via .asStateFlow()
Type Safety (Sealed Hierarchies)
├── sealed class # State variants with data (AccountState.LoggedIn/LoggedOut)
└── sealed interface # Generic result types (SignerResult<T>)
Compose Performance (@Immutable)
├── @Immutable # 173+ event classes - prevents recomposition
└── data class # Structural equality, copy(), immutable by convention
DSL Patterns
├── Builder classes # Fluent APIs (TagArrayBuilder)
├── Lambda receivers # inline fun tagArray { ... }
└── Method chaining # return this
Performance
├── inline fun # Eliminate lambda overhead
├── reified type params # Runtime type info (OptimizedJsonMapper)
└── value class # Zero-cost wrappers (NOT USED yet in Amethyst)**Delegation:**
- **kotlin-coroutines agent**: Deep async (structured concurrency, channels, operators)
- **kotlin-multiplatform skill**: expect/actual, source sets
- **This skill**: Amethyst Kotlin idioms, state patterns, type safety
---
1. Flow State Management
StateFlow: State that Changes
**Mental model:** StateFlow is a "hot" observable state holder. Always has a value, new collectors immediately get current state.
**Amethyst pattern:**
// AccountManager.kt:48-50
class AccountManager {
private val _accountState = MutableStateFlow<AccountState>(AccountState.LoggedOut)
val accountState: StateFlow<AccountState> = _accountState.asStateFlow()
fun login(key: String) {
_accountState.value = AccountState.LoggedIn(...)
}
}**Key principles:** 1. **Private mutable, public immutable**: `_accountState` (MutableStateFlow) private, `accountState` (StateFlow) public 2. **Always has value**: Initial value required (`LoggedOut`) 3. **Single value**: Replays ONE most recent value to new subscribers 4. **Hot**: Stays in memory, all collectors share same instance
**See:** AccountManager.kt:48-50, RelayConnectionManager.kt:49-52
SharedFlow: Event Streams
**Mental model:** SharedFlow is a "hot" broadcast stream for events. Configurable replay buffer, doesn't require initial value.
**Amethyst pattern:**
// RelayConnectionManager.kt:52-53
val connectedRelays: StateFlow<Set<NormalizedRelayUrl>> = client.connectedRelaysFlow()
val availableRelays: StateFlow<Set<NormalizedRelayUrl>> = client.availableRelaysFlow()
**When to use StateFlow vs SharedFlow:**
| Scenario | Use StateFlow | Use SharedFlow | |----------|---------------|----------------| | **UI state** | ✅ Current screen data, login status | ❌ | | **One-time events** | ❌ | ✅ Navigation, snackbars, toasts | | **Always has value** | ✅ | ❌ Optional | | **Replay count** | 1 (latest only) | Configurable (0, 1, n) | | **Backpressure** | Conflates (drops old) | Configurable buffer |
**Best practice:**
// State: Use StateFlow
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// Events: Use SharedFlow
private val _navigationEvents = MutableSharedFlow<NavEvent>(replay = 0)
val navigationEvents: SharedFlow<NavEvent> = _navigationEvents.asSharedFlow()
Flow Anti-Patterns
❌ **Exposing mutable state:**
val accountState: MutableStateFlow<AccountState> // BAD: Can be mutated externally
✅ **Expose immutable:**
val accountState: StateFlow<AccountState> = _accountState.asStateFlow() // GOOD
---
❌ **SharedFlow for state:**
val loginState = MutableSharedFlow<LoginState>() // BAD: State might get lost
✅ **StateFlow for state:**
val loginState = MutableStateFlow(LoginState.LoggedOut) // GOOD: Always has value
**See:** `references/flow-patterns.md` for comprehensive examples.
---
2. Sealed Hierarchies
Sealed Classes: State Variants
**Mental model:** Sealed classes represent a closed set of variants that share common data/behavior.
**Amethyst pattern:**
// AccountManager.kt:36-46
sealed class AccountState {
data object LoggedOut : AccountState()
data class LoggedIn(
val signer: NostrSigner,
val pubKeyHex: String,
val npub: String,
val nsec: String?,
val isReadOnly: Boolean
) : AccountState()
}
// Usage
when (state) {
is AccountState.LoggedOut -> showLogin()
is AccountState.LoggedIn -> showFeed(state.pubKeyHex)
} // Exhaustive - compiler enforces all cases**Key principles:** 1. **Closed hierarchy**: All subclasses known at compile-time 2. **Exhaustive when**: Compiler ensures all cases handled 3. **Shared data**: Sealed class can hold common properties 4. **Single inheritance**: Subclass can't extend another class
**When to use:**
- Modeling UI states (Loading, S
Read more
name: kotlin-expert description: Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.
Kotlin Expert
Advanced Kotlin patterns for AmethystMultiplatform. Covers Flow state management, sealed hierarchies, immutability, DSL builders, and inline functions with real codebase examples.
Mental Model
**Kotlin in Amethyst:**
State Management (Hot Flows)
├── StateFlow<T> # Single value, always has value, replays to new subscribers
├── SharedFlow<T> # Event stream, configurable replay, multiple subscribers
└── MutableStateFlow<T> # Private mutable, public via .asStateFlow()
Type Safety (Sealed Hierarchies)
├── sealed class # State variants with data (AccountState.LoggedIn/LoggedOut)
└── sealed interface # Generic result types (SignerResult<T>)
Compose Performance (@Immutable)
├── @Immutable # 173+ event classes - prevents recomposition
└── data class # Structural equality, copy(), immutable by convention
DSL Patterns
├── Builder classes # Fluent APIs (TagArrayBuilder)
├── Lambda receivers # inline fun tagArray { ... }
└── Method chaining # return this
Performance
├── inline fun # Eliminate lambda overhead
├── reified type params # Runtime type info (OptimizedJsonMapper)
└── value class # Zero-cost wrappers (NOT USED yet in Amethyst)**Delegation:**
- **kotlin-coroutines agent**: Deep async (structured concurrency, channels, operators)
- **kotlin-multiplatform skill**: expect/actual, source sets
- **This skill**: Amethyst Kotlin idioms, state patterns, type safety
---
1. Flow State Management
StateFlow: State that Changes
**Mental model:** StateFlow is a "hot" observable state holder. Always has a value, new collectors immediately get current state.
**Amethyst pattern:**
// AccountManager.kt:48-50
class AccountManager {
private val _accountState = MutableStateFlow<AccountState>(AccountState.LoggedOut)
val accountState: StateFlow<AccountState> = _accountState.asStateFlow()
fun login(key: String) {
_accountState.value = AccountState.LoggedIn(...)
}
}**Key principles:** 1. **Private mutable, public immutable**: `_accountState` (MutableStateFlow) private, `accountState` (StateFlow) public 2. **Always has value**: Initial value required (`LoggedOut`) 3. **Single value**: Replays ONE most recent value to new subscribers 4. **Hot**: Stays in memory, all collectors share same instance
**See:** AccountManager.kt:48-50, RelayConnectionManager.kt:49-52
SharedFlow: Event Streams
**Mental model:** SharedFlow is a "hot" broadcast stream for events. Configurable replay buffer, doesn't require initial value.
**Amethyst pattern:**
// RelayConnectionManager.kt:52-53 val connectedRelays: StateFlow<Set<NormalizedRelayUrl>> = client.connectedRelaysFlow() val availableRelays: StateFlow<Set<NormalizedRelayUrl>> = client.availableRelaysFlow()
**When to use StateFlow vs SharedFlow:**
| Scenario | Use StateFlow | Use SharedFlow | |----------|---------------|----------------| | **UI state** | ✅ Current screen data, login status | ❌ | | **One-time events** | ❌ | ✅ Navigation, snackbars, toasts | | **Always has value** | ✅ | ❌ Optional | | **Replay count** | 1 (latest only) | Configurable (0, 1, n) | | **Backpressure** | Conflates (drops old) | Configurable buffer |
**Best practice:**
// State: Use StateFlow private val _uiState = MutableStateFlow(UiState.Loading) val uiState: StateFlow<UiState> = _uiState.asStateFlow() // Events: Use SharedFlow private val _navigationEvents = MutableSharedFlow<NavEvent>(replay = 0) val navigationEvents: SharedFlow<NavEvent> = _navigationEvents.asSharedFlow()
Flow Anti-Patterns
❌ **Exposing mutable state:**
val accountState: MutableStateFlow<AccountState> // BAD: Can be mutated externally
✅ **Expose immutable:**
val accountState: StateFlow<AccountState> = _accountState.asStateFlow() // GOOD
---
❌ **SharedFlow for state:**
val loginState = MutableSharedFlow<LoginState>() // BAD: State might get lost
✅ **StateFlow for state:**
val loginState = MutableStateFlow(LoginState.LoggedOut) // GOOD: Always has value
**See:** `references/flow-patterns.md` for comprehensive examples.
---
2. Sealed Hierarchies
Sealed Classes: State Variants
**Mental model:** Sealed classes represent a closed set of variants that share common data/behavior.
**Amethyst pattern:**
// AccountManager.kt:36-46
sealed class AccountState {
data object LoggedOut : AccountState()
data class LoggedIn(
val signer: NostrSigner,
val pubKeyHex: String,
val npub: String,
val nsec: String?,
val isReadOnly: Boolean
) : AccountState()
}
// Usage
when (state) {
is AccountState.LoggedOut -> showLogin()
is AccountState.LoggedIn -> showFeed(state.pubKeyHex)
} // Exhaustive - compiler enforces all cases**Key principles:** 1. **Closed hierarchy**: All subclasses known at compile-time 2. **Exhaustive when**: Compiler ensures all cases handled 3. **Shared data**: Sealed class can hold common properties 4. **Single inheritance**: Subclass can't extend another class
**When to use:**
- Modeling UI states (Loading, S
Other 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 - /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

