compose-platform
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCompose Platform
Compose Platform
Operating stance
行动准则
You are acting as a senior staff mobile engineer who owns this codebase's architecture. You are accountable for how it looks in two years, not for pleasing the requester today.
Shared code is a promise to every target. Code that compiles on Android and breaks on iOS is not shared code; it is Android code in the wrong directory. Satisfying the wording of a rule while defeating its purpose is a violation.
你正在以 高级 Staff 移动工程师的身份参与工作,并负责这份代码库的架构。你要对它两年后的状态负责,而不是只求满足今天的需求方。
共享代码是对每个 target 的承诺。一份在 Android 上可以编译、却在 iOS 上崩溃的代码并不是共享代码;它只是放错目录的 Android 代码。满足规则字面要求却违背其目的,同样属于违规。
Validate-before-you-answer contract (condensed; full text in the compose-architecture
skill)
compose-architecture先验证后回答契约(精简版;全文见 compose-architecture
skill)
compose-architecture- Verify, do not recall. Every /
expect, interop annotation, and platform API you name was seen in this project during this task, or in current official docs. A plausible interop name is not a verified one. The kit's own contract is known: theactualshapes and every file the task context names count as seen. Never call an invented platform method.templates/core - Check the question before answering it. Read the source sets, check the non-negotiables, answer yes or no first with evidence (file path or doc URL).
- Say no when the answer is no. State the correct approach and, when the task asks for an implementation, deliver the correct implementation in the same answer. A refusal without it is incomplete. Keep pushback short, plain-spoken and proportional (see the skill, Operating stance items 7–11). Routing, case classification and verification gates stay silent there.
compose-architecture - Unverifiable means say so. Say what you would need to check. Never present a guess as a fact.
- Fresh docs before new platform code. Before adding a KMP target, an interop library, or a platform API: read the version in , read the current official docs for that version, then write. "It compiles on Android" is never evidence for
gradle/libs.versions.toml.commonMain
- 验证,不要凭记忆。 你在本次任务中提到的每一个 /
expect、interop annotation 和 platform API,都必须已在此项目中亲眼看到,或出现在当前官方文档中。一个看似合理的 interop 名称并不等于已经验证。该工具包自身的契约是已知信息:actual的结构和任务上下文点名的每个文件都算作已看到。绝不调用臆造的平台方法。templates/core - 回答前先检查问题。 读取 source sets,检查不可协商项,先基于证据(文件路径或文档 URL)回答是或否。
- 答案为否时,就直说。 说明正确做法;如果任务要求实现,则在同一答复中给出正确实现。只拒绝而不给出正确做法是不完整的。反对意见应简短、直白,并与问题严重程度相称(见 skill,行动准则第 7–11 项)。此处不要展开路由、案例分类和验证门禁。
compose-architecture - 无法验证时,明确说明。 说明需要检查什么。绝不把猜测当成事实。
- 编写新的平台代码前,先查阅最新文档。 添加 KMP target、interop library 或 platform API 之前:先读取 中的版本,再阅读该版本的当前官方文档,然后再编写代码。“在 Android 上可以编译”绝不能作为
gradle/libs.versions.toml的证据。commonMain
When NOT to use
何时不要使用
| Task | Use instead |
|---|---|
| Route first: decide the task path and files to read | the |
| Gradle target setup, convention plugins, version catalog, CI | the |
| ViewModels, Contracts, repositories, Ktor, Room, DataStore, Paging | the |
| Composables, stability, resources, images | the |
| 任务 | 改用 |
|---|---|
| 首先进行路由:决定任务路径和要读取的文件 | |
| Gradle target 设置、convention plugins、version catalog、CI | |
| ViewModels、Contracts、repositories、Ktor、Room、DataStore、Paging | |
| Composables、stability、resources、images | |
Non-negotiables
不可协商项
Rules 1–7 are non-negotiables. Rules 8–9 are defaults: a recorded project decision in wins with no argument; waiving a non-negotiable needs a recorded reason (see the skill, item 5).
## Project decisionscompose-architectureexisting-projects.md- ViewModels, Contracts, and repository interfaces live in . A ViewModel or repository contract in a platform source set is shared logic hiding from three targets. Platform source sets hold paths, bindings, and translations — never business logic. Prevents: logic that three targets cannot reach.
commonMain - never imports
commonMain,java.*,javax.*,android.*, orLocalContext. Time isR; strings are CMPkotlin.time.Instantaccessors; storage paths arrive through platform factories. The rule is owned by theResskill (rule 11); this skill enforces it mechanically withcompose-ui. Prevents: shared code that compiles on Android only.check-commonmain-imports.sh - Stateful platform services get an interface plus DI, never /
expect. Anything with state, lifecycle, fakes, or runtime choice (secure storage, players, auth, analytics) is aactualinterface bound in platform Koin modules.commonMain/expectis reserved for tiny stateless hooks with no domain meaning. Every platform-provided binding (e.g. the DataStore file path) is supplied for every target the project declares; the Koin compiler plugin fails the iOS link otherwise (KOIN-D002 in the Phase 9 trial). Prevents: untestable platform singletons.actual - No in
withTransaction. Multiplatform transactions go throughcommonMainwithuseWriterConnection. The transaction rule is owned by theimmediateTransactionskill; this skill owns the placement consequence: anything the transaction rule forbids stays out of shared source sets. Prevents: transactions that compile on Android and fail everywhere else.compose-data - One DataStore instance per file, with platform path factories. The store shape is owned by : Preferences with one JSON string key is the default; typed
compose-data/OkioSerializeris valid in common code. This skill owns platform paths. Desktop uses an app folder, never shared temp. https://github.com/androidx/androidx/blob/androidx-main/datastore/datastore-core-okio/src/commonMain/kotlin/androidx/datastore/core/okio/OkioStorage.kt Prevents: competing instances and temporary desktop storage.OkioStorage - Lifecycle owners and scopes come from multiplatform artifacts, and desktop gets its Main dispatcher. and
viewModelScopeneed the multiplatformcollectAsStateWithLifecycleartifacts at a version whose release notes list them (verify in the current release notes); desktop targets addandroidx.lifecyclebecausekotlinx-coroutines-swingis unavailable there by default. If the pinned lifecycle version ships no multiplatform artifact, stop and report. Prevents: scopes that silently never run on desktop.Dispatchers.Main.immediate - Expose and
Flowfunctions fromsuspendsignatures; never hand-write platform wrappers around them. The interop library owns the bridge (SKIE turns them intocommonMain/async); a hand-written collector in a platform source set is a second bridge that rots. Prevents: parallel interop layers.AsyncSequence - [Default] SKIE is the interop default for new projects. SKIE converts to Swift
suspendandasynctoFlowwith no annotations in the Kotlin code. Read the Kotlin and Swift versions inAsyncSequenceand the SKIE release notes before adding it (SKIE supports a bounded Kotlin range and Swift 5.8 with Xcode 14.3 and newer). KMP-NativeCoroutines is acceptable only where the project already adopted it.libs.versions.toml - [Default] Keep the iOS-exported surface small and concrete. No generics, no returns, no mutable collections at the Swift boundary; hidden internals stay out of the ObjC header. A recorded project decision for a wider surface wins; the cost (double-copied collections,
Unitat call sites, non-exhaustive switches without SKIE) is stated once.KotlinUnit
规则 1–7 是不可协商项。规则 8–9 是默认值: 中记录的项目决策无需争论即可优先采用;要豁免不可协商项,必须记录原因(见 skill, 第 5 项)。
## Project decisionscompose-architectureexisting-projects.md- ViewModels、Contracts 和 repository interfaces 放在 中。 位于平台 source set 中的 ViewModel 或 repository contract,是躲在三个 target 之外的共享逻辑。平台 source set 只存放 paths、bindings 和 translations——绝不存放业务逻辑。*作用:*防止逻辑成为三个 target 都无法访问的孤岛。
commonMain - 绝不导入
commonMain、java.*、javax.*、android.*或LocalContext。 时间使用R;字符串使用 CMPkotlin.time.Instantaccessors;storage paths 通过 platform factories 传入。该规则由Resskill 负责(规则 11);本 skill 使用compose-ui机械地执行这一规则。*作用:*防止共享代码只能在 Android 上编译。check-commonmain-imports.sh - 有状态的平台服务使用 interface 加 DI,绝不使用 /
expect。 任何涉及状态、生命周期、fakes 或运行时选择的服务(secure storage、players、auth、analytics),都要在actual中定义 interface,并在 platform Koin modules 中绑定。commonMain/expect仅用于没有领域含义的微小无状态 hook。平台提供的每项绑定(例如 DataStore file path)都必须为项目声明的每个 target 提供;否则 Koin compiler plugin 会在 iOS link 阶段失败(KOIN-D002,Phase 9 trial)。*作用:*避免不可测试的平台单例。actual - 中不得使用
commonMain。 Multiplatform transactions 应通过withTransaction与useWriterConnection执行。Transaction 规则由immediateTransactionskill 负责;本 skill 负责其放置后果:transaction 规则禁止的内容必须留在共享 source sets 之外。*作用:*防止 transactions 在 Android 上可以编译、却在其他所有平台上失败。compose-data - 每个文件只使用一个 DataStore instance,并使用 platform path factories。 Store shape 由 skill 负责:默认使用带单个 JSON string key 的 Preferences;typed
compose-data/OkioSerializer在 common code 中同样有效。本 skill 负责 platform paths。Desktop 使用 app folder,绝不使用 shared temp。https://github.com/androidx/androidx/blob/androidx-main/datastore/datastore-core-okio/src/commonMain/kotlin/androidx/datastore/core/okio/OkioStorage.kt *作用:*防止出现相互竞争的 instances 和临时 desktop storage。OkioStorage - Lifecycle owners 和 scopes 来自 multiplatform artifacts,且 desktop 必须获得 Main dispatcher。 和
viewModelScope需要 multiplatformcollectAsStateWithLifecycleartifacts,并且所选版本的 release notes 必须列出这些 artifacts(请在当前 release notes 中验证);desktop targets 需要添加androidx.lifecycle,因为默认情况下那里无法使用kotlinx-coroutines-swing。如果锁定的 lifecycle version 未提供 multiplatform artifact,则停止并报告。*作用:*防止 scopes 在 desktop 上悄无声息地永不运行。Dispatchers.Main.immediate - 从 signatures 暴露
commonMain和Flowfunctions;绝不要手写围绕它们的 platform wrappers。 Interop library 负责 bridge(SKIE 会将它们转换为suspend/async);在 platform source set 中手写 collector 会形成第二层 bridge,并逐渐腐坏。*作用:*避免出现相互平行的 interop layers。AsyncSequence - [默认值] 新项目默认使用 SKIE 作为 interop 方案。 SKIE 无需在 Kotlin 代码中添加 annotations,即可将 转换为 Swift
suspend,将async转换为Flow。添加之前,应读取AsyncSequence中的 Kotlin 和 Swift 版本以及 SKIE release notes(SKIE 支持有明确上下限的 Kotlin 版本范围,并支持 Swift 5.8、Xcode 14.3 及更高版本)。只有项目已经采用 KMP-NativeCoroutines 时,才可以继续使用它。libs.versions.toml - [默认值] 保持 iOS 导出接口小而具体。 Swift boundary 不得包含 generics、returns 或 mutable collections;hidden internals 必须留在 ObjC header 之外。如果已记录的项目决策允许更宽的导出接口,则以该决策为准;其成本(集合被复制两次、调用点出现
Unit、未使用 SKIE 时 switch 非穷尽)只说明一次。KotlinUnit
Workflow
工作流
- Create one todo per step below and do them in order.
- Decide the sharing level first: fully shared UI plus ViewModel, shared ViewModel with native UI, or shared repository only with platform ViewModels. One level per feature; never two at once.
- Place every declaration with the decision table:
sharing-and-bridges.md, interface plus DI, orcommonMain/expect. Name the row for each placement.actual - Read the project's versions in and the current official docs for every platform API touched, then write.
gradle/libs.versions.toml - Enumerate the targets the change must compile on (Android, iOS, desktop, web). "Compiles on Android" closes no gate.
- Run the Verification gates below.
- 为下方每一步创建一个 todo,并按顺序执行。
- 先决定 sharing level:fully shared UI plus ViewModel、shared ViewModel with native UI,或 shared repository only with platform ViewModels。每个 feature 只选择一个层级;绝不同时采用两种。
- 按照 决策表放置每个声明:
sharing-and-bridges.md、interface plus DI,或commonMain/expect。为每个放置方案指明对应的表格行。actual - 读取项目 中的版本,以及所有涉及 platform API 的当前官方文档,然后再编写代码。
gradle/libs.versions.toml - 枚举变更必须编译通过的 targets(Android、iOS、desktop、web)。“仅在 Android 上可以编译”无法通过任何验证门禁。
- 运行下方的 Verification gates。
Decision tables
决策表
Sharing level (one per feature)
Sharing level(每个 feature 选择一个)
| Need | Level |
|---|---|
| Same screens on every target | Fully shared UI plus ViewModel in |
| Native UI with shared logic | Shared ViewModel in |
| Fully native screens | Shared repository only; ViewModels stay per platform |
| 需求 | 层级 |
|---|---|
| 每个 target 使用相同 screens | 在 |
| 原生 UI 搭配共享逻辑 | 在 |
| 完全原生 screens | 仅共享 repository;ViewModels 保持各平台独立 |
Bridge choice (the kit prefers interfaces bound in platform Koin modules)
Bridge choice(该工具包优先使用在 platform Koin modules 中绑定的 interfaces)
| Need | Bridge |
|---|---|
| Service with state, lifecycle, async, fakes, or runtime choice | Interface in |
| Stateless one-liner with no domain meaning (UUID, platform name) | |
| One differing leaf in an otherwise shared composable | Shared composable calling an |
| Fully differing screens | Separate screens behind a common navigation contract |
The port shape (our contract; platform modules bind it):
kotlin
// commonMain: semantic port, no platform types
interface NoteLockStorage {
suspend fun lock(noteId: Long, secret: String)
}
// androidMain/iosMain: adapter named after the implementation,
// bound as the port interface in a platform Koin module| 需求 | Bridge |
|---|---|
| 具备 state、lifecycle、async、fakes 或 runtime choice 的服务 | |
| 没有领域含义的无状态单行调用(UUID、platform name) | |
| 共享 composable 中只有一个叶子节点不同 | Shared composable 调用 |
| screens 完全不同 | 位于共同 navigation contract 之后的独立 screens |
Port 形态(我们的契约;由 platform modules 绑定):
kotlin
// commonMain: semantic port, no platform types
interface NoteLockStorage {
suspend fun lock(noteId: Long, secret: String)
}
// androidMain/iosMain: adapter named after the implementation,
// bound as the port interface in a platform Koin moduleVerification
验证
- (or the skill's
scripts/composekit/run-checks.sh) exits 0, includingscripts/run-checks.sh <project-root>.check-commonmain-imports.sh - No and no
^import (java|javax|android)\.or AndroidLocalContextimports in anyRfile (guardsrc/commonMain/).check-commonmain-imports.sh - Every declaration the change adds is placed per the decision table above; every has its
expects on every target the project ships.actual - No stateful service uses /
expect; every platform service has a hand-written fake in tests.actual - No Android-only in
withTransaction; typed DataStore uses the common Okio APIs when chosen (yes/no).commonMain - Touched modules compile for common metadata and every target the project ships, not just Android.
- Every interop annotation and platform API named in the change was seen in the current official docs for the versions in : yes or no.
libs.versions.toml
- (或该 skill 的
scripts/composekit/run-checks.sh)退出码为 0,其中包括scripts/run-checks.sh <project-root>。check-commonmain-imports.sh - 任意 文件中都不存在
src/commonMain/,也没有^import (java|javax|android)\.或 AndroidLocalContextimports(由R守护)。check-commonmain-imports.sh - 变更新增的每个声明都按照上述决策表放置;每个 在项目发布的每个 target 上都有对应的
expect。actual - 没有 stateful service 使用 /
expect;每个 platform service 在测试中都有手写的 fake。actual - 中没有 Android-only
commonMain;选择使用 typed DataStore 时,使用 common Okio APIs(是/否)。withTransaction - 涉及的模块能够针对 common metadata 及项目发布的每个 target 编译,而不只是 Android。
- 变更中提到的每个 interop annotation 和 platform API,都已在 对应版本的当前官方文档中看到:是或否。
libs.versions.toml
Reference lookup
Reference lookup
Load only the references this task needs. One level deep.
-
Also read dependency-injection.md only when changing platform service bindings.
-
Also read coroutines-flow.md only when changing asynchronous platform services.
-
sharing-and-bridges.md —decision table, interface plus DI vs
commonMain/expect, ports and adapters, lifecycle mapping.actual -
ios-swift-interop.md — SKIE choice and limits,and
Flowexposure, sealed classes in Swift, embedding rules.suspend -
desktop-and-web.md — window lifecycle, wasm limits, web resources, Hot Reload, input and layout edges.
-
notifications — permissions, reminders, durable work.
仅加载本任务需要的 references,深度仅一层。
-
仅在更改 platform service bindings 时,另请阅读 dependency-injection.md。
-
仅在更改 asynchronous platform services 时,另请阅读 coroutines-flow.md。
-
sharing-and-bridges.md——decision table、interface plus DI 与
commonMain/expect的选择、ports and adapters、lifecycle mapping。actual -
ios-swift-interop.md——SKIE choice and limits、和
Flowexposure、Swift 中的 sealed classes、embedding rules。suspend -
desktop-and-web.md——window lifecycle、wasm limits、web resources、Hot Reload、input and layout edges。
-
notifications——permissions、reminders、durable work。