开发流程
1. 文档定位
本文档定义 ViewCompose 当前开发协作流程。
目的不是增加流程负担,而是解决两个真实问题:
- 功能开发跨多个阶段,容易把不同改动混在一起
- 线程中断、附件损坏、上下文丢失后,需要快速恢复工作
因此,后续开发默认遵守本文档,除非任务本身明确要求不同流程。
2. 小步提交原则
每完成一个可独立验证的小步骤,立即提交一次。
小步骤的判断标准:
- 能单独描述目标
- 能单独验证
- 不依赖把多个无关修改捆在一起才能成立
例如:
- 新增一份专项规划文档
- 落地一个最小宿主抽象
- 修掉一条独立 bug
- 补一组单元测试
- 补一个 demo 页面
- 补一条 instrumentation 回归
禁止:
- 把多个无关 bug 修复混成一个提交
- 把“文档规划 + 大段实现 + 多组测试”长期堆在工作区不提交
2.1 底层不稳定问题抢占规则
不稳定的底层契约、所有权模型或核心实现,优先级高于发版窗口便利性、当前计划阶段和新增 Roadmap 工作。一旦证据确认设计本身不成立,必须暂停依赖它的扩展,在责任层直接替换,并在 一次硬切中完成调用方迁移,同时删除旧 API、Transport、Fallback 与兼容分支。已确认的底层修正 不得推迟到后续阶段。时序 Guard、弃用但无效的字段、调用方专用 Wrapper 或并行 Legacy 路径都 不能替代底层模型修正;证据、迁移和发布影响必须明确记录。
3. 文档同步原则
开始实现前先按文档治理规范判断影响类型; PR 必须列出同步更新的 KDoc/Javadoc、模块文档或跨模块文档。若判断无文档影响,也必须说明 不改变公开 API、行为、架构、兼容性或维护流程的具体理由。
新增或修改公开/受保护 API 时,必须在实现前确定 Q 等级,并在同一 PR 内按照
源码文档与 API 注释规范补齐所有参数、返回值、状态、生命周期、
线程、失败和平台契约;Q3 API 同步提供可编译 @sample。既有注释欠账不能作为新增欠账或
“后续再补文档”的理由。
同一变更还必须提供符合 Governance V2
公共能力影响契约的结构化 capability-impact 记录。
实现前要确定稳定 capability owner,并处理 KDoc、模块、sample、Reference、Tutorial、Guide、
Architecture、Migration 与 redirect。每个检测到的结构化 capability 变更,都要在
docs/project/records/documentation-governance-v2/impacts/ 下新增一条不可变记录。编译门禁会将其
与 PR 精确基准比较,并拒绝缺失、复用、陈旧、重复或 owner 不匹配的 impact。
涉及下面任一情况时,必须先更新文档,或和实现同步提交:
- 新能力方向
- 架构边界变化
- 新测试策略
- 新 demo 模块规划
- 新宿主/容器语义
- 文档里已经登记过的技术债、架构点、roadmap 项被修复或优化
优先更新对应分类中的当前有效文档,例如:
补充要求:
- 如果这次代码改动直接修掉了文档中提过的一个问题点,不能只改代码不改文档
- 这种场景下,文档更新和代码更新必须在同一步内完成,或紧邻提交完成
- 文档中的“当前问题 / 剩余问题 / 后续计划”要随实现状态一起收口,不能长期滞后于代码
3.1 独立发布模块的发布意图
每个 PR 在合并前都必须判断 Maven 发布影响。自动归属发现会影响发布的源码、模块构建元数据或
编译 API sample 时,必须为该 PR 新增一份不可变 release/changes/<unique>.json。直接制品改动
填写 breaking、feature 或 fix;只有检测路径不改变公开契约或制品时,才能用 ignored 并
写明具体理由。禁止手写 dependency,它由发布规划器根据当前 Gradle project graph 推导反向依赖。
纯测试、Demo、benchmark 和手写文档默认不影响发布。根目录共享构建输入无法只凭路径确定影响, 必须声明受影响制品,或说明不发布的具体理由。Changeset 合并后只读并永久保留为审计记录。即使 使用 squash 或 rebase,发布意图也属于 PR,而不是每个中间 commit。
本地运行 ./gradlew verifyViewComposeReleaseIntent;它已进入 qaQuick,CI 会与精确 PR base SHA
比较。release owner 按发布流程使用
planViewComposeRelease 和 prepareViewComposeRelease。
4. 测试与 demo 补齐原则
只要功能进入“已实现”状态,就应补齐对应验证资产。
默认顺序:
- 单元测试
- demo 场景
- 必要的 demo UI 测试
如果某一步暂时做不到,提交说明里必须明确缺什么、为什么缺。
4.1 完成态门禁命令
统一命令入口:
- 快速门禁:
./gradlew qaQuick - 预览快照门禁:
./gradlew qaPreview - 全量门禁:
./gradlew qaFull
qaQuick 负责编译核心模块、运行单元测试,并执行包含语言放置与已审阅翻译指纹在内的统一文档
门禁。qaPreview 运行 :viewcompose-preview:verifyPaparazziDebug,作为独立可见的视觉 CI 检查。
只有审阅生成图片与差异后,视觉变更才能更新已提交基准;禁止仅为通过门禁而录制原因不明的差异。
仓库 Gradle 门禁统一使用 JDK 21。当前构建基线为 Gradle 9.3.1、Android Gradle Plugin 9.1.1 与
Kotlin 2.2.10;发布的 Runtime Library 仍使用 Java 11 字节码。仓库暂时通过
android.builtInKotlin=false 与 android.newDsl=false 保留显式 Kotlin Android Plugin 和旧版
Android DSL;AGP 10 会移除这条退出路径,因此只能在 Convention Plugin 与 Publication Tooling
协同迁移时同时移除两项。Preview 使用 SDK 37 编译;一个仓库所有的测试资源会把 Android
Robolectric 默认 Runtime 固定在其支持的 SDK 35。仅当测试明确命名并验证特定 API 级别的兼容性
契约时,才可以选择更低的 SDK;两种测试选择都不会降低任何生产 Compile SDK。
PR 工作流会先从 tools/viewcompose-quality-build 独立运行 planPullRequestImpact;该入口只配置这个
Included Build,不配置 Android 多项目构建。分类器读取精确 base-to-head Git diff、发布产物目录、
依赖契约和冻结的全量回退策略。其 JSON 与 Job Summary 会列出所选门禁族、直接产物、传递依赖、
反向依赖、直接非发布项目、原因、工作流选择和全量回退状态。纯文档与 website 变更可跳过 Android
子任务;发布模块生产代码会选择发布意图、API 文档、文档、模块以及依赖图可达的 Preview 门禁;
模块测试、Demo、sample、集成测试和 benchmark 各自保留显式门禁族与项目归属。变更路径超过
300 条、出现未知路径、命中敏感共享输入、事件不是 PR、diff 为空,或 PR 带 full-verification
标签时,一律选择当前全部工作流。
新增的 release/changes/*.json 文件按只追加发布意图分类,可以随其他可收敛范围的生产改动一起
执行。修改、删除、复制或重命名 Changeset 仍会选择完整验证,发布注册表与发布工具也一样。这是
不可变意图新增与可变发布基础设施之间的硬切,并非普遍放宽 release/**。
分支保护 Context 继续精确使用 qaQuick 与 Build documentation;qaPreview 可见,但当前不是必需
Context。每个可见 Context 都由 always() 结果门面报告:只有分类成功且明确未选择子任务时,跳过
才算成功;规划失败或已选子任务未成功都会失败。必需工作流仍对每个 PR 触发,因此路径过滤不会让
必需 Context 永久 pending。所有 main 与手工运行都会选择完整验证;只有完整文档子任务及其门面
都成功后才允许部署。
已选文档子任务会在恢复生成 API 候选前规划生成器指纹和完整不可变历史指纹。PR 永不保存该缓存,
成功的 main 子任务是唯一写入方。恢复键只是一条提示:每个 source revision 的条目集合和全部
文件大小/SHA-256 都必须验证后才能复用;陈旧或损坏组会删除并重建。Job Summary 会报告 hit、
partial、miss、recovery、复用/生成组数、无效组、有界并行度和装配耗时。源码、语言和翻译检查通过
verifyDocumentationStructure 只运行一次;CI 只生成一次站点目录,再调用 prepared type-check
与构建入口,避免重复 npm 预构建钩子。
除了稳定的 qa_quick 工作流选择外,分类器还统一持有类型化的 qaQuick 执行模式。skip 跳过
Android 工作,complete 只运行完整 qaQuick,affected-with-shadow 依次运行 qaAffected 与完整
qaQuick,affected 则只运行 qaAffected。工作流会在必需的 qaQuick 门面通过前校验所选模式和
两个步骤的结果;不支持的模式或非预期的执行/跳过结果都会关闭式失败。
范围候选使用 4 GiB Gradle heap 和最多两个 worker。根构建独立重建当前
api/implementation/compileOnly/runtimeOnly 项目图,
分类器闭包只要与真实图不一致就失败;编译和单测任务从已配置项目动态选择,不读取手写产物任务表。
Demo、sample、集成测试和 benchmark 选择各自项目任务;只有依赖 Maven 坐标的 sample 消费者才
加入本地发布。文档与 Preview 继续由各自独立可见的工作流负责。
无 Shadow 的 affected 模式被严格限定为两个类别。第一类要求门禁所有权精确等于文档治理、文档
站点和 Tutorial sample,:samples:tutorials 是唯一非发布项目,不含发布产物,也没有全量回退原因。
第二类要求精确选择文档治理、文档站点、模块验证、Preview、发布意图和 sample,并至少包含一个发布
模块的 src/test/samples 源码;其他路径只能是文档、当前中文镜像、生成的能力目录、仅追加的
Changeset、Tutorial 主源码,以及可选的 Counter debug Preview 源码。模块生产源码、构建脚本、普通
测试以及删除或重命名的代码继续使用 affected-with-shadow,候选与完整门禁都必须成功。main、
手工运行和所有全量回退 PR 使用 complete;Demo、集成测试、benchmark、共享输入、未知路径和所有
未验收模块类别也保留完整对照。项目级本地 Gradle 默认配置不变。
2026-08-26 的本地验收使用了一份真实 Paging 历史 diff。qaAffected 为一个直接产物和十个依赖
产物选择 39 条任务路径,以 2 分 6 秒 通过(215 个 actionable task,其中 188 个执行、27 个
up-to-date);完整 qaQuick 随后以 8 分 5 秒 通过(2,342 个 actionable task,其中 2,096 个
执行、246 个 up-to-date)。候选耗时降低 74.0%,本地执行工作量结论为有改进,且两个结果
一致。但这不能证明上线后的延迟收益:它只有一个开发机样本,两条路径都继承了本地缓存,而且
候选先运行并部分预热了完整门禁。因此,托管环境延迟仍为结论不足;在 Phase 6 分变更类别
样本满足计划的观察与正确性条件前,继续保留完整影子对照。
PR #173 提供了该实现的第一份托管全量回退验收。候选按预期跳过,完整 qaQuick 以 19 分 37 秒
通过,qaPreview 为 8 分 41 秒,文档工作为 5 分 12 秒,所有结果门面均通过。相对紧邻的已验收
PR,qaQuick 变化 -0.17%,qaPreview 变化 -0.19%,两者均为无实质变化。文档耗时变化
-14.8%,但不可变缓存状态和输入不同,因此延迟结果为结论不足。一个全量回退样本可以证明
行为正确,不能代表分布;下一步仍是收集各类范围可收敛变更的观察数据。
11 个可比较的托管文档/Tutorial-sample PR(#177、#178、#179、#180、#182、#183、#184、#185、
#203、#204、#205)构成了已验收的无 Shadow 语料。每个候选都选择 1,176 个 actionable task,
不含发布产物,并与随后 2,342 个任务的完整 Shadow 得到相同成功结论。把 required critical path
重建为“从 Job 开始到候选完成的时间”与并行文档 Child Job 耗时两者中的较大值后,近邻秩 P50 为
6 min 22 s,P95 为 7 min 17 s,均满足 8 min/12 min 阈值。11 个文档 Child Job 全部精确
恢复并验证 5/5 个不可变 API 组,生成组和无效组均为零,精确命中率 100%。范围与缓存结论为
improved,零分歧的正确性结论为 no material change。验收当时,这些时间仍由 Shadow
运行重建,并非硬切后的实际观察,因此时延结论为 inconclusive,仍需一条真实 critical path。
该证据为第一个精确类别启用了 affected。
PR #225 提供了该类别硬切后的首个真实无 Shadow 运行。五条候选任务只覆盖
:samples:tutorials,以 6 min 9 s 通过,从工作流创建到必需门面完成用时 8 min 12 s,完整工作
按预期跳过。执行与端到端耗时分别比 Phase 0 P50 低 72.9% 和 66.0%。范围和时延结论为
improved,正确性为 no material change。单个运行不能建立分布;评估该类别 P50/P95 前还需
从自然出现的合格工作中累计九次成功运行。
11 个托管模块文档/编译样例 PR(#186、#187、#188、#189、#190、#191、#194、#195、#198、
#199、#200)构成了第二个已验收的无 Shadow 语料。每个 PR 只修改文档/治理记录、模块
src/test/samples 编译样例、受限的 Tutorial 或 Counter sample 源码、仅追加 Changeset、中文镜像
和生成的能力目录;11 个候选都与随后完整 Shadow 得到相同成功结论,分歧为零。重建的无 Shadow
执行路径近邻秩 P50 为 8 min 5 s、P95 为 10 min 4 s;端到端 P50 为 9 min 13 s、P95 为
11 min 18 s。执行 P50 比 Phase 0 的 22 min 43 s 低 64.4%。11 个文档 Child Job 全部复用
5/5 个不可变 API 组,生成组与无效组均为零。另一组 11 个成功 main 样本的完整 qaQuick Job
P95 为 20 min 41 s,比 Phase 0 低 16.2%,完整路径没有回退。范围、缓存复用和时延结论为
improved,正确性为 no material change。PR #196 因修改模块构建脚本继续使用
affected-with-shadow;生产源码、普通测试、代码删除/重命名、敏感工具和未知路径也不进入本次硬切。
硬切上线时,仍需首个符合条件的托管运行记录实际时延。
PR #226 提供了该类别硬切后的首个真实无 Shadow 运行。68 条候选任务覆盖 23 个发布产物和
:samples:tutorials,以 7 min 48 s 通过,从工作流创建到必需门面完成用时 9 min 42 s,完整
工作按预期跳过。执行与端到端耗时分别比 Phase 0 低 65.7% 和 59.8%。范围和时延结论为
improved,正确性为 no material change,因为所选闭包、Preview、文档和全部门面均通过。
合入后的 main 随后以 15 min 23 s 通过完整 qaQuick,工作 Job 用时 16 min 38 s,比已验收的
完整主分支 P95 低 19.6%,因此完整路径的安全性保持 no material change。单个运行不能建立
分布;评估该类别 P50/P95 前还需从自然出现的合格工作中累计九次成功运行。
第一组硬切后控制窗口(#219--#223)没有出现符合条件的无 Shadow affected 运行。#219 只修改
激活计划并正确选择 skip;从工作流创建到 qaQuick 门面完成用时 70 s,文档门面以
6 min 29 s 完成,比可比较的 #216 skip 观察减少 24 s(-5.8%),成功结论一致,时延为
no material change。#220--#223 分别因共享质量/站点工具、发布构建逻辑,以及发布或文档历史
元数据而正确选择 complete。四次完整 Gradle qaQuick 分别为 13 min 2 s、16 min 53 s、
16 min 11 s 和 15 min 2 s;近邻秩 P50/P95 为 15 min 2 s/16 min 53 s,比 Phase 0
执行对照低 33.8%/31.6%。必需端到端关键路径分别为 15 min 15 s、24 min 33 s、
18 min 3 s 和 16 min 55 s,P50/P95 为 16 min 55 s/24 min 33 s,比 Phase 0 低
29.9%/45.2%。所有门禁和必需门面均通过。安全性保持 no material change,这组小型、
异构的完整路径样本相较 Phase 0 为 improved;由于目标类别运行数为零,实际无 Shadow 时延仍
为 inconclusive。这五个同日且偏重发布的变更只是控制证据,不能代表变更类别分布。继续等待
自然出现的文档/Tutorial-sample 或模块文档/编译样例 PR 来扩展两类硬切后语料;不得仅为制造观察
数据而修改样例。
所有调用 Gradle 的工作流都由 gradle/actions/setup-gradle 单独负责 Gradle User Home 缓存。
actions/setup-java 只安装所需 JDK,不再另行缓存 Gradle。每个 setup-gradle 都显式设置
cache-read-only:除仓库默认分支外的任何 ref 只能恢复缓存,PR 和非默认分支不能写入,只有默认
分支 Job 可以写入。禁止再增加并行的 actions/cache Gradle Home 缓存,也不得恢复 setup-java
的 cache: gradle;是否采用 Gradle Build Cache 或 Configuration Cache,必须分别测量并验收。
首个隔离 Paging 候选探针表明 Build Cache 有潜力,但还不能进入必需 CI。依赖预热并清理输出后,
无缓存基线以 80.88 秒 通过;两次清理后的缓存恢复分别为 6.04 秒 和 5.45 秒(-92.5% 与
-93.3%),215 个 actionable task 中有 108 个从 9.9 MiB 缓存恢复。每轮结果都正确,因此
本地结论为有改进。托管环境可移植性、main 到 PR 的复用、总容量和淘汰行为尚未测量,Build
Cache 继续关闭。Configuration Cache 也继续关闭:同一候选在本地以 4.78 秒 存储、1.29 秒
复用(-73.0%),但当前 CI 没有第二次相同调用,也未配置加密的跨 Job 传输;其当前路径结论为
无实质变化。以后只能通过独立托管影子重新评估,禁止与其他门禁或文档工作捆绑启用。
qaFull 在 qaQuick 基础上增加应用、Counter sample 和教程的连接设备测试。仓库内每个
connectedDebugAndroidTest 入口会先运行 verifyConnectedAndroidDeviceReady。前置检查要求:未通过
ANDROID_SERIAL 指定设备时只能有一台在线设备、系统已完成启动、屏幕处于唤醒状态且 keyguard
没有显示。该检查不会绕过安全锁屏;重试前必须唤醒并解锁所选设备。能力标记为“完成”前,默认要求
qaFull 通过;若当前缺设备或存在临时豁免,必须在 roadmap 写明豁免范围和补齐时间。
4.2 远程 Demo APK
维护者不在开发电脑旁、但需要可安装构建时,可以从 GitHub Actions 页面运行 Demo APK Workflow。
该流程从所选 Git Ref 构建使用 Debug 签名的 app APK,并上传 SHA-256 校验文件和构建元数据,
保留 14 天。该 Artifact 只用于人工验证框架,不是 Release Package。
5. 新增代码归类原则
新增代码必须先判断“属于哪个模块、哪个目录层级”,再开始落文件。
要求:
- 不接受为了赶进度,把新代码平铺进当前目录
- 不接受把平台实现、DSL、runtime、demo 代码混放
- 如果现有目录没有合适落点,先更新文档说明,再新增目录
默认判断顺序:
- 先判断模块职责边界,例如
viewcompose-runtime、viewcompose-ui-contract、viewcompose-animation-core、viewcompose-animation、viewcompose-gesture-core、viewcompose-gesture、viewcompose-graphics-core、viewcompose-graphics、viewcompose-ui-foundation、viewcompose-constraintlayout-androidx、viewcompose-renderer-android、viewcompose-host-android、viewcompose-lifecycle-androidx、viewcompose-viewmodel-androidx、app - 再判断目录职责边界,例如
context/、dsl/、runtime/、view/、defaults/ - 最后才决定具体文件名
执行要求:
- 新功能实现前,先阅读相关架构文档和同模块已有代码
- 如果发现“当前改动能跑,但文件落点明显不合理”,应优先纠正结构,而不是把技术债留到后面集中处理
- review 时,模块归属和目录归属属于必查项,不是可选项
5.1 反平铺约束
为避免目录再次退化为平铺,新增约束:
- 同一目录源码文件建议上限:
12,超过后必须按职责拆分子目录。 - 命中上限时优先按“领域/控件族群”拆分,不按人名或临时阶段拆分。
- 目录重排必须与文档更新同一步完成(至少更新架构总览的目录基线)。
- 目录重排默认不改公开 API;若必须改包名或 API,需单独提交并给出迁移说明。
5.2 环境来源一致性
新增映射或扩展框架能力时,环境来源必须遵守单一入口,不允许另起一套:
- 宿主侧环境语义统一来自
viewcompose-ui-foundation/context/Environment与UiEnvironment。 - Android 环境提取统一通过
AndroidEnvironmentBridge进入UiEnvironmentValues。 - renderer 不新增环境语义通道;只允许使用 renderer 内部尺寸工具(
viewcompose-renderer-android/view/DimensionUtils.kt)做平台换算。 - 禁止在 renderer 容器类新增私有
density缓存或dpToPx/spToPx辅助方法。 - 发现现存代码偏离以上约束时,必须在同一步改动里完成“代码修正 + 文档更新”。
5.2.1 Lifecycle / ViewModel API 落点
生命周期与 ViewModel 协作能力的新增/修改必须遵守:
collectAsState/collectAsStateWithLifecycle放在:viewcompose-lifecycle-androidx(com.viewcompose.lifecycle)。viewModel/savedStateHandle放在:viewcompose-viewmodel-androidx(com.viewcompose.viewmodel)。- 宿主默认 Local 注入由
viewcompose-host-android的 host bridge 负责,不在上述模块重复实现注入逻辑。
5.3 Root 作用域集成装配
普通应用 Root 显式选择集成。SPI Discovery 只保留给底层中立扩展,反射仅作为最后兜底且需单独评审:
- Activity、Fragment、Navigation 与具名 Design System Root 显式构造各自 Root 作用域 Overlay Host;Classpath 顺序不得选择设计系统。
AndroidOverlayHostFactoryProvider + ServiceLoader只供自定义底层 Host 使用,并且只发现一个 中立viewcompose-overlay-androidProvider。Material 与 One UI Adapter 都不注册完整 Host Provider。- 可选 View 装饰后端必须通过
AndroidViewDecorationBackend + ServiceLoader接入;renderer/host 禁止反向依赖具体阴影实现,缺失后端时必须稳定 no-op。 - 若确实需要反射(临时兼容场景),必须在同一步补充架构文档与契约测试,并登记移除计划,不得长期保留。
5.4 Local API 一致性
新增 Local/主题作用域能力时,必须遵守统一范式:
- 对外只使用
uiLocalOf、UiLocals.current、ProvideLocal、ProvideLocals。 - 禁止新增专用
ProvideXxx风格包装方法,避免语义分叉与维护成本膨胀。 - 变更 Local 机制时,必须同步补齐 snapshot/lazy/overlay 传播回归测试。
- 发现旧实现仍使用专用包装时,优先在同一轮改造中收口到统一 API,并同步更新文档。
5.5 NodeSpec-Only 语义边界
节点语义扩展必须遵守单轨模型:
- 新增语义字段只允许进入
NodeSpec或Modifier,禁止引入动态Props。 - 禁止新增或回引
Props/TypedPropKeys/PropKeys/node.props。 - renderer binder 读取节点语义时,必须使用显式 spec 读取(不可静默 fallback 到默认 spec)。
- 若确需新增元数据(如锚点),必须通过 modifier 元素或明确的 spec 字段传递,不得用隐式 map 透传。
- 相关变更必须同步更新 node-spec.md 与对应守卫测试。
5.6 节点组重组稳定性约束
涉及 SlotTable Lite 组级重组能力的变更,必须遵守:
emit同层 group 的 key/顺序必须保持稳定;新增循环或条件分支时优先显式 key。- 若设计上无法保持稳定,必须接受“最近稳定祖先回退重组”语义,并补充对应测试。
- 禁止通过关闭告警或吞异常掩盖结构漂移;结构漂移必须可观测(日志/诊断可见)。
emit参数变化(spec/modifier)必须可触发组级重组,禁止出现“参数变化但组被错误复用”。- 相关改动至少补一条 Runtime/UI Foundation 单测验证组复用与回退行为。
5.7 状态快照一致性约束
涉及 MutableState、RuntimeObservation、ComposerLite 的改动,必须遵守:
MutableState写入必须走 snapshot 事务(显式MutableSnapshot或 autocommit),禁止新增绕过事务的写路径。- mutation 去抖与并发冲突语义统一通过
SnapshotMutationPolicy实现,不允许在调用侧散落自定义判等逻辑。 - 并发冲突场景必须覆盖三类测试:无冲突、merge 成功、merge 失败。
- compose 一轮内的读取一致性必须有单测约束,防止“同一轮读值漂移”回归。
- 调整 snapshot 语义时,必须同步更新 state-snapshots.md。
- 在组合阶段发生“先写 mirror state 再读回”时,禁止把该回读值用于控制流(协程启动、任务调度、版本选择);这类判定必须读取实时内核字段,并补对应回归用例。
5.8 组合事务与结构化协程约束
涉及 ComposerLite、Effect、Flow、动画或协程 API 的改动,必须遵守:
- 组合结果必须经过 prepare/commit/abort;renderer 失败不得提交 slot、观察订阅或 Effect。
DisposableEffect、SideEffect、LaunchedEffect只能在成功提交后启动。- 失败候选中的
RememberObserver必须走onAbandoned,不能走onForgotten。 - 业务可见异步任务必须属于
RenderSession组合 Job;禁止新增CoroutineScope(SupervisorJob())独立根。 - 自定义 dispatcher/context 只能覆盖非 Job 元素;携带
Job必须 fail-fast。 - 协程相关改动至少覆盖:Key 重启、条件移除、失败组合不启动、Session 销毁、子任务异常隔离。
- renderer 事务回归至少覆盖:同层中途失败、递归子树失败、新节点释放、旧 View 顺序与绑定恢复。
AndroidView.update/onReset/nativeView必须可重放且只修改传入 View;外部不可重放副作用必须使用成功事务后执行的onCommit。- 组合和 renderer 的事务日志必须与本轮 touched scope/mutated node 数量相关;禁止重新引入每帧全树 checkpoint。
- renderer 快速路径调整必须验证:稳定 VNode/List 引用保持、
SkipSubtree不进入 children、诊断关闭时不做深度结构统计。 - 重复失效优化必须保留“组合进行中再次失效”的下一帧语义,并覆盖同帧多次写只调度一次。
RecomposeBoundary内普通 Kotlin 捕获值必须通过inputs声明;snapshot state 不需要重复声明。- 新增 render/session 失败路径必须映射到结构化
RenderFailure阶段与恢复状态,并验证单个失败不会阻断后续提交期回调或清理。
5.9 帧对齐调度约束
涉及 RenderSession、失效调度与测试等待机制的改动,必须遵守:
- 状态失效重绘统一走
FrameAlignedRenderDispatcher+Choreographer,禁止新增container.post主调度路径。 RenderSession.render()必须保持立即执行语义;若调整语义,必须先更新架构文档并补全回归用例。- 调度器改动必须覆盖 4 类单测:同帧合并、取消、重入下一帧、跨线程请求去重。
- instrumentation 若依赖“UI 空闲后断言”,必须保证等待至少一个 frame,避免调度升级后误报。
- session
dispose()路径改动必须验证“销毁后无延迟渲染”。
5.10 Renderer 单源注册约束
涉及 renderer binder/differ 的新增或重构,必须遵守:
NodeType -> binder、NodeViewPatch -> patch applier、NodeSpec -> patch factory只允许在NodeBinderDescriptors维护。- 禁止在
NodeViewBinderRegistry或NodeBindingDiffer新增并行手工映射表。 - 新增节点能力时必须先补 descriptor,再补对应 binder/patch 逻辑。
- 变更完成后必须跑 descriptor guard tests,确保覆盖与一致性无缺口。
NodeBinder*.kt源码必须放在view/tree/binder/core/descriptor/,禁止平铺回core/根目录。- 若目录结构回退,必须在同一提交恢复目录收敛并补结构守卫测试。
5.11 模块依赖边界约束
- 每个运行时模块必须且只能登记为 Kernel、UI Foundation、Android Engine、Design System、 Integration 或显式 Consumer Aggregate;Tooling 单独登记。
- 依赖只能指向同层或门禁允许的低层。Tooling 禁止进入公开运行时依赖,任意
viewcompose-*模块禁止依赖app。 - UI Foundation 主源码禁止导入 Renderer、AndroidX 或 Material API;UI Contract 主源码禁止
导入
android.*或androidx.*。 - 中立
ComponentActivity/Fragment.setUiContent只位于viewcompose-android;具名setMaterial3UiContent位于viewcompose-material3-android;renderInto与AndroidView/nativeView保留在底层viewcompose-host-androidEngine。 - Material Theme Policy 只位于
viewcompose-material3,Material Activity/Fragment 与 Presentation 接线只能位于名称明确的 Integration。UI Foundation、Renderer Android、Host Android 与中立 Android 聚合模块禁止导入或依赖 Material。 qaQuick中的verifyModuleDependencyBoundaries与verifyDesignSystemIsolation是不可豁免 硬门禁;禁止只靠 Code Review 口头维持边界。- 公开依赖按 Consumer 暴露而不是实现便利性分类:public/protected 签名类型与明确的入口聚合使用
api;完全属于私有实现的依赖使用implementation。caller-owned 平台集成是唯一例外,且 必须在模块手册与外部 Consumer 测试中写明,不能根据已有implementation声明反推。 - 普通应用只声明实际使用的 Aggregate 与可选 Feature。禁止把下层坐标写成修补不完整 Maven 元数据的必需依赖。
- 所有直接 ViewCompose 发布边必须在同一变更中登记到
gradle/viewcompose-dependency-contracts.properties。verifyViewComposeDependencyContracts会阻断契约与 Gradle 声明漂移。 - 新增或修改入口必须增加最小外部 Consumer 编译测试。发版前,本地仓库检查必须验证
api保持为 Maven compile scope、implementation保持为 runtime scope。 - 依赖暴露变更属于发布输入变更;同一 PR 必须更新所属模块手册并添加不可变 Release Intent。
- 首次 Central 发布前,仓库 Maven Sample 只有在门禁先将当前 Checkout 发布到
build/maven-repository,再消费生成 POM 时,才能使用新坐标。发布后必须在没有生成仓库的 干净 Checkout 中再次验证安装路径。
5.12 开发工具隔离
可在应用进程内执行的开发工具遵循 ADR-0009:
- 具体 Preview、Inspector、源码导航与 IDE Transport 实现只能位于 Tooling 模块。Runtime 模块 可以暴露可空中立端口,但不能持有具体 Tooling 协议、Report Writer、Request Receiver 或 IDE 生命周期。
- 激活必须同时满足:可选 Tooling 制品存在、进程可调试、收到有效显式请求。禁止把
debuggable解释为持续观察许可。 - 非活动 Tooling 不得在滚动、全局布局、绘制、触摸、Animation Frame 或重组安装 Listener;不得 执行 View Traversal、Stack Capture、序列化或文件 I/O;不得持有提前启动的 Worker。任何狭窄 例外都需要 ADR、Allowlist 与同设备 Benchmark 证据。
- 优先使用一个带 Nonce 的请求与一份响应 Snapshot,不维护持续刷新的报告。确定性测试必须验证 Nonce、进程、包名、大小上限、生命周期清理、陈旧响应拒绝与失败隔离。
- Runtime 变更运行
verifyDevelopmentToolingIsolation。观察热路径的 Tooling 还必须对比相同 Debug Build、设备、Workload、刷新率和温度状态;空闲滚动期间 Tooling 写入次数必须为零。 - PR 必须明确说明应用进程 Tooling 是否变化、Runtime 所有权为何仍中立、如何证明 Release Classpath 排除,以及收集了哪些非活动路径证据。
5.13 模块单包根约束
涉及新增模块、包路径重构或文件迁移时,必须遵守:
- 每个模块仅允许一个包根前缀,覆盖
src/main、src/test、src/androidTest。 - Android 模块
namespace必须与该模块包根一致(viewcompose-ui-contract例外)。 - lifecycle/viewmodel 对外包名固定为
com.viewcompose.lifecycle与com.viewcompose.viewmodel,并且源码必须放在各自模块。 qaQuick中的verifyModulePackageRoots与verifyAndroidModuleNamespaces为硬门禁,任何违规不得豁免合并。
5.14 Runtime 纯度与测试覆盖约束
涉及 viewcompose-runtime 的改动,必须遵守:
viewcompose-runtime固定为 Kotlin/JVM 模块,禁止回退 Android library 形态。- runtime 主源码禁止
android.*/androidx.*import,且 runtime 构建禁止引入androidx.core.ktx。 qaQuick中的verifyRuntimePurity为硬门禁,违规必须阻断合并。- runtime 关键分支(policy/snapshot/observation/invalidation/composer)变更必须同步补单测,禁止只改实现不补回归。
5.15 Host 会话与诊断边界约束
涉及 RenderSession、host 诊断回调或会话创建路径改动时,必须遵守:
- Android 会话执行细节(frame clock/dispatcher)只放
viewcompose-host-android,UI Foundation 仅保留RenderSessionRuntime契约与 provider。 - 聚合层的
setUiContent与 Engine 的renderInto禁止暴露 renderer 实现诊断类型;统一使用 core 诊断类型RenderStats/RenderTreeResult。 - lazy item 子会话与 overlay surface 子会话必须通过会话契约创建,禁止直接 new 平台具体实现类。
- 相关重构必须补边界守卫测试,至少覆盖“禁止 renderer 类型泄漏到 host public API”与“provider 缺失回退 no-op”两条路径。
5.16 Modifier 与容器策略边界
涉及 Modifier 或容器策略相关改动时,必须遵守:
viewcompose-ui-contract的Modifier仅维护“全局稳定语义”的元素与 builder API,禁止新增“仅特定容器生效”的策略型 modifier。reusePolicy与motionPolicy必须进入容器 DSL 参数和NodeSpec,由 Renderer 直接读取。原生 焦点后代可见性这类正确性要求属于不变量,不是 Opt-in 策略。- Pager 驻留与直接输入控制仍是显式容器字段。禁用直接输入不得禁用状态命令或程序化焦点可见性。
- 新增策略类型必须同轮补齐 DSL->NodeSpec 映射测试与 Renderer Bind/Patch 生效测试。新增 Boolean 前必须先证明该行为确实可选且只有一个稳定所有者。
5.17 开发预览约束
涉及组件新增、组件行为调整或视觉语义调整时,必须同步维护开发预览资产:
:viewcompose-preview-core只承载 Preview 注解、确定性配置和版本协议,主源码禁止android.*/androidx.*import。:viewcompose-preview-runner只负责原生 View 静态挂载、截图和诊断导出,禁止 Compose 与 IDE SDK 依赖。:viewcompose-preview的PreviewCatalog是组件预览单源,新增组件时必须补PreviewSpec。- Paparazzi 快照测试必须消费同一份
PreviewCatalog,禁止单独维护第二套截图样例。 qaPreview为硬门禁;修改组件视觉语义后必须更新快照基线并通过完整协议/运行器/快照测试。- preview 模块禁止依赖
:app,禁止 import demo 包路径。 - preview worker 和 IDE 插件只允许通过带
protocolVersion/requestId的结构化数据协议通信。 - overlay 在 preview 场景只允许静态模拟,真实弹窗行为回归必须落在 instrumentation。
5.18 动画与手势约束
涉及动画/手势能力新增或改造时,必须遵守:
- 动画能力分层固定为
:viewcompose-animation-core(内核)+:viewcompose-animation(DSL 集成)+:viewcompose-host-android(interop);手势能力分层固定为:viewcompose-gesture-core(策略内核)+:viewcompose-gesture(DSL 入口)+ renderer(Android 事件适配)。 - Android 高阶动画能力(
TransitionManager/MotionLayout/Animator)仅允许通过:viewcompose-host-androidinterop API 暴露。 graphicsLayer语义变更必须同步补 renderer patch/rebind 稳定性测试,禁止通过全量 rebind 兜底。- 手势事件消费规则固定为“手势优先,未消费再 clickable 回落”;涉及冲突策略修改时必须补“子手势 vs 父滚动容器”回归。
- 列表/分页动画能力默认 opt-in;改动容器
motionPolicy/reusePolicy语义时必须补容器回归与文档说明。 AnimatedVisibility必须走NodeType.AnimatedVisibilityHost承载尺寸动画;隐藏语义固定为“exit 动画结束后再移除 subtree”。pointerInput仲裁语义变更必须补“Consumed 强短路”回归:pointerInput消费后,transform/drag/anchoredDraggable/combinedClickable均不可再触发。- transform 阈值语义变更必须补单测覆盖 slop 三路径(pan/zoom/rotation)与 instrumentation 覆盖双指平移/旋转变化。
- anchored settle 语义变更必须补单测覆盖“速度触发/距离触发/最近锚点”三路径,禁止仅凭人工回归上线。
updateTransition语义必须保持“单 transition 多 channel 共享时间线”;AnimatedVisibility必须复用该时间线,不允许回退到多自动画时钟拼装。Modifier.animateContentSize(...)必须保持布局级尺寸动画语义(父布局可观察到连续尺寸变化),禁止回退到graphicsLayer缩放假象。AnimatedSizeHost实现改动必须覆盖“展开 + 收起”双向视觉连续性,禁止出现只展开平滑、收起瞬跳的回归。- 手势策略算法(axis lock / transform slop / swipe settle)变更必须改在
:viewcompose-gesture-core,renderer 仅允许阈值采集与事件分发适配。 combinedClickable在enabled=true但无回调时必须保持 no-op,不得消费触摸流;语义变更必须补回归测试。
5.19 ConstraintLayout 约束
涉及 ConstraintLayout 能力新增或改造时,必须遵守:
- 组件 DSL 与 scope 只放
:viewcompose-constraintlayout-androidx;renderer 只做 AndroidConstraintLayout映射与约束应用。 layoutId/constrainAs/constrain属于 parent-data,错误宿主必须触发ModifierParentDataValidator警告,禁止静默忽略。- 同一 child 同时存在 inline 约束与 decoupled
ConstraintSet时,必须保持 inline 优先并输出一次 warning。 ConstraintDimension与Modifier.width/height/size冲突时,必须保持约束 dimension 优先。- 新增 guideline/barrier/chain/Flow/Group/Layer/Placeholder/constraintSet 语义时,必须同轮补 DSL 单测 + renderer 单测 + demo UI 回归锚点。
Barrier(allowsGoneWidgets = ...)参数必须真实生效,禁止仅保留参数但在 renderer 侧静默降级。- chain
weights与referencedIds数量不一致时必须 fail-fast(DSL)并在 renderer 输出一次可定位 warning。 - 约束新增
min/max/percent/constrained、baselineToTop/baselineToBottom、circle语义时,必须同轮补齐 DSL 发射断言与 renderer 应用断言。
5.20 Graphics 分层与绘制语义约束
涉及 graphics 能力新增或改造时,必须遵守:
viewcompose-graphics-core仅承载平台无关图形模型与 draw command,禁止引入android.*/androidx.*。viewcompose-graphics仅承载 DSL 与业务 API(Canvas、drawBehind/drawWithContent/drawWithCache),禁止直接落 Android Canvas 执行细节。- renderer 仅做
DrawCommand -> Android Canvas/Paint/Path执行映射与 patch 接入,不允许在业务层重复实现绘制命令。 drawWithCache变更必须补 cache 命中与失效断言,禁止通过每帧重建缓存绕过回归。- Android 专属图形扩展(
RenderEffect、RuntimeShader、Drawablebridge)必须落在viewcompose-host-androidinterop,禁止回流graphics-core或graphics。 - graphics 视觉语义变更必须同轮更新
viewcompose-preview的PreviewCatalog与 Paparazzi 快照基线(qaPreview硬门禁)。
6. 线程中断恢复原则
如果聊天线程丢失、附件损坏或上下文中断,恢复顺序固定为:
git log- 当前工作区
git diff - 根目录 roadmap / architecture 文档
- 最近失败日志或测试报告
- 最后才依赖聊天记录回忆
也就是说:
项目真实上下文以仓库状态为准,不以聊天线程为准。
7. 提交信息原则
提交标题必须直接描述当前这一个最小步骤。
推荐格式:
docs: ...feat: ...fix: ...test: ...refactor: ...
示例:
docs: add overlay components roadmapfeat: add overlay host contractfix: refresh dialog content on state updatestest: add snackbar presenter coveragetest: add overlay demo instrumentation
8. 当前执行约定
当前项目默认采用下面这条执行顺序:
- 先规划
- 再做最小实现
- 每完成一小步立即提交
- 再进入下一小步
这条约定的目标不是追求提交数量,而是保证:
- 每一步都可回退、可审阅、可恢复
- 任何线程丢失后,都能从仓库状态继续工作
9. 文档分层约定
文档的完整目录、命名、链接和生命周期规则统一由 文档治理规范定义。
基本分层:
- 当前入口:
docs/README.md - 长期规范:
docs/architecture/、docs/guides/、docs/tooling/、docs/project/ - 跨会话执行计划:
docs/project/plans/ - 历史审计/快照:
docs/archive/
根目录只保留项目入口和社区治理文件,不再承载功能、架构或计划文档。
10. 执行计划防丢失约定
对于“跨多步、跨多天”的任务,必须先创建执行计划文档,并持续回写状态。
执行规则:
- 计划文档放在
docs/project/plans/,使用小写 kebab-case 名称 - 每完成一小步(且完成一次提交)后,立即更新计划文档中的 checklist 与执行日志
- 计划文档必须记录:当前基线、完成标准、未完成项、下一步
- 全部完成后,将长期结论回填到对应有效文档,再把计划迁入
docs/archive/ - 同步检查路线图和相关有效文档中的“进行中/未完成/Next/待推进”标记,避免状态漂移