kotlin-multiplatform

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

1,495 stars

Best use case

kotlin-multiplatform is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

Teams using kotlin-multiplatform should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/kotlin-multiplatform/SKILL.md --create-dirs "https://raw.githubusercontent.com/vitorpamplona/amethyst/main/.claude/skills/kotlin-multiplatform/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/kotlin-multiplatform/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How kotlin-multiplatform Compares

Feature / Agentkotlin-multiplatformStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Kotlin Multiplatform: Platform Abstraction Decisions

Expert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific.

## When to Use This Skill

Making platform abstraction decisions:
- "Should I create expect/actual or keep Android-only?"
- "Can I share this ViewModel logic?"
- "Where does this crypto/JSON/network implementation belong?"
- "This uses Android Context - can it be abstracted?"
- "Is this code in the wrong module?"
- Preparing for iOS/web/wasm targets
- Detecting incorrect placements

## Abstraction Decision Tree

**Central question:** "Should this code be reused across platforms?"

Follow this decision path (< 1 minute):

```
Q: Is it used by 2+ platforms?
├─ NO  → Keep platform-specific
│         Example: Android-only permission handling
│
└─ YES → Continue ↓

Q: Is it pure Kotlin (no platform APIs)?
├─ YES → commonMain
│         Example: Nostr event parsing, business rules
│
└─ NO  → Continue ↓

Q: Does it vary by platform or by JVM vs non-JVM?
├─ By platform (Android ≠ iOS ≠ Desktop)
│  → expect/actual
│  Example: Secp256k1Instance (uses different security APIs)
│
├─ By JVM (Android = Desktop ≠ iOS/web)
│  → jvmAndroid
│  Example: Jackson JSON parsing (JVM library)
│
└─ Complex/UI-related
   → Keep platform-specific
   Example: Navigation (Activity vs Window too different)

Final check:
Q: Maintenance cost of abstraction < duplication cost?
├─ YES → Proceed with abstraction
└─ NO  → Duplicate (simpler)
```

### Real Examples from Codebase

**Crypto → expect/actual:**
```kotlin
// commonMain - expect declaration
expect object Secp256k1Instance {
    fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray
}

// androidMain - uses Android Keystore
// jvmMain - uses Desktop JVM crypto
// iosMain - uses iOS Security framework
```
**Why:** Each platform has different security APIs.

**JSON parsing → jvmAndroid:**
```kotlin
// quartz/build.gradle.kts
val jvmAndroid = create("jvmAndroid") {
    api(libs.jackson.module.kotlin)
}
```
**Why:** Jackson is JVM-only, works on Android + Desktop, not iOS/web.

**Navigation → platform-specific:**
- Android: `MainActivity` (Activity + Compose Navigation)
- Desktop: `Window` + sidebar + MenuBar
**Why:** UI paradigms fundamentally different.

## Mental Model: Source Sets as Dependency Graph

Think of source sets as a dependency graph, not folders.

```
┌─────────────────────────────────────────────┐
│ commonMain = Contract (pure Kotlin)         │
│ - Business logic, protocol, data models     │
│ - No platform APIs                          │
└────────────┬────────────────────────────────┘
             │
             ├──────────────────────┬────────────────────
             │                      │
             ▼                      ▼
   ┌───────────────────┐  ┌──────────────────┐
   │ jvmAndroid        │  │ iosMain          │
   │ JVM libs shared   │  │ iOS common       │
   │ - Jackson         │  │                  │
   │ - OkHttp          │  └────┬─────────────┘
   └───┬───────────┬───┘       │
       │           │           │
       ▼           ▼           ├─→ iosArm64Main
  ┌─────────┐ ┌──────────┐     └─→ iosSimulatorArm64Main
  │android  │ │jvmMain   │
  │Main     │ │(Desktop) │
  └─────────┘ └──────────┘

Future: jsMain, wasmMain
```

**Key insight:** jvmAndroid is NOT a platform - it's a shared JVM layer.

## The jvmAndroid Pattern

**Unique to Amethyst.** Shares JVM libraries between Android + Desktop.

### When to Use jvmAndroid

Use jvmAndroid when:
- ✅ JVM-specific libraries (Jackson, OkHttp, url-detector)
- ✅ Android implementation = Desktop implementation (same JVM)
- ✅ Library doesn't work on iOS/web

Do NOT use jvmAndroid for:
- ❌ Pure Kotlin code (use commonMain)
- ❌ Platform-specific APIs (use androidMain/jvmMain)
- ❌ Code that should work on all platforms

### Example from quartz/build.gradle.kts

```kotlin
// Must be defined BEFORE androidMain and jvmMain
val jvmAndroid = create("jvmAndroid") {
    dependsOn(commonMain.get())

    dependencies {
        api(libs.jackson.module.kotlin)  // JSON parsing - JVM only
        api(libs.url.detector)            // URL extraction - JVM only
        implementation(libs.okhttp)       // HTTP client - JVM only
    }
}

// Both depend on jvmAndroid
jvmMain { dependsOn(jvmAndroid) }
androidMain { dependsOn(jvmAndroid) }
```

**Why Jackson in jvmAndroid, not commonMain?**
- Jackson is JVM-specific library
- Works on Android (runs on JVM)
- Works on Desktop (runs on JVM)
- Does NOT work on iOS (not JVM) or web (not JVM)

**Web/wasm consideration:** For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance).

## What to Abstract vs Keep Platform-Specific

Quick decision guidelines based on codebase patterns:

### Always Abstract
- **Crypto** (Secp256k1, encryption, signing)
- **Core protocol logic** (Nostr events, NIPs)
- **Why:** Needed everywhere, platform security APIs vary

### Often Abstract
- **I/O operations** (file reading, caching)
- **Logging** (platform logging systems differ)
- **Serialization** (if using kotlinx.serialization)
- **Why:** Commonly reused, platform implementations available

### Sometimes Abstract
- **Business logic:** YES - state machines, data processing
- **ViewModels:** YES - state + business logic shareable (StateFlow/SharedFlow)
- **Screen layouts:** NO - platform-native (Window vs Activity)
- **Why:** ViewModels contain platform-agnostic state; Screens render differently per platform

### Rarely Abstract
- **Complex UI components** (composables with heavy platform dependencies)
- **Why:** Platform paradigms can differ significantly

### Never Abstract
- **Navigation** (Activity vs Window fundamentally different)
- **Permissions** (Android vs iOS APIs incompatible)
- **Platform UX patterns**
- **Why:** Too platform-specific, abstraction creates leaky APIs

### Evidence from shared-ui-analysis.md

| Component | Shared? | Rationale |
|-----------|---------|-----------|
| PubKeyFormatter, ZapFormatter | ✅ YES | Pure Kotlin, no platform APIs |
| TimeAgoFormatter | ⚠️ ABSTRACTED | Needs StringProvider for localized strings |
| ViewModels (state + logic) | ✅ YES | StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible |
| Screen layouts (Scaffold, nav) | ❌ NO | Window vs Activity, sidebar vs bottom nav fundamentally different |
| Image loading (Coil) | ⚠️ ABSTRACTED | Coil 3.x supports KMP, needs expect/actual wrapper |

## expect/actual Mechanics

**When to use:** Code needed by 2+ platforms, varies by platform.

### Pattern Categories from Codebase

**Objects (singletons):**
```kotlin
// 24 expect declarations found, common pattern:
expect object Secp256k1Instance { ... }
expect object Log { ... }
expect object LibSodiumInstance { ... }
```

**Classes (instantiable):**
```kotlin
expect class AESCBC { ... }
expect class DigestInstance { ... }
```

**Functions (utilities):**
```kotlin
expect fun platform(): String
expect fun currentTimeSeconds(): Long
```

**See** [references/expect-actual-catalog.md](references/expect-actual-catalog.md) for complete catalog with rationale.

## Target-Specific Guidance

### Android, JVM (Desktop), iOS - Current Primary Targets

**Status:** Mature patterns, stable APIs

**Android (androidMain):**
- Uses Android framework (Activity, Context, etc.)
- secp256k1-kmp-jni-android for crypto
- AndroidX libraries

**Desktop JVM (jvmMain):**
- Uses Compose Desktop (Window, MenuBar, etc.)
- secp256k1-kmp-jni-jvm for crypto
- Pure JVM libraries

**iOS (iosMain):**
- Active development, framework configured
- Architecture targets: macosArm64Main, iosArm64Main, iosSimulatorArm64Main
- Platform APIs via platform.posix, Security framework

### Web, wasm - Future Targets

**Status:** Not yet implemented, consider for future-proofing

**Constraints to know:**
- ❌ No platform.posix (file I/O different)
- ❌ No JVM libraries (Jackson, OkHttp won't work)
- ❌ Different async model (JS event loop vs threads)

**Future-proofing tips:**
1. Prefer pure Kotlin in commonMain
2. Use kotlinx.* libraries:
   - kotlinx.serialization instead of Jackson
   - ktor instead of OkHttp (ktor supports web)
   - kotlinx.datetime instead of custom date handling
3. Avoid platform.posix for file operations
4. Test abstractions work without JVM assumptions

**Example migration path:**
```kotlin
// Current: jvmAndroid (JVM-only)
api(libs.jackson.module.kotlin)

// Future: commonMain (all platforms)
api(libs.kotlinx.serialization.json)
```

## Integration: When to Invoke Other Skills

### Invoke gradle-expert

Trigger gradle-expert skill when encountering:
- Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch)
- Build errors related to source sets
- Version catalog issues (libs.versions.toml)
- "Duplicate class" errors
- Performance/build time issues

**Example trigger:**
```
Error: Duplicate class found: fr.acinq.secp256k1.Secp256k1
```
→ Invoke gradle-expert for dependency conflict resolution.

### Flags to Raise

**Platform code in commonMain:**
```kotlin
// ❌ INCORRECT - Android API in commonMain
expect fun getContext(): Context  // Context is Android-only!
```
→ Flag: "Android API in commonMain won't compile on other platforms"

**Duplicated business logic:**
```kotlin
// ❌ INCORRECT - Same logic in both
// androidMain/.../CryptoUtils.kt
fun validateSignature(...) { ... }

// jvmMain/.../CryptoUtils.kt
fun validateSignature(...) { ... }  // Duplicated!
```
→ Flag: "Business logic duplicated, should be in commonMain or expect/actual"

**Reinventing wheel - suggest KMP alternatives:**
- Custom date/time → kotlinx.datetime
- OkHttp → ktor (supports web)
- Jackson → kotlinx.serialization
- Custom UUID → kotlinx.uuid (when stable)

## Common Pitfalls

### 1. Over-Abstraction
**Problem:** Creating expect/actual for UI components
```kotlin
// ❌ BAD
expect fun NavigationComponent(...)
```
**Why:** Navigation paradigms too different (Activity vs Window)
**Fix:** Keep platform-specific, accept duplication

### 2. Under-Sharing
**Problem:** Duplicating business logic across platforms
```kotlin
// ❌ BAD - duplicated in androidMain and jvmMain
fun parseNostrEvent(json: String): Event { ... }
```
**Why:** Bug fixes need to be applied twice, tests duplicated
**Fix:** Move to commonMain (pure Kotlin) or create expect/actual

### 3. Leaky Abstractions
**Problem:** Platform code in commonMain
```kotlin
// commonMain - ❌ BAD
import android.content.Context  // Won't compile on iOS!
```
**Fix:** Use expect/actual or dependency injection

### 4. Premature Abstraction
**Problem:** Creating expect/actual before second platform needs it
```kotlin
// ❌ BAD - only used on Android currently
expect fun showNotification(...)
```
**Why:** Wrong abstraction boundaries, wasted effort
**Fix:** Wait until iOS actually needs it, then abstract

### 5. Wrong Source Set
**Problem:** JVM libraries in commonMain
```kotlin
// commonMain - ❌ BAD
import com.fasterxml.jackson.databind.ObjectMapper
```
**Why:** Jackson won't compile on iOS/web
**Fix:** Move to jvmAndroid or migrate to kotlinx.serialization

## Quick Reference

| Code Type | Recommended Location | Reason |
|-----------|---------------------|--------|
| Pure Kotlin business logic | commonMain | Works everywhere |
| Nostr protocol, NIPs | commonMain | Core logic, no platform APIs |
| JVM libs (Jackson, OkHttp) | jvmAndroid | Android + Desktop only |
| Crypto (varies by platform) | expect in commonMain, actual in platforms | Different security APIs per platform |
| I/O, logging | expect in commonMain, actual in platforms | Platform implementations differ |
| State (business logic) | commonMain or commons/jvmAndroid | Reusable StateFlow patterns |
| **ViewModels** | **commons/commonMain/viewmodels/** | **StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible** |
| UI formatters (pure) | commons/commonMain | Reusable, no dependencies |
| UI components (simple) | commons/commonMain | Cards, buttons, dialogs |
| **Screen layouts** | **Platform-specific** | **Window vs Activity, sidebar vs bottom nav** |
| Navigation | Platform-specific only | Activity vs Window too different |
| Permissions | Platform-specific only | APIs incompatible |
| Platform UX (menus, etc.) | Platform-specific only | Native feel required |

## See Also

- [references/abstraction-examples.md](references/abstraction-examples.md) - Good/bad abstraction examples with rationale
- [references/source-set-hierarchy.md](references/source-set-hierarchy.md) - Visual hierarchy with Amethyst examples
- [references/expect-actual-catalog.md](references/expect-actual-catalog.md) - All 24 expect/actual pairs with "why abstracted"
- [references/target-compatibility.md](references/target-compatibility.md) - Platform constraints and future-proofing

## Scripts

- `scripts/validate-kmp-structure.sh` - Detect incorrect placements, validate source sets
- `scripts/suggest-kmp-dependency.sh` - Suggest KMP library alternatives (ktor, kotlinx.serialization, etc.)

Related Skills

kotlin-expert

1495
from vitorpamplona/amethyst

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-coroutines

1495
from vitorpamplona/amethyst

Advanced Kotlin coroutines patterns for AmethystMultiplatform. Use when working with: (1) Structured concurrency (supervisorScope, coroutineScope), (2) Advanced Flow operators (flatMapLatest, combine, merge, shareIn, stateIn), (3) Channels and callbackFlow, (4) Dispatcher management and context switching, (5) Exception handling (CoroutineExceptionHandler, SupervisorJob), (6) Testing async code (runTest, Turbine), (7) Nostr relay connection pools and subscriptions, (8) Backpressure handling in event streams. Delegates to kotlin-expert for basic StateFlow/SharedFlow patterns. Complements nostr-expert for relay communication.

Amethyst Builder Skill

1495
from vitorpamplona/amethyst

Build customized Amethyst Nostr clients for Android. Fork, rebrand, customize, and distribute your own version.

quartz-integration

1495
from vitorpamplona/amethyst

Integration guide for using the Quartz Nostr KMP library in external projects. Use when: (1) adding Quartz as a Gradle dependency, (2) setting up NostrClient with WebSocket, (3) creating/signing/sending events, (4) building relay subscriptions with Filter, (5) handling keys with KeyPair/NostrSignerInternal, (6) using Bech32 encoding/decoding (NIP-19), (7) platform-specific setup (Android vs JVM/Desktop), (8) NIP-57 zaps, NIP-17 DMs, NIP-44 encryption in external projects.

nostr-expert

1495
from vitorpamplona/amethyst

Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details.

gradle-expert

1495
from vitorpamplona/amethyst

Build optimization, dependency resolution, and multi-module KMP troubleshooting for AmethystMultiplatform. Use when working with: (1) Gradle build files (build.gradle.kts, settings.gradle), (2) Version catalog (libs.versions.toml), (3) Build errors and dependency conflicts, (4) Module dependencies and source sets, (5) Desktop packaging (DMG/MSI/DEB), (6) Build performance optimization, (7) Proguard/R8 configuration, (8) Common KMP + Android Gradle issues (Compose conflicts, secp256k1 JNI variants, source set problems).

find-non-lambda-logs

1495
from vitorpamplona/amethyst

Use when auditing or migrating Log calls to lambda overloads, after adding new logging, or checking for string interpolation in Log.d/i/w/e calls that waste allocations when the log level is filtered out

find-missing-translations

1495
from vitorpamplona/amethyst

Use when comparing Android strings.xml locale files to find untranslated string resources, missing translation keys, or preparing translation work for a specific language

Desktop Expert

1495
from vitorpamplona/amethyst

Expert in Compose Multiplatform Desktop development for AmethystMultiplatform. Covers Desktop-specific APIs, OS conventions, navigation patterns, and UX principles.

compose-expert

1495
from vitorpamplona/amethyst

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 icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).

android-expert

1495
from vitorpamplona/amethyst

Android platform expertise for Amethyst Multiplatform project. Covers Compose Navigation, Material3, permissions, lifecycle, and Android-specific patterns in KMP architecture.

compose-multiplatform-patterns

144923
from affaan-m/everything-claude-code

KMP项目中的Compose Multiplatform和Jetpack Compose模式——状态管理、导航、主题化、性能优化和平台特定UI。