account-state
Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`,…
Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications. Technique-layer skill — complements the codebase-specific kotlin-expert.
$ npx -y skills add vitorpamplona/amethyst --skill kotlin-types-value-class --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/kotlin-types-value-classContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications. Technique-layer skill — complements the codebase-specific kotlin-expert.
name: kotlin-types-value-class description: Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications. Technique-layer skill — complements the codebase-specific kotlin-expert.
Prefer `@JvmInline value class` for single-field types that carry domain meaning. Data classes are for aggregating multiple fields. A value class gives you type safety (you can't mix up `UserId` and `String`) without the allocation overhead of a data class.
| Situation | Prefer | |---|---| | Single field + domain-meaningful (`UserId`, `EmailAddress`, `Percentage`) | `@JvmInline value class` | | Single field + no domain meaning (just grouping) | Type alias or keep the primitive | | Multiple fields | Data class | | Needs custom `equals`/`hashCode`/`toString` beyond the wrapped value | Data class (value classes delegate to the underlying type) | | Used as a generic type argument or nullable in hot paths | Data class or primitive (autoboxing cost) |
// GOOD: domain-meaningful single field @JvmInline value class UserId(val value: String) @JvmInline value class EmailAddress(val value: String) @JvmInline value class Percentage(val value: Float) // BAD: data class wrapping a single field data class UserId(val value: String) // unnecessary allocation data class EmailAddress(val value: String) // type safety without the overhead is available // BAD: value class with no domain meaning @JvmInline value class Wrapper(val value: String) // just use the String, or a type alias // BAD: value class needing custom equality @JvmInline value class CaseInsensitiveString(val value: String) // value class equals delegates to String equals, which IS case-sensitive // Use a data class if you need different equality semantics
`@JvmInline value class` is treated as `Stable` by the Compose compiler when its underlying type is stable (primitives, `String`, and other stable types). This means:
// Before: data class wrapping a single field data class UiState(val userId: String) // works, but allocates a wrapper object // After: value class is stable and zero-allocation at runtime @JvmInline value class UserId(val value: String) data class UiState(val userId: UserId)
A value class can only declare one field, but Compose provides `packFloats`, `packInts`, and matching `unpack*` functions in `androidx.compose.ui.util` to store multiple primitives in a single `Long`. This lets you represent composite values (e.g., a 2D point, size, or padding) as a zero-allocation value class instead of a multi-field data class.
@JvmInline value class Offset(val packedValue: Long) fun Offset(x: Float, y: Float): Offset = Offset(packFloats(x, y)) val Offset.x: Float get() = unpackFloat1(packedValue) val Offset.y: Float get() = unpackFloat2(packedValue)
| Mistake | Fix | |---|---| | Data class wrapping a single domain field | Replace with `@JvmInline value class` | | Value class with no domain meaning (just a wrapper) | Use a type alias or the primitive directly | |
Account state and in-memory event store patterns in Amethyst. Use when working with `Account.kt` (per-user state objects — `kind3FollowList`, `nip65RelayList`,…
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…
Android platform patterns for the `amethyst/` module. Use when working with (1) Android navigation (Navigation Compose, type-safe routes, bottom nav), (2)…
Signer abstraction patterns in Amethyst. Use when working with event signing, choosing between a local keypair (`NostrSignerInternal`), a remote NIP-46 bunker…
Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember,…
Use when writing or reviewing Jetpack Compose layout APIs, modifier parameters, modifier chain construction, hardcoded root layout decisions, or layout…