# hmos-incremental-ui-align

HarmonyOS-Android UI 自动对齐流水线。

## 它解决什么问题

以往对齐鸿蒙和安卓 UI 的流程需要：
1. 人工把设备点到目标页面
2. 手动跑 parse 脚本截图 + dump view tree
3. 肉眼对比差异
4. 手写改鸿蒙源码

每多一个页面/弹窗/tab，上述步骤都要重复一遍，非常费时。

本 skill 做了三件事：
- 自动**寻路**（视觉模型根据自然语言点到指定页面，模型复用 `HOMETRANS_MODEL_*` 统一配置）
- 自动**采集** view tree + 截图（安卓走 adb、鸿蒙走 hdc）
- 自动**对比 + 改码**，并按 MVVM 模式把 mock 数据放到 Model 层

用户只需要一句自然语言 + 点击路径。

> ⚠️ 当前只保证 **UI 对齐**，不保证功能行为对齐。

---

## 前置条件

1. **设备连接**
   - 安卓设备：已装目标 App，`adb devices` 能看到
   - 鸿蒙设备：已装目标 App，`hdc list targets` 能看到
   - 两台设备建议都保持亮屏解锁

2. **运行时依赖**
   - 采集脚本 `page_capture.ts` 与寻路脚本 `app_feature_verify.ts` 均为 TypeScript 版，用 Node 直接运行（Node ≥ 22.18 或 ≥ 23.6，原生剥离类型，无需编译、零第三方依赖）。确认 `node --version` 即可，无需 Python / uv。

3. **模型配置**（寻路导航用）
   与自测共用同一个多模态模型：`ht init` 把模型四要素（api_key / name / base_url / provider）导出为 `HOMETRANS_MODEL_*` 环境变量。脚本优先读环境变量，缺失时回落 `~/.hometrans/autotest.yaml`；都没有时兼容老版本的 `GLM_API_KEY`（走智谱 GLM 端点）。无需单独设置 API Key。

4. **鸿蒙 SDK 路径**（给改码阶段查 API 用）
   取自 OS 环境变量 `OHOS_SDK_PATH` / `HMS_SDK_PATH`（为空时由 `DEVECO_SDK_HOME` 派生），由 `ht init` 写入机器环境变量；例如 DevEco Studio 自带的 `E:\DevEco Studio\sdk\default\openharmony\ets`。

---

## 入参与配置

本 skill 需要两类信息：

**① 调用时传入的入参**（每次按需提供）：

| 入参 | 必填 | 说明                                                                 |
|---|---|--------------------------------------------------------------------|
| `android_project_dir` | 是 | 安卓源码根目录                                                            |
| `harmony_project_dir` | 是 | 鸿蒙工程根目录（会被直接修改）                                                    |
| `capture_output_dir` | 否 | 采集产物输出目录（截图/view tree/分析报告）。默认 `<harmony_project_dir>/.hometrans/capture_output` |

**② 全局配置**（统一链路「环境变量 → config.json → 询问用户」，由 `ht init` 同时写入机器环境变量与 config.json，无需每次传）：

- 模型配置 ← `HOMETRANS_MODEL_*` 环境变量（缺失时回落 `~/.hometrans/autotest.yaml`；与自测共用同一个模型）
- 鸿蒙 SDK ETS API 目录 ← 环境变量 `OHOS_SDK_PATH` / `HMS_SDK_PATH`（为空时由 `DEVECO_SDK_HOME` 派生为 `<DEVECO_SDK_HOME>/default/openharmony/ets`、`.../hms/ets`）

> **Step 0.0** 会在执行前检查这些环境变量是否存在；缺失时会要求用户输入。

> 安卓/鸿蒙两端的 **App 显示名**（`android.app_name` / `harmony.app_name`）和 **包名**（`android.package` / `harmony.package`）也无需提供——skill 的 **Step 0.1** 会按 `android_project_dir` / `harmony_project_dir` 自动解析：
> - 安卓包名取自 app 模块 `build.gradle(.kts)` 的 `applicationId`（兜底 `AndroidManifest.xml` 的 `package`）；App 名取自启动 Activity / `<application>` 的 `android:label`（`@string/xxx` 会去 `res/values*/strings.xml` 解析，优先中文）。
> - 鸿蒙包名取自 `AppScope/app.json5` 的 `bundleName`；App 名取自 `app.json5` 的 `label`（`$string:xxx` 会去 `AppScope/resources/**/element/string.json` 解析，优先中文）。
>
> 若某项无法从工程目录解析，skill 会回到询问用户。

---

## 使用方式

在 Claude Code 里直接调用 skill，**把两个工程目录附在需求里**（可作为命名参数，也可在自然语言里说明）：

```
/hmos-incremental-ui-align android_project_dir: <安卓工程绝对路径> harmony_project_dir: <鸿蒙工程绝对路径>
<自然语言需求，描述要对齐的页面+点击路径>
```

### 写需求的三个要点

1. **页面路径写清楚**
   用「→」或「-」标注一级一级的点击步骤，比如 `首页 → 点击"AI智能填报" → 点击"专业"筛选按钮`。路径越具体，寻路成功率越高。

2. **说明是否要覆盖交互态**
   默认会自动扫描 tab、弹窗、下拉等交互元素并逐个采集。如果只想对齐主页面，请明确说「只对齐主页面，不管弹窗和 tab」。

3. **说明是否用 Mock 数据**
   默认用 mock 数据。如果想调真实后台，在需求里写「不要 mock 数据，调用真实后台」。

### 例子

**例 1（单个弹窗对齐）**
```
/hmos-incremental-ui-align 鸿蒙版本app(登录状态下，进入首页-点击"AI智能填报"-点击"专业"筛选按钮)
得到的弹窗样式和安卓同样路径得到的页面不一致，请修改鸿蒙源码将上述页面与安卓版本完全对齐
```

**例 2（补齐缺失页面 + 调真实后台）**
```
/hmos-incremental-ui-align 鸿蒙版本app(点击我的-超级会员组件)显示与安卓不一致，且安卓版本在超级会员
上点击"会员中心"会跳到超级会员弹窗页，鸿蒙也没有这页。请修改鸿蒙源码将这些页面与安卓版本
完全对齐，不要mock数据，调用真实后台数据
```

**例 3（多级路径 + 多个页面）**
```
/hmos-incremental-ui-align 鸿蒙版本（点击底部高考按钮 -> 带年纪查专业 -> 点击某一具体专业(如临床医学)
-> 点击就业分析，到达"专业就业健康度"展示页面）以及在"专业就业健康度"页面上点击"完整数
据指标"到达的就业健康度详情页都和安卓同一路径的不一致。比较这些页面的差别并将他们在视觉
效果上完全对齐，不要用mock数据，调用后台真实逻辑
```

---

## 运行过程中会发生什么

skill 会严格按 `SKILL.md` 里的流水线跑：

### Step 0 · 解析入参
读取调用入参 `android_project_dir` / `harmony_project_dir` / `capture_output_dir`，并按统一链路解析 `HOMETRANS_MODEL_*`（统一模型，链路「环境变量 → `~/.hometrans/autotest.yaml` → 询问」）与 `OHOS_SDK_PATH` / `HMS_SDK_PATH`（链路「环境变量 → `~/.hometrans/config.json` 的 `env.*` → 询问」；**Step 0.0** 会先做存在性检查，两层都缺失才要求用户输入）。随后 **Step 0.1** 按两个工程目录自动解析两端的 App 显示名与包名（无需手动配置）。

### Step 1 · 双端页面采集
1.1 解析你的需求，拆成一组「基础页」，每个基础页有 `android_nav_path` 和 `hmos_nav_path`
1.2 对每个基础页，两端分别：`app_feature_verify.ts` 寻路 → 成功后 `page_capture.ts` 截图 + dump view tree
1.3 扫描基础页 view tree，发现 tab、弹窗、下拉等交互元素，自动对每个子状态再采集一遍
1.4 扫描安卓源码里的动画/Toast/语音传感器等 API，产出 `dynamic_ui_inventory.md`（截图和 view tree 抓不到这些效果）
1.5 识别多状态组件（如语音输入的 idle/recording/cancelling），读源码建 `state_model.md`，用 `page_capture_burst.ts` 连续采帧捕捉每个状态的代表帧
1.6 鸿蒙端页面不存在时，寻路会失败，对应目录留空（Step 2 会改从源码读）

产物目录结构：
```
{capture_output_dir}/
  task_{timestamp}/
    android_page_1_{name}/
      screenshot.png
      view_tree.xml
      meta.json          # 含 dpi / density_factor，供换算用
    hmos_page_1_{name}/
      screenshot.png
      view_tree.xml
      meta.json
    android_page_1_{name}_popup_filter/
      ...
    dynamic_ui_inventory.md   # Step 1.4 产物
    state_model.md            # Step 1.5 产物（仅多状态组件存在时）
```

### Step 2 · UI 差异分析
主 agent 串行执行，不会下放给子 agent（保证质量）：
- **2.1** 读安卓 screenshot + view tree，写 `android_page_*/UI_Analysis.md`（组件清单 + 位置 / 颜色 / icon / 尺寸 / 对齐方式等），并附加 `dynamic_ui_inventory.md` 里与该页相关的动态效果
- **2.2** 读鸿蒙 screenshot + view tree，写 `hmos_page_*/UI_Analysis.md`；鸿蒙页不存在时改读源码写 `UI_Analysis_from_code.md`
- **2.3** 两份 Analysis 对比，按 `references/Comparison_Template.md` 生成 `UI_comparison.md`（静态组件 diff 表 + 动态UI diff 表 + 状态机 diff 表）
- **2.4** 验证所有 `UI_Analysis*.md` 和 `UI_comparison.md` 都已写全

所有尺寸都会以 `126px (3.0x → 42vp)` 的格式同时给出原始 px、**从各自 `meta.json` 读到的真实设备密度**、换算后的 vp（不再假设固定 3x）。

### Step 3 · 改鸿蒙源码
- 跑 `node ./scripts/extract_checklist.ts --task-dir "{task_dir}"` 确定性地从所有 `UI_comparison.md` 抽取 diff 项，生成 `{task_dir}/fix_checklist.md`（唯一 source of truth），并核对脚本输出的 `items >= diff_rows` 对账报告，避免人工枚举漏项
- 先探测工程状态管理范式（V1/V2，工程级判定一次）：新工程/0→1 转换默认 V2，已有 V1 的工程沿用 V1
- 按范式读对应文档：V1 读 `references/MVVM开发文档/`，V2 读 `references/MVVM开发文档V2/`（详见 `references/MVVM开发文档V2/_范式选择说明.md`）
- 读 `page_align.md` 学习转换规则
- 逐条修 diff，每修一个把 `- [ ]` 改成 `- [x]`；`[STATE]`/`[TRANSITION]`/`[ANIMATION]` 类条目需按 `state_model.md` 精确复制手势阈值和动画参数
- 每个尺寸 / icon / alignment 都要回溯到安卓源码的 XML 或资源值，不允许"看起来差不多"

### Step 4 · 编译校验
若可用，调 `hmos-fix-build-errors` skill 确保工程能编过。**不会自动部署**。

---

## 目录结构

```
skills/hmos-incremental-ui-align/
├── SKILL.md                    # 流水线定义（主 agent 执行逻辑）
├── README.md                   # 本文档
├── page_align.md               # Step 3 的转换规则
├── diff_analysis.md            # Step 2.3 的对比分析细则
├── scripts/
│   ├── app_feature_verify.ts   # 寻路工具（TypeScript，Node 直跑）
│   ├── page_capture.ts         # view tree + 截图采集工具（TypeScript，Node 直跑，含密度换算）
│   ├── page_capture_burst.ts   # 连续帧采集工具（多状态组件用，TypeScript，Node 直跑）
│   ├── extract_checklist.ts    # 确定性 diff→checklist 抽取工具（TypeScript，Node 直跑）
│   └── navigation-capure.md    # 各脚本的调用约定
└── references/
    ├── UI_Analysis_Template.md                # Step 2 分析模板（含动态UI/多状态章节）
    ├── Comparison_Template.md                 # Step 2.3 对比模板（含动态UI/状态机对比章节）
    ├── State_Model_Template.md                # Step 1.5 状态机建模模板
    ├── MVVM开发文档/                           # 鸿蒙 MVVM 参考（V1 状态管理）
    ├── MVVM开发文档V2/                         # 鸿蒙 MVVM 参考（V2 状态管理 + 范式选择说明）
    ├── android-to-harmonyOS-ui-layout-mapping-reference.md
    ├── android-to-harmonyOS-ui-atomic-component-mapping-reference.md
    └── android-to-harmonyOS-ui-interaction-mapping-reference.md
```

---

## 单独运行脚本（调试用）

跳过 skill 手动跑采集：

```powershell
# 安卓寻路（TypeScript 版，Node ≥ 22.18/23.6 直跑，无需 uv / PYTHONIOENCODING）
node skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts `
  --device adb `
  --app "Salt Player" `
  --package "com.salt.music" `
  --prompt "进入首页-点击AI智能填报-点击专业筛选按钮" `
  --max-steps 15

# 安卓采集（TypeScript 版，Node ≥ 22.18/23.6 直跑，无需 uv / PYTHONIOENCODING）
node skills/hmos-incremental-ui-align/scripts/page_capture.ts --device adb -o ./tmp/android_page_1_xxx

# 鸿蒙同理，把 --device adb 换成 --device hdc
```

> 注意：`--prompt` 模式会自动在前面加「打开{app_name}，」，所以 prompt 里不要再写"打开"。

---

## 常见问题

**Q: 寻路失败怎么办？**
A: skill 默认重试两次。流水线的内部规则：①先看页面是否存在；②路径错了就 force-stop 重试；③路径对但工具报错就让 agent 自己看截图判断是否到了。超过两次仍失败才会跳过该页。

**Q: 鸿蒙端页面根本不存在，能新建吗？**
A: 可以。Step 2.2 会改从鸿蒙源码读，Step 3 会按安卓的实现规格新建 `.ets` 文件到 `entry/src/main/ets/pages/`，并登记路由。

**Q: 为什么同一次任务会采集多个页面？**
A: Step 1.3 默认扫描交互元素（tab / 弹窗 / 展开收起等）并递归采集。想关掉请在需求里写「只对齐主页面」。

**Q: 改完会自动部署吗？**
A: 不会。Step 4 只跑编译验证（如果 `hmos-fix-build-errors` skill 可用）。部署自己来。

**Q: 能不能只做差异分析、不改码？**
A: 目前流水线是端到端的。如果只想要分析产物，跑完 Step 2 后手动中断即可，所有 `UI_Analysis.md` 和 `UI_comparison.md` 都会落盘在 `{capture_output_dir}/task_{timestamp}/` 下。

**Q: 动画、Toast 这类瞬态效果能对齐吗？**
A: 能。Step 1.4 会扫描安卓源码里的动画/Toast/语音传感器 API，产出 `dynamic_ui_inventory.md`；Step 2.3 会生成对应的「Dynamic / Transient UI Comparison」diff 表；Step 3 逐条修复时会写入真实的 ArkUI `animateTo()` / `promptAction.showToast()` 等实现。

**Q: 像"按住说话"这种有多个状态的组件怎么对齐？**
A: Step 1.5 会先建 `state_model.md`（状态枚举 + 转移条件 + 每状态属性，均需溯源到源码行号），再用 `page_capture_burst.ts` 连续采帧抓每个状态的代表帧。单击、松开长按等手势可自动触发；长按保持/拖拽/语音等手势需要人工在设备上操作，skill 会打印操作说明等你确认。

---

## 已知限制

- 只对齐 UI，不对齐功能/数据流
- 寻路依赖多模态模型对页面文本的识别，小字体或非标准控件可能识别不准
- 双端设备的分辨率/密度差异会影响像素到 vp 的换算，流水线已从各自 `meta.json` 读取真实 `density_factor`，但同一个 Figma 规格下仍可能出现 ±1vp 偏差
- 需要长按保持、拖拽、语音、传感器等物理输入触发的状态，无法全自动采集，需要用户配合手动操作
