Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    vitorpamplona

    kotlin-multiplatform

    vitorpamplona/kotlin-multiplatform
    Coding
    1,332
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Platform abstraction decision-making for Amethyst KMP project...

    SKILL.md

    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:

    // 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:

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

    // 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):

    // 24 expect declarations found, common pattern:
    expect object Secp256k1Instance { ... }
    expect object Log { ... }
    expect object LibSodiumInstance { ... }
    

    Classes (instantiable):

    expect class AESCBC { ... }
    expect class DigestInstance { ... }
    

    Functions (utilities):

    expect fun platform(): String
    expect fun currentTimeSeconds(): Long
    

    See 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 (0.23.0 in libs.versions.toml) for crypto
    • AndroidX libraries

    Desktop JVM (jvmMain):

    • Uses Compose Desktop (Window, MenuBar, etc.)
    • secp256k1-kmp-jni-jvm (same 0.23.0 line) for crypto
    • Pure JVM libraries

    iOS (iosMain):

    • Mature target — actively built and tested
    • Architecture targets: iosArm64, iosSimulatorArm64, iosX64 (plus macosArm64 for host tooling)
    • 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:

    // 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:

    // ❌ 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:

    // ❌ 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

    // ❌ 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

    // ❌ 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

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

    // ❌ 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

    // 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 - Good/bad abstraction examples with rationale
    • references/source-set-hierarchy.md - Visual hierarchy with Amethyst examples
    • references/expect-actual-catalog.md - All 24 expect/actual pairs with "why abstracted"
    • 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.)
    Recommended Servers
    Neon
    Neon
    Thoughtbox
    Thoughtbox
    Prisma
    Prisma
    Repository
    vitorpamplona/amethyst
    Files