截图 diff 是错误的 AI UI 收敛循环,designfit 通过提取 Figma 规格并与渲染结果做几何+token 比对,给出可操作的修复清单而非图像差异。
如果你曾经让一个 coding agent 对着 Figma 画框说"让它匹配",你就会知道失败模式是什么样的。agent 构建出大致正确的东西。你或者某个工具拿它和设计稿比较,被告知"还是不对",于是修改,再比较,然后又不对了。周而复始。问题不在 agent,在于对比方式。
这篇文章要回答的是:为什么最直观的对截图做 diff 这种方式,不能作为和 AI agent 闭环的正确原语,以及应该用什么取而代之。简短答案:不要 diff 像素,要测量几何和 token。这就是 designfit 的核心思路——一个我构建的开源 MCP server 和 Claude Code skill。它从 Figma 画框中提取设计规格,验证渲染出的前端是否符合规格,然后交给 agent 一份机器可执行的修复清单,而不是一张图片。
▶ 观看 35 秒演示:在一个 360 节点的 Figma 画框上运行真实案例,三次构建迭代从 87 分到 100 分。
截图 diff 的流程是:渲染你的实现,导出 Figma 画框,计算逐像素差异。这对于已经正确的 UI 来说是极好的回归测试工具。但对于 agent 正在主动构建的 UI 来说,它是一个非常糟糕的收敛工具。
渲染后的浏览器截图充满了并非错误的差异。抗锯齿会用中间色涂抹文字和圆角的边缘,这些颜色取决于每个字形精确的子像素位置。字体 hinting 和平台光栅化器会把元素移动亚像素级别。子像素布局舍入会把一个盒子 nudge 不到一个设备像素的位置。这些都不是设计错误,但每一个像素都会在 diff 中显示为"不同"。
所以 agent 收到的信号是"还是不对",但它没有办法区分真正错误的颜色和字母边缘一排抗锯齿像素。它持续修改。因为 diff 对 agent 无法控制的噪声过于敏感,这些修改并不会可靠地把数值驱动到零。分数摇摆,agent 震荡,你烧掉 token 和时间,却没有离"匹配设计稿"更近一步。
更深层的问题是确定性。一个有用的反馈循环需要相同输入产生相同输出。像素 diff 在不同机器、不同字体栈、甚至不同渲染结果之间都不具备这个特性,因为它的输入包含了光栅化器的噪声。如果测量本身不是确定性的,循环就无法收敛。
设计师对照 mockup 审查实现时,不会叠加两张图片然后搜寻不同像素。他们捕捉的是一小套结构化的东西:
颜色错误:那个按钮是错误的蓝色。
尺寸错误:那张卡片太宽了。
错位:那个元素偏离了它应该在的位置。
缺失或多余的元素:设计稿里的徽章没出现。
就这样。他们在对比少数几个有意义的、具名的属性与设计意图。每一项检查都可以确定性地产出:颜色就是颜色,宽度是像素数量,位置是坐标,元素存在或者不存在。
所以正确的原语不是"有多少像素不同",而是"实现违反了设计声明的哪些属性"。这个集合很小、很稳定、机器可执行——这正是 agent 需要的东西,用来真正修复问题而不是胡乱折腾。
designfit 验证两类东西,别的都不做。
设计 token。设计指定的可解析样式值:fill、color、fontFamily、fontSize、fontWeight、lineHeight、letterSpacing、borderRadius、borderColor、borderWidth、opacity。颜色用 CIEDE2000(ΔE)比较,这是一种感知色差,所以"感知上无法察觉的不同"不会判定为失败。数值 token 用像素比较。
几何。每个元素的边框盒(x、y、width、height),相对于屏幕根节点而不是绝对视口坐标比较。如果构建出的页面恰好居中或偏移,只要每个位置都先相对于根节点做了归一化,仍然能通过。你测量的是布局,不是整个页面落在哪里。
再加上存在性:设计期望的每个元素是否在 DOM 中,以及是否有标记了设计不知道的东西。
每项比较都有显式容差,所以亚像素和感知色差噪声永远不会触发问题。默认值:
| 检查项 | 容差 |
|---|---|
| 几何 | ±1 px |
| 颜色 | ΔE 4.0 |
| 字体大小 | ±1 px |
| 字重 | 完全匹配 |
| 圆角 | ±1 px |
| 间距 | ±1 px |
| 透明度 | ±0.02 |
你可以按项目收紧或放宽任何一个。
因为输入的是计算后样式值和边框盒测量值而不是光栅化图像,比较是确定性的:相同实现、相同设计,每次运行结果相同。这正是循环能够收敛的原因。它也让 agent 能够提前停止:如果连续两次运行分数都没有提升,说明最后一次修改没有改变 designfit 测量的任何东西,没什么值得继续震荡的了。
designfit 暴露两个 MCP 工具。designfit_extract 把 Figma 画框转换为验证所需的输入。designfit_validate 接收该输入加上运行中的 URL,返回 { pass, score, violations, unmapped }。Claude Code skill 驱动 agent 完成整个循环:
提取设计规格。给 designfit_extract 画框的 Figma 链接。它返回设计树(每个节点含其 frame 和 token)、组件映射和视口。绑定到属性的 Figma 变量和已发布样式被记录为该属性的 token 来源。
构建 UI,边构建边打标记。为节点构建的每个元素加上 data-designfit-id="<figmaNodeId>"。
对运行中的 URL 做验证。
重复修复直到 pass 为 true,或者如果分数停滞则停止并报告。
清理标记,除非你保留它们用于后续重新验证。
在第一版中,第 1 步不存在。agent 通过 Figma 自己的 MCP server 读取画框,然后手工组装规格。那是循环中出错率最高的步骤:frame 拼写错误或遗漏了 token 来源会产生一份与设计不符的规格,然后一个完全确定性的比较会确定性地检查错误的东西。
designfit_extract 用代码替代了人工操作。它用个人访问令牌从 Figma REST API 读取画框(Claude Code 插件只需请求一次),或者你粘贴 /nodes JSON。和工具的其余部分一样,它是确定性的:相同的节点 JSON 输入,相同的规格输出。隐藏节点被跳过,只包含矢量元素的画框(一个 icon)变成一个叶子节点,渐变、图片和效果被排除。
上面的演示是真实运行,结果比预期更有启发性。
这个画框是一个密集的深色仪表盘:提取后 360 个节点,耗时约 1.5 秒。我在三次迭代中验证了 25 个标记元素:
迭代 1:87/100,失败。17 个 error 和 7 个 warning:一个变量绑定的填充色有偏差,几个硬编码的颜色和圆角漂移了(warning,因为 Figma 里没有任何东西强制它们),以及一组几何错误。
迭代 2:89/100。token 问题消失了,但 14 个几何错误仍然存在。
迭代 3:100/100,通过。
其中两个几何错误根本不是尺寸错误。一个区块在构建中是 49 px 高,而在 Figma 中是 542 px;一张表格比设计位置高了 73 px。这两个元素的标记都打在了错误的节点上:标记打在了区块的行标题而不是区块本身,打在了表格的包装器而不是表格上。原来手工组装的规格是基于这些错误标记构建的,所以从未发现这个问题。从 Figma 直接读取画框才发现了。
在一个真实画框上运行 extract 还发现了 designfit 本身的两个 bug,均已在 0.2.1 中修复。Figma 的单侧描边粗细被忽略了,所以底部描边被当成了顶部描边。另外 CSS 的 letter-spacing: normal 被读作"无值",导致每个 Figma 文本节点的 letterSpacing: 0 都触发了 warning。
每个 violation 都标明了组件、检查类型、属性、期望值(有时含 token 来源)、实际值、偏差和修复提示:
{
"component": "Button/Primary#btn",
"check": "token",
"property": "fill",
"expected": { "value": "#1d4ed8", "source": "color/primary" },
"actual": { "value": "#2b6cf0" },
"delta": "ΔE 6.4 (#2b6cf0 vs #1d4ed8)",
"severity": "error",
"fixHint": "use color/primary (#1d4ed8)"
}
source 字段也是强制开关。绑定到 Figma 变量或已发布样式的属性会被强制执行:不匹配是 error,会导致运行失败并扣分。没有 token 在背后的硬编码值只是 warn:它会出现在修复清单中让你不要错过,但它永远不会导致运行失败,因为唯一的修复方法只能是另一个魔数。几何总是被强制的。pass 意味着零 error。
designfit 0.2 从 Figma 提取规格,然后针对一个视口运行 token、几何和存在性检查:画框设计时的那个断点。这就是今天产品的全部,有意为之。
明确写在路线图上、尚未发布的功能:命名 token 的间距(今天错padding和gap也能被捕捉,但只是作为几何偏差),跨多个断点验证,以及用于几何和 token 无法捕捉的判断的辅助视觉模型层。任何模糊的东西永远不会进入 pass/fail 路径。赌注是:大多数"让它匹配设计"的震荡来自于错误的颜色、错误的尺寸和缺失的元素,而确定性测量本身就能消灭它们。
它是免费的,采用 MIT 许可。在 Claude Code 中:
/plugin marketplace add as9978/designfit
/plugin install designfit@designfit
在任何其他 MCP client 中,npm install -g designfit。无论哪种方式,运行一次 npx playwright install chromium。然后粘贴一个 Figma 链接,让 agent 在它能测量的东西上闭环。
我是作者,独自构建这个项目。我很想听听 token 和几何模型在你们的画框上哪里失效了:提交一个 issue 来说明它漏掉的 case,或者它标记了但并非真实问题的 case。
Validate AI-built front-ends against their Figma design — deterministic token + geometry conformance, not pixels. MCP server + Claude Code skill.
Validate AI-built front-ends against their Figma design — without the screenshot-diff thrash.
A real run on a 360-node Figma frame: designfit_extract reads the frame from its link, then designfit_validate scores three build iterations, 87 to 89 to 100 pass. No screenshot diffing anywhere in it.
designfit is an MCP server + Claude Code skill that checks a rendered implementation against its Figma design and hands the coding agent a machine-actionable fix-list. It compares design tokens and geometry (element boxes relative to the screen root) — not raw pixels — so font-rendering noise never makes the agent oscillate. Deterministic in, deterministic out.
Why geometry, not pixels
Screenshot-diffing an AI-built UI against a Figma frame thrashes: anti-aliasing and sub-pixel shifts read as "still wrong," so the agent fixes forever. designfit compares what a designer actually catches — wrong colors, wrong sizes, misalignment, missing elements — as deterministic measurements with…