为什么只有界面每次都要过人手
代码质量早就有自动装置。lint 拦住违规,类型检查对齐形状,测试守住行为。这三样不用人盯着也会跑,违反了就合不进去。
界面不是这样。对比度有没有崩、卡片是不是套了两层、移动端文字会不会被截断,通常由评审者用眼睛判断。于是评审者忙的时候就过了,换个人标准也跟着变,同一条意见几个月后又冒出来。
开始把界面交给 AI 之后,这个问题会放大。人一天做出来的界面,代理一小时就能做出来,而评审仍然是人的速度。不把“可以自动检查的部分”和“确实需要人看的部分”分开,瓶颈就原封不动地留着。
机器能判断的范围比想象中宽:对比度、行高、可点区域大小、标题层级跳级、卡片嵌套、配色——凡是能用数值判定的都算。把这些下沉到 CI,人就可以专注在层级与含义上。
一个退出码就是整道关卡
Impeccable 的 detect 通过退出码报告结果:有问题以非零结束,没问题为 0。CI 把非零结束的命令视为该步骤失败,所以工作流里写一行命令,中间不需要任何 action 或插件,关卡就成立了。
实际跑一遍是这样:拿一张写满常见毛病的 HTML 去跑,得到 17 条、退出码 2;只修被指出的项目再跑一次,得到 0 条、退出码 0。流水线就靠这一个差别分岔。
值得接的位置有两处。一处以源码目录为对象,另一处以预览部署地址为对象。下一节会说明,这两者抓到的东西并不相同。
不调用 LLM 这一点在 CI 里尤其重要。不必把 API 密钥放进机密变量,同样的输入给出同样的结果,运行时间与成本都可预期。判定会随模型回答漂移的检查,是不能当关卡用的。
- name: Design check
run: npx impeccable detect src/
# 想连预览部署一起检查
- name: Design check (deployed)
run: npx impeccable detect ${{ steps.deploy.outputs.preview-url }}对象决定检查方式
命令是同一条,但给它什么,内部做的事就不同。HTML 文件会连同引用的 CSS 一起做静态分析。JSX、TSX、CSS 之类的其他文件用模式匹配扫一遍。给网址则用真实浏览器把页面打开,看渲染完成后的画面。
这个差别正是实务中容易犯迷糊的地方。只扫组件源码,能抓到以数值呈现的项目,却会漏掉只有拼装之后才显现的项目。比如紧贴标题上方的小标签,或者最终的对比度值,都要等画面组装好才能判定。源码扫描报 0,线上地址却被指出,原因就在这里。
所以如果只接一道关卡,预览地址那边抓得更多。代价是速度:网址检查要启动浏览器,比源码扫描慢。每次推送跑源码扫描、预览就绪后再跑网址扫描,是比较稳妥的组合。
选项知道四个就够。json 是机器可读输出,适合生成报告或发到 PR 评论;scope 把检查限定在类型、布局这类范围;viewport 用于网址检查时改变宽度,以移动端再跑一遍;no-advisory 则直接隐藏被归类为参考性的发现。
npx impeccable detect --json src/ > findings.json # 生成报告
npx impeccable detect --scope type,layout src/ # 限定范围
npx impeccable detect --viewport 390x844 <URL> # 以移动端宽度再跑
npx impeccable detect --no-advisory src/ # 隐藏参考性发现例外连同理由留在代码里
设了关卡就一定会需要例外。品牌选定的字体可能正好在过度使用的名单上,或者某个页面刻意偏离规则。这时不要整体关掉检查,而是用两种方式记下来。
第一种是文件内注释。写 impeccable-disable,后面跟规则名和理由,就在那个文件里豁免;也有只豁免一行的写法。例外就贴在代码旁边,文件被移动或删除,例外也随之消失。
第二种是仓库配置。用 ignores 命令把规则、文件路径或某个具体取值登记为例外,会作用于整个仓库,并且可以一并写下理由,方便日后追溯为什么放行。
两种方式有个共同点:例外留在文件里,而不是留在对话里。评审中口头达成的例外传不到下一个人,写进仓库的例外能传下去。
<!-- impeccable-disable overused-font -- 品牌指定字体 -->
/* impeccable-disable-line dark-glow */
npx impeccable ignores add-value overused-font Inter --reason "Brand font"
npx impeccable ignores list想在提交之前就拦下来
CI 是最后一张网。等东西已经做好了才被拦下,就要付返工的代价。所以在受支持的工具上会一并装上编辑时刻运行的钩子:UI 文件被改动的瞬间跑检测器,把结果送回代理的流程里。
各工具介入的时点不同。Cursor 会在错误的写入落地之前拦住,其余工具则在修改完成之后提示。无论哪种,都是在人打开评审之前先过一道。
要不要用钩子,安装过程中会问。如果不喜欢自动化打断编辑流程,可以只接 CI。反过来,把大量界面工作交给代理的团队,从钩子上得到的回报更多——指摘立刻回来,代理才能在同一段上下文里改掉。
