作者:互联网 时间: 2026-07-22 08:49:16
AI 已经可以在几分钟内生成一个完整页面,但“代码写出来了”和“界面实现正确”之间,仍然隔着一段很长的距离。

举一个实际开发中很常见的例子:设计稿里的图标容器是 27×27pt,图片资源的逻辑尺寸是 54×54pt。AI 正确设置了 UIImageView 的 frame,却把 contentMode 写成了 center。从源码和布局数据看,容器尺寸完全正确;运行以后,图标却因为没有缩放而明显偏大。只有把它改成 scaleAspectFit,最终效果才符合设计。
这个问题揭示了 AI 编写 UI 时的一处关键盲区:
Astrolabe(星盘) 是一个开源的运行时 UI 检查工具,目标是让 AI coding agent 能够读取正在运行的 App,检查节点结构、布局和样式,观察真实截图,并用运行时证据判断自己写出的 UI 是否正确。后文统一简称为 astrolabe。
截图能够告诉 AI“这里看起来不对”,却很难单独回答“为什么不对”。例如一个按钮位置异常,可能来自约束错误、父视图尺寸错误、Safe Area、Transform,也可能只是截图比例处理错误。
反过来,只读取视图层级也不够。frame 正确不代表资源缩放正确,颜色属性正确也不代表遮罩、透明度和混合后的最终像素正确。
astrolabe 将三类证据放在同一条工作链路中:
| 证据 | 回答的问题 |
|---|---|
| UI 层级与节点关系 | 页面由哪些对象组成,它们如何嵌套 |
| 运行时属性与结构化断言 | frame、字体、颜色、可见性、图片模式等逻辑值是否正确 |
| 系统截图与视觉差异 | 用户最终看到的像素是否符合预期 |
AI 因此可以完成一条真正闭环的开发流程:
读取设计要求-> 修改 UI 源码-> 编译并运行 App-> 检查运行时节点与截图-> 定位差异原因-> 修改源码并重新验证
astrolabe 目前由三个开源仓库组成:
| 仓库 | 职责 |
|---|---|
astrolabe | Swift Host、CLI、MCP Adapter 和 AI Skill |
astrolabe-runtime-ios | 集成在 Debug App 中,采集 UIKit 与 Core Animation 运行时数据 |
astrolabe-protocol | 定义平台无关的 Wire Protocol、JSON Schema、Fixture 和 Swift DTO |
一次检查请求会经过下面的链路:
AI Agent-> astrolabe Skill-> TypeScript MCP Adapter-> Swift Host-> 模拟器 Loopback TCP / 真机 USBMux-> iOS Runtime-> UIKit / CALayer
Runtime 在主线程读取 UIKit 对象,在越过线程和进程边界前将其转换为不可变的协议数据。Host 负责 App 发现、快照管理、节点查询、断言、截图、Baseline 和视觉差异分析。MCP Adapter 只负责工具协议适配,不重复实现检查逻辑;Skill 则告诉 AI 什么时候应该抓取页面、如何复用快照,以及什么证据能够支持什么结论。
这种拆分让 UI 采集、通信协议、产品能力和 AI 工作流保持独立。未来增加 Android Runtime 时,可以复用 Host、MCP 和平台无关协议,而不需要把 iOS 的 UIKit 语义带到 Android 中。
astrolabe 已支持 iOS 模拟器与 USB 真机,核心能力包括:
| 能力 | 用途 |
|---|---|
| App 发现 | 找到已经启用 Runtime 的模拟器或真机 App |
| 页面概览 | 提取当前屏幕中最值得检查的文本、控件和图片节点 |
| 层级与节点查询 | 按文本、类型、语义角色、可见性等条件定位节点 |
| 节点详情 | 读取 frame、字体、颜色、图片、圆角、边框、阴影、无障碍和约束等属性 |
| 布局与样式断言 | 对节点位置、尺寸和样式执行结构化检查 |
| 原生分辨率截图 | 获取模拟器或真机的最新屏幕像素 |
| Visual Diff 与 Baseline | 比较当前页面与目标图或历史基准的像素差异 |
| 冻结快照 | 让后续查询持续基于同一个页面层级事实 |
| 临时属性实验 | 在内存中调整允许修改的展示属性,快速验证 UI 假设 |
这些能力既可以通过 CLI 使用,也可以作为 MCP tools 交给 Codex。AI 不需要解析面向人类的 Inspector 界面,而是直接消费稳定、结构化且可查询的数据。
真实 App 的 UI 层级远比一个 Demo 页面复杂。窗口、容器、布局包装层、重复 Cell、不可见节点、Backing Layer 和 UIKit 内部节点会共同构成一棵庞大的树。如果将完整 JSON 塞进模型上下文,不仅成本高,真正有价值的业务节点也很容易被噪声淹没。
文件压缩解决不了这个问题。即使使用 gzip 将网络传输体积压小,解压后的全部文本仍然需要进入模型上下文。
astrolabe 使用的是领域语义压缩:完整层级保存在 Host 的本地快照中,MCP 响应只返回当前任务需要的投影,并保留 snapshotId、节点 ID 和分页游标,使 AI 能够继续追踪原始事实。
以一次生产环境 USB 真机页面测试为例:
| 数据 | 规模 | 紧凑 JSON 大小 |
|---|---|---|
| 完整页面层级 | 2,753 个节点 | 2,532,537 字节,约 2.42 MiB |
| 默认推荐节点 | 12 个节点 | 2,620 字节,约 2.56 KiB |
单次推荐结果相对完整层级缩小约 966.6 倍,数据量减少约 99.90% 。
这不是简单地截断前 12 个节点。处理过程包括:
完整的 2.42 MiB 层级事实并没有被丢弃。AI 如果发现某个推荐节点值得继续检查,可以通过节点 ID 获取完整详情;如果推荐结果不包含目标,也可以继续使用 find_nodes 在同一个快照中检索。
语义压缩的目标不是让 AI“少看一点”,而是让它先看到最有价值的部分,同时保持结果可解释、可恢复和可继续查询。
移动页面是动态的。AI 抓取页面后,用户可能滚动列表、打开弹窗或切换路由。如果每个工具都重新抓取当前层级,一次检查流程可能混合多个时刻的数据。
astrolabe 会为页面层级生成 snapshotId。后续的查找、分页、节点检查和样式断言只要携带这个 ID,就会继续处理同一份层级事实。
快照同时明确了证据边界:
这套约束看起来比“每次都取最新数据”麻烦,却能阻止 AI 使用时间上不一致的证据得出确定结论。
排查 UI 时,人类开发者经常在调试器里临时改一个颜色或字号,先确认方向,再回到源码正式修改。AI 同样需要这种快速反馈。
astrolabe 允许 AI 对白名单内的展示属性执行内存补丁,例如文本、字号、颜色、透明度、圆角、边框、阴影和部分约束值。不同属性可以组合,同一属性也可以反复调整,并且始终保留首次修改前的原值以便回滚。
这项能力有严格边界:
例如 AI 怀疑一个标题应该使用 20pt 而不是 15pt,可以先临时调整并观察真实页面。如果判断成立,再修改源码、重新编译,并在没有活动补丁的干净进程中完成最终验收。
如果你的工作流中已经让 AI 编写 iOS UI,astrolabe 可以用于:
它不会替代单元测试、快照测试、XCUITest 或人工设计验收。它补充的是这些工具之间长期缺失的一层:让 AI 能够直接读取运行中的 UI,并将结构、属性和像素组织成可验证的证据。
项目目前支持 iOS 和 Codex,Host 运行在 macOS,Runtime 只在 Debug 构建中启用。安装 Host:
git clone https://github.com/regulusleow/astrolabe.gitcd astrolabenpm run install:codex
iOS App 通过 Swift Package Manager 集成 astrolabe-runtime-ios,启动 Debug App 后,Codex 就可以通过 MCP 发现并检查页面。
项目地址:
astrolabeastrolabe-runtime-iosastrolabe-protocolastrolabe 的最初灵感来自 Lookin 团队在 iOS UI 检查领域的探索。Lookin 及 LookinServer 对运行时视图层级、属性读取和对象通信的实践,为这个项目提供了重要启发。在此感谢 Lookin 团队及所有参与开源贡献的开发者。
astrolabe 当前正式支持 UIKit,暂不支持 SwiftUI。虽然 Runtime 可能读取到 UIHostingController 生成的部分 UIKit hosting hierarchy,但这些节点不能等同于 SwiftUI 的声明式视图树,因此不会将其作为稳定的 SwiftUI 检查能力对外承诺。
接下来的两个主要方向是:
平台会继续扩展,但核心目标不会改变:让 AI 获得结构化、可验证且能够追溯的运行时 UI 证据。
AI 编写 UI 的速度已经很快。接下来真正重要的,不只是让它生成更多代码,而是让它能够看见运行结果、理解差异,并对自己的实现负责。