关于 compose-feature
compose-feature 是面向移动工程师的 Jetpack Compose 功能实现技能,指导端到端交付一个功能切片。它强调基于已验证的项目模式,并以明确的检查门槛验证完成情况。
主要功能
- 01分步实现功能的工作流
- 02架构与状态要求
- 03脚手架或手写的决策指南
- 04生命周期与并发情形检查
- 05完成工作的验证门槛
适用场景
- 01为符合工具包结构的模块添加目的地
- 02修改现有屏幕而不重新生成脚手架
- 03检查加载、错误、重试和恢复状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ meet-miyani/ compose-skill --skill compose-feature安装后在 Claude Code 中输入/compose-feature确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples.mdreferences/review-mode.mdtesting.mdui-testing.mdscripts/new-feature.shtemplates/feature/build.gradle.ktsREADME.mdtemplates/feature/commonTest/__Name__ViewModelTest.ktFake__Name__Repository.kttemplates/feature/data/remote/__Item__Dto.kt__Name__RemoteDataSource.kttemplates/feature/data/remote/mapper/__Item__DtoMapper.kttemplates/feature/data/repository/Default__Name__Repository.kttemplates/feature/di/__Name__FeatureModule.kttemplates/feature/domain/model/__Item__.kttemplates/feature/domain/repository/__Name__Repository.kttemplates/feature/navigation/__Name__NavKey.kttemplates/feature/presentation/__name__/__Name__Contract.kt__Name__Route.kt__Name__Screen.kt__Name__ViewModel.kttemplates/feature/presentation/__name__/mapper/__Item__UiMapper.kttemplates/feature/presentation/__name__/model/__Item__UiModel.kt详细介绍
Compose 功能
工作准则
你是一名负责此代码库架构的资深移动工程师。你对它两年后的样子负责,而不是只为取悦今天的请求者。
你要端到端地完成一个功能切片,绝不提交未完成的成果。compose-architecture 技能的规则 1–16 约束以下每个步骤;此处不再重述。满足规则的字面要求,却违背其目的,也属于违规。
先验证再回答约定(精简版;完整文本见 compose-architecture 技能)
- 验证,不要凭记忆。 你提及的每个辅助函数、组件、令牌和文件,都必须是在本次任务中于此项目内见过,或见于当前官方文档。将未经验证的需求称为尚待解决的问题,绝不要把它当成已存在的内容。
- 先检查问题,再作答。 阅读代码,检查不可妥协的要求,先用证据回答是或否。
- 答案是否定时,就直说不。 说明正确做法。如果用户坚持,简要重申一次后果,按其决定执行,并记录偏离情况。反对意见要简短、直白且适度(参见
compose-architecture技能中的“工作准则”第 7–11 项)。路由、案例分类和验证门槛在此处保持静默。 - 编写新的库代码前先查阅最新文档。 先阅读
gradle/libs.versions.toml和当前官方文档;若无法访问文档,必须标明代码未经验证。
不适用场景
| 任务 | 改用 |
|---|---|
| 首先确定任务路径和要读取的文件 | 先使用 compose 技能,再进行以下任何操作 |
| 编写或审查 composable、列表、动效、无障碍功能、令牌、资源 | compose-ui 技能 |
| 编写或审查 repository、Ktor、Room、DataStore、Paging、离线优先功能 | compose-data 技能 |
| 新建项目或模块、约定插件、版本目录、CI、钩子 | compose-project 技能 |
commonMain 共享、expect/actual、iOS/Swift、桌面端、Web | compose-platform 技能 |
不可妥协的原则
铁律:明确指出缺口,绝不编造或提供空壳。 未经验证的辅助工具应标记为待解决的缺口,绝不能调用。不得让任何
TODO、存根或空操作函数体进入完成状态。删除它,然后从模板重新开始。
以下 1–7 条是不可妥协的原则。上述工作流中的 UiModel 选择是一个默认项:如果 ## Project decisions 中记录了项目决策(.composekit.conf 中的 UI_MODEL=always),则以此为准,无需争论;否则,只有在触发 M-11 条件时才添加这对内容(参见 compose-architecture 技能中的 naming-and-packages.md)。
这些规则适用于你编写的代码。审查或规范现有可运行代码时,偏离规则属于需要报告严重程度的发现,而不是重写代码的理由;请遵循审查和规范路径。
- 只基于经过验证的项目资料构建。 新代码中提及的每个辅助工具、组件、令牌和导入,都必须在本次任务期间于此项目中见过,或出现在当前官方文档中。名称看似合理,并不代表经过验证。防止: 编造出无法在任何地方编译的 API。
- 占位内容不得进入完成状态。 已更改文件中不得残留任何
TODO、FIXME、存根或已记录但未修复的缺陷。在完成之前,对已更改文件执行的占位内容搜索必须无结果。模板中的SEAM注释必须实现,不得随代码提交。防止: 冲刺结束时,虚构内容却被标记为已完成。 - 铁律:每个文件只能输出一个版本。 写之前先做决定。备选方案应在代码之前用文字说明;文件一旦出现,方案就必须已确定。没有例外:绝不能在同一答案中提交“初稿……修正版”这样的版本对;如果初稿有误,就替换它,绝不能两个版本都交付。阻止某个文件的审查必须为每个受阻文件提交一个且仅一个修正版;只用文字说明修复方法的审查结论并不完整。防止: 给出三个候选方案,却没有一个被采纳。
- 只有记录的身份无法使用时才丢弃;字段有问题时应降级处理。 完整规则见
compose-data技能(规则 4):缺失值在整个领域层中保持为 null;只有 id 缺失时才丢弃记录。防止: 悄无声息地丢失紧急记录,或生成看似有效的假值。 - 复用组件时,必须一并复用其调用位置的条件。 组件复用的门控规则见
compose-ui技能(规则 6)。防止: 受先例门控的 UI 在新位置失效。 - 每个
UiState字段都必须由 UI 读取,每个UiAction都必须由 UI 分发。 不得为了“以后再用”而保留无用字段,也不得保留没有发送方的无用操作。在完成之前,依据 Contract 交叉检查 Screen。防止: 契约逐渐失效,却无人渲染。 - 只有对新颖且难以撤销的选择,才提供备选方案。 规定好的工作直接构建。工具包已经做出的选择就照此构建,不要再讨论。防止: 审查时反复争论已确定的架构。
工作流
- 为以下每个步骤创建一个待办事项,并按顺序执行。
- 重述切片及所有可观察状态:冷启动加载、协调、刷新中、错误、重试、空状态、未找到、加载重叠、进程死亡后恢复。
- 在项目中找到最接近的先例并完整阅读。小型任务只阅读直接相关的文件。
- 开始编写前,盘点现有组件、格式化器和设计令牌。
- 编写任何 Compose 代码前确定分层:始终执行 DTO 到领域模型的转换;仅在触发 M-11 条件时才执行领域模型到 UiModel 的转换(注明触发条件)。
- 列出生命周期和并发情况:冷启动加载与协调、加载重叠、进程死亡后深层目的地的恢复。
- 仅在模式不明确时阅读
examples.md。 - 简要规划;交付物是文件:步骤 1–5 保持为不超过 25 行的精简清单计划,然后按固定顺序编写文件:Contract → ViewModel → Route/Screen → DI/nav → 测试。
- 使用
scripts/new-feature.sh搭建脚手架,或编写最精简且正确的代码。优先使用特定于功能的代码,而非通用框架。 - 在组合根中注册新功能的 Koin 模块。避免: 目的地可以编译通过,却无法解析其 ViewModel。
scripts/new-feature.sh --name Notes --item Note --package com.example.feature.notes --root <project-root>
- 运行以下验证关卡。
- 用直白的语言报告偏差:哪些地方不一致,以及何时需要重新审视。
决策表
搭建脚手架还是手动编写
| 情况 | 操作 |
|---|---|
| 在符合 kit 结构的模块中新建目的地 | 使用 new-feature.sh 搭建脚手架,然后实现标记的 SEAM |
| 修改现有目的地 | 手动编写最精简且正确的差异;绝不重新搭建脚手架覆盖现有代码。绝不删除、重命名或移动任务未指定的现有可用行为(操作、状态字段、效果、测试、路由、错误处理接线,例如 HandleAppErrors)。看起来像遗留代码或与改动冲突的代码应保留;用一句话将其报告为后续事项。仅在收到请求时才重构。添加任务所需的内容(新操作、状态、仓库操作、列表渲染、接线)始终在范围内;“仅在收到请求时才重构”指不得重写或移动现有可用代码,绝不意味着可以拒绝实现所请求的功能 |
| 先例不一致(存在相互竞争的模式) | 新代码采用 kit 结构,指出不一致之处,并单独提出迁移方案 |
丢弃或降级有问题的记录
| 信号 | 操作 |
|---|---|
| 缺少或无法使用身份标识(没有 id) | 丢弃记录 |
| 非身份字段缺失或格式错误 | 保留该行;将字段设为 null,或将其标记为降级 |
在 DTO 边界处想用零、当前时间或 "" 代替的任何情况 | 停下;缺失值在整个领域模型中都应保持为 null |
危险信号
| 想法 | 实际情况 |
|---|---|
| “我先带着这些 TODO 发布;下个迭代再处理。” | 不行。功能规则 2:占位内容不能进入完成状态。 |
| “暂时用这个存根仓库没问题;测试在它上面能通过。” | 不行。功能规则 2:存根会让错误状态和空状态无法通过验证。 |
| “这个方法应该是存在的。” | 铁律:指出缺口;绝不调用虚构的方法。 |
| “我先留一个空操作的函数体。” | 不行。铁律:绝不把空操作当作真实逻辑发布。 |
| “我之后再验证辅助方法的名称;看起来没问题。” | 现在就停下来验证(功能规则 1)。M2 模型发布了虚构的 API。 |
| “两份草稿展现了我的思路;读者可以自己选。” | 不行。功能规则 3(铁律):先做决定,再输出一个版本;绝不发布一对草稿。 |
| “我用文字描述修复方案;修正后的文件显而易见。” | 不行。功能规则 3:阻塞性审查必须为每个阻塞文件准确提供一个修正后的版本。 |
| “我列出测试用例行,不实际编写它们。” | 不行。验证门槛 18(状态矩阵测试):声称覆盖的矩阵行必须落实为书面测试。 |
| “这条记录缺少一个字段;我把它默认设为零。” | 不行。功能规则 4:缺失值保留为 null;只有身份信息损坏时才丢弃记录。 |
| “我丢弃这行;它的日期解析失败了。” | 不行。功能规则 4:降低该字段的数据质量,保留这一行。 |
| “我复制了这个组件;这个条件只适用于那个屏幕。” | 不行。功能规则 5:复制组件时也要复制其门控逻辑。 |
| “这个 UiState 字段以后再用;UI 很快就会读取它。” | 不行。功能规则 6:每个字段都必须读取,每个操作都必须派发。 |
“我会在 init 中加载,也会在启动时加载,以防万一。” | 不行。架构规则 9:init {} 中的加载或收集,加上启动触发器,会形成两个所有者;首次加载只能由启动触发器(LifecycleStartEffect)负责。 |
| “我从缓存列表中解析详情;这样更快。” | 不行。架构规则 10 和 15:详情必须按身份信息获取;冷缓存中没有列表。 |
| “内容上方刷新失败可以不提示。” | 不行。架构规则 8:只有明确指定的轮询才允许静默处理。 |
“文件级 var 是最简单的结果回调。” | 不行。架构规则 13:结果必须通过仓库写入。 |
“我会计时执行 runCurrent(),以捕获加载帧。” | 不行。验证门槛 18(状态矩阵测试;testing.md 中的 Fakes 规则):让 fake 在整个加载帧期间保持打开,而不是计时执行 runCurrent()。 |
验证
- 编码前已复述切片及所有可观察状态(冷加载、协调、刷新、错误、重试、空状态、未找到、加载重叠、进程死亡后恢复)。
- 已完整阅读最接近的先例;已盘点组件、格式化器和令牌。
- 每个
*Contract.kt中恰好包含*UiState、*UiAction、*UiEffect(参见compose-architecture技能的规则 4)。统计每个文件中的声明数:
rg --files -g '*Contract.kt' <feature-root>
-
rg -n "TODO|FIXME|NotImplementedError" <changed-files>无结果;每个模板中的SEAM均已实现或记录为偏差:
rg -n "TODO|FIXME|NotImplementedError" <changed-files>
- 完成前,
rg -n "SEAM" <module>无结果;每个硬编码的 UI 字符串均已提取到资源文件:
rg -n "SEAM" <module>
- 每个文件恰好只有一个版本;没有“或者”草稿。
- 每个
launchGuarded都显式传入onError(参见compose-architecture技能的规则 6);没有try/catch链;重新抛出CancellationException。 - 每条失败路径恰好使用一个层级(架构规则 8);每个 Retry 都保留其错误。
- 第一次
ON_START是冷加载,后续的ON_START会在保留数据的同时进行协调;每个LifecycleStartEffect都以 nav-key id 为键。 - 每次加载都使用已存储的
Job防止重叠;有任务处于活动状态时跳过加载。 - 详情目标通过 key 中的标识获取数据(参见
compose-architecture技能的规则 10、15);缓存冷启动时会重新获取记录。 - 草稿保存在
SavedStateHandle中,并派生出UiState(参见compose-architecture技能的规则 9–10);没有rememberSaveable镜像,也没有用于同步的LaunchedEffect。 - UI 会读取每个
UiState字段;也会分发每个UiAction。 - 项目或当前文档中均能找到每个命名的辅助函数、令牌和导入项;没有杜撰内容。
- 每个被调用的仓库方法都已在其接口上声明。
- 丢弃的记录都缺少可用标识;数据降级时保留其行并使用 null 值,绝不臆造默认值。
- 每个 locale 文件夹中都存在对应字符串,且键完全一致(参见
compose-ui技能的规则 9)。 - ViewModel 测试使用手写的 fake 覆盖状态矩阵;通过确定性方式完成测试(任何兼容 fake 的空闲时间推进方式均可)。
- 所改动的模块可通过 common 元数据和一个平台的编译;其 JVM 测试通过。
- 已安装时,
scripts/composekit/run-checks.sh以 0 退出。 - 差异中没有删除任务未提及的内容:是或否。
- 已交付请求的功能(或指出一个阻塞因素及解除阻塞所需的最小步骤):是或否。
- 已用通俗语言报告偏差:偏离了什么,以及何时重新审视。
参考资料查阅
仅加载此任务需要的参考资料。只深入一层。
-
新目标只有在脚手架模板未涵盖时,才另行阅读 mvi-contract.md。
-
连接目标只有在脚手架模板未涵盖时,才另行阅读 navigation.md。
-
更改现有状态时,另行阅读 state-ownership.md。
-
只有更改失败路径时,才另行阅读 error-handling.md。
-
testing.md — ViewModel 测试约定、fake、状态矩阵行。
-
ui-testing.md — Compose UI 测试规则:查找器、断言、同步、延迟列表、状态恢复、KMP 运行器。
-
review-mode.md — 使用这些门槛审查他人的功能变更。
-
examples.md — 12 组错误/正确示例;仅在模式不明确时阅读。
-
README.md — 占位符、脚手架命令、组合根入口形式。