원클릭으로
kmp
Kotlin Multiplatform fundamentals - use for project setup, expect/actual patterns, source sets, and platform-specific code
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Kotlin Multiplatform fundamentals - use for project setup, expect/actual patterns, source sets, and platform-specific code
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | kmp |
| description | Kotlin Multiplatform fundamentals - use for project setup, expect/actual patterns, source sets, and platform-specific code |
Kotlin Multiplatform enables sharing code across Android, iOS, Desktop, Web (WASM), and Server.
your-project-admin/
├── build.gradle.kts # Root build config
├── settings.gradle.kts # Module includes
├── gradle/libs.versions.toml # Version catalog
│
├── core/
│ ├── common/ # Utilities, Result types, extensions
│ │ └── src/commonMain/kotlin/
│ ├── data/ # Data abstractions, DataStore
│ │ ├── src/commonMain/kotlin/
│ │ ├── src/androidMain/kotlin/
│ │ └── src/iosMain/kotlin/
│ ├── database/ # Room (Android/iOS/JVM only)
│ │ └── src/commonMain/kotlin/
│ ├── network/ # Ktor client
│ │ └── src/commonMain/kotlin/
│ └── ui/ # Design system, theme
│ └── src/commonMain/kotlin/
│
├── feature/
│ ├── auth/
│ │ ├── api/ # Public interfaces, models
│ │ │ └── src/commonMain/kotlin/
│ │ └── impl/ # Implementation, UI
│ │ └── src/commonMain/kotlin/
│ └── home/
│ ├── api/
│ └── impl/
│
├── composeApp/ # Platform entry points
│ ├── src/commonMain/ # App composition, DI graph
│ ├── src/androidMain/ # MainActivity
│ ├── src/iosMain/ # iOS entry
│ ├── src/jvmMain/ # Desktop main()
│ └── src/wasmJsMain/ # Web entry
│
└── iosApp/ # Xcode project
commonMain
├── androidMain
├── iosMain
│ ├── iosX64Main
│ ├── iosArm64Main
│ └── iosSimulatorArm64Main
├── jvmMain
├── wasmJsMain
└── jsMain (fallback)
plugins {
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinSerialization) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.room) apply false
alias(libs.plugins.metro) apply false
}
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "your-project-admin"
// Core modules
include(":core:common")
include(":core:data")
include(":core:database")
include(":core:network")
include(":core:ui")
// Feature modules
include(":feature:auth:api")
include(":feature:auth:impl")
include(":feature:home:api")
include(":feature:home:impl")
// App entry points
include(":composeApp")
[versions]
kotlin = "2.1.0"
agp = "8.7.3"
compose-multiplatform = "1.7.3"
ktor = "3.1.1"
room = "2.8.4"
datastore = "1.2.0"
decompose = "3.5.0"
metro = "0.1.1"
essenty = "2.5.0"
coroutines = "1.10.1"
serialization = "1.7.3"
ksp = "2.1.0-1.0.29"
[libraries]
# Kotlin
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
# Ktor
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
# Room (Android, iOS, JVM only)
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version = "2.6.2" }
# DataStore
datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "datastore" }
# Decompose
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }
essenty-lifecycle = { module = "com.arkivanov.essenty:lifecycle", version.ref = "essenty" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
room = { id = "androidx.room", version.ref = "room" }
metro = { id = "dev.zacsweers.metro", version.ref = "metro" }
// core/common/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "CoreCommon"
isStatic = true
}
}
jvm("desktop")
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
}
androidMain.dependencies {
// Android-specific
}
iosMain.dependencies {
// iOS-specific
}
val desktopMain by getting {
dependencies {
// Desktop-specific
}
}
}
}
android {
namespace = "com.your-project.admin.core.common"
compileSdk = 35
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
// commonMain/kotlin/Platform.kt
expect class PlatformContext
expect fun getPlatformName(): String
expect fun createDataStorePath(context: PlatformContext): String
// androidMain/kotlin/Platform.android.kt
actual typealias PlatformContext = android.content.Context
actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
actual fun createDataStorePath(context: PlatformContext): String {
return context.filesDir.resolve("datastore").absolutePath
}
// iosMain/kotlin/Platform.ios.kt
import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask
actual class PlatformContext
actual fun getPlatformName(): String = "iOS"
actual fun createDataStorePath(context: PlatformContext): String {
val documentDir = NSFileManager.defaultManager.URLForDirectory(
NSDocumentDirectory,
NSUserDomainMask,
null,
false,
null
)
return "${documentDir?.path}/datastore"
}
// desktopMain/kotlin/Platform.jvm.kt
import java.io.File
actual class PlatformContext
actual fun getPlatformName(): String =
"${System.getProperty("os.name")} ${System.getProperty("os.version")}"
actual fun createDataStorePath(context: PlatformContext): String {
val home = System.getProperty("user.home")
return File(home, ".your-project-admin/datastore").absolutePath
}
// wasmJsMain/kotlin/Platform.wasmJs.kt
actual class PlatformContext
actual fun getPlatformName(): String = "Web (WASM)"
actual fun createDataStorePath(context: PlatformContext): String {
return "your-project-admin-datastore" // Uses localStorage
}
composeApp
├── feature:auth:impl
│ ├── feature:auth:api
│ ├── core:ui
│ └── core:network
├── feature:home:impl
│ ├── feature:home:api
│ ├── core:ui
│ └── core:database
├── core:ui
│ └── core:common
├── core:network
│ └── core:common
├── core:database
│ └── core:common
└── core:data
└── core:common
// feature/auth/api/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
}
kotlin {
// Targets...
sourceSets {
commonMain.dependencies {
// Only models and interfaces - no implementations
api(projects.core.common)
}
}
}
// feature/auth/impl/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.feature.auth.api)
implementation(projects.core.ui)
implementation(projects.core.network)
implementation(libs.decompose)
implementation(libs.decompose.compose)
}
}
}
// core/common/src/commonMain/kotlin/Result.kt
sealed class AppResult<out T> {
data class Success<T>(val data: T) : AppResult<T>()
data class Error(val message: String, val cause: Throwable? = null) : AppResult<Nothing>()
data object Loading : AppResult<Nothing>()
}
inline fun <T, R> AppResult<T>.map(transform: (T) -> R): AppResult<R> = when (this) {
is AppResult.Success -> AppResult.Success(transform(data))
is AppResult.Error -> this
is AppResult.Loading -> this
}
inline fun <T> AppResult<T>.onSuccess(action: (T) -> Unit): AppResult<T> {
if (this is AppResult.Success) action(data)
return this
}
inline fun <T> AppResult<T>.onError(action: (String, Throwable?) -> Unit): AppResult<T> {
if (this is AppResult.Error) action(message, cause)
return this
}
// feature/auth/api/src/commonMain/kotlin/AuthRepository.kt
interface AuthRepository {
suspend fun login(email: String, password: String): AppResult<User>
suspend fun logout(): AppResult<Unit>
fun observeAuthState(): Flow<AuthState>
}
data class User(
val id: String,
val email: String,
val name: String
)
sealed class AuthState {
data object Unauthenticated : AuthState()
data class Authenticated(val user: User) : AuthState()
}
// feature/auth/impl/src/commonMain/kotlin/AuthRepositoryImpl.kt
class AuthRepositoryImpl(
private val apiService: AuthApiService,
private val tokenStorage: TokenStorage
) : AuthRepository {
private val _authState = MutableStateFlow<AuthState>(AuthState.Unauthenticated)
override suspend fun login(email: String, password: String): AppResult<User> {
return try {
val response = apiService.login(LoginRequest(email, password))
tokenStorage.saveToken(response.token)
_authState.value = AuthState.Authenticated(response.user)
AppResult.Success(response.user)
} catch (e: Exception) {
AppResult.Error("Login failed: ${e.message}", e)
}
}
override suspend fun logout(): AppResult<Unit> {
tokenStorage.clearToken()
_authState.value = AuthState.Unauthenticated
return AppResult.Success(Unit)
}
override fun observeAuthState(): Flow<AuthState> = _authState.asStateFlow()
}
// When expect/actual is overkill, use runtime checks
enum class Platform {
Android, iOS, Desktop, Web
}
expect val currentPlatform: Platform
// androidMain
actual val currentPlatform: Platform = Platform.Android
// iosMain
actual val currentPlatform: Platform = Platform.iOS
// desktopMain
actual val currentPlatform: Platform = Platform.Desktop
// wasmJsMain
actual val currentPlatform: Platform = Platform.Web
// Usage
@Composable
fun AdaptiveComponent() {
when (currentPlatform) {
Platform.Android -> AndroidSpecificUI()
Platform.iOS -> IOSSpecificUI()
Platform.Desktop -> DesktopSpecificUI()
Platform.Web -> WebSpecificUI()
}
}
// core/common/src/commonTest/kotlin/ResultTest.kt
class ResultTest {
@Test
fun `map transforms success value`() {
val result: AppResult<Int> = AppResult.Success(5)
val mapped = result.map { it * 2 }
assertTrue(mapped is AppResult.Success)
assertEquals(10, (mapped as AppResult.Success).data)
}
@Test
fun `map preserves error`() {
val result: AppResult<Int> = AppResult.Error("test error")
val mapped = result.map { it * 2 }
assertTrue(mapped is AppResult.Error)
}
}
// androidTest - uses Robolectric or instrumented tests
// iosTest - runs on simulator
// jvmTest - standard JUnit
commonMaincommonTestImportant limitations when targeting WebAssembly:
localStorage or IndexedDB via expect/actual pattern// commonMain - interface only
expect class AppStorage {
fun getString(key: String): String?
fun putString(key: String, value: String)
}
// wasmJsMain - browser storage
actual class AppStorage {
actual fun getString(key: String): String? =
window.localStorage.getItem(key)
actual fun putString(key: String, value: String) {
window.localStorage.setItem(key, value)
}
}
Dispatchers.IO (use Dispatchers.Default)See What's inside for related categories — kmp is a native/cross-platform skill without natural composition partners in other skill domains.
Automated UI verification loop: Marionette screenshot -> Figma reference -> pixel-diff -> difference list -> auto-fix iteration. Solves the false-positive verification problem flagged in insights reports (UI claimed fixed without actually being compared against the reference). Triggers (English): verify ui, ui compare, pixel diff, figma compare, automated ui verification, golden test fail. 自动 UI 验证:Marionette 截图 → Figma 参考图 → pixel-diff → 差异列表 → 自动修复循环。 触发关键字(中文):verify ui、ui 对比、pixel diff、figma 对比、自动验证
Structured visual QA verdict for screenshot-to-reference comparisons
Operational rules for driving Codex CLI from scripts: success-signal contract, diff-feeding semantics, worktree -C flag, and stdin vs argv. Triggers when invoking `codex exec` programmatically (not interactively) — script wrappers, ralph loops, cron pipelines, multi-CLI fan-out. Surfaces silent failure modes that exit 0 but produce no useful output. Triggers (English): codex exec, codex cli, codex review, codex rescue, codex fallthrough, agent script invocation, programmatic codex.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Socratic deep interview with mathematical ambiguity gating before autonomous execution
Use when creating or developing, before writing code or implementation plans - refines rough ideas into fully-formed designs through collaborative questioning, alternative exploration, and incremental validation. Don't use during clear 'mechanical' processes