news 2026/9/7 14:21:34

LobeHub ux-audit 技能系列(一):Layer 1 静态代码审计(L1)——不跑界面、只看代码的 UX 基线检查法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LobeHub ux-audit 技能系列(一):Layer 1 静态代码审计(L1)——不跑界面、只看代码的 UX 基线检查法

LobeHub ux-audit 技能系列(一):Layer 1 静态代码审计(L1)——不跑界面、只看代码的 UX 基线检查法

【免费下载链接】lobehub🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub

导读:本文详解 LobeHub 仓库内.agents/skills/ux-audit技能三阶段审计模型中的Layer 1 — Static audit(L1 静态审计)。它以src/routes路由与src/features特性组件为输入,通过"读代码"判定一个界面该有的状态与交互模式是否存在于代码中(缺 empty/error 分支、无 retry、草稿未持久化、模式缺失等),是每次审计都执行的最廉价基线。读完本文,你将掌握 L1 的适用范围与边界、四步标准流程、状态缺口清单式检查法,以及如何把它与ux技能清单、pattern-catalog模式目录、L2/L3 层衔接成闭环。

本文面向的是 LobeHub 仓库中以技能化、可重复、证据驱动方式进行单界面 UX 审查的方法论文档。原文档位于.agents/skills/ux-audit/references/layer-1-static.md,是ux-audit技能的 L1 层流程文件。以下内容以该文档为主体骨架,结合其兄弟文件(SKILL.mdpattern-catalog.mdlayer-2-visual.mdlayer-3-dynamic.md)以及.agents/skills/ux/SKILL.md及其分模块清单展开。

L1 在整个审计体系中的定位

LobeHub 的ux-audit技能(见.agents/skills/ux-audit/SKILL.md)把一次界面审计拆成三个独立层级,每一层只对"它能看见的东西"下结论:

层级流程文件做什么能抓住的问题成本
L1 Staticlayer-1-static.md读代码缺失的状态/分支(empty/error/retry)、草稿未持久化、模式缺失、结构性问题便宜、离线、每次审计必跑
L2 Visuallayer-2-visual.md渲染截屏评审真实视觉层级与主导控件、间距/对比/对齐、截断/溢出、空/加载/错误态的实际观感、响应式、深浅色中,需要一次渲染
L3 Dynamiclayer-3-dynamic.md用 acceptance 框架驱动真实用户旅程并打点in-progress/locked 状态、强制错误/空状态、步骤 N 是否通向 N+1、焦点/键盘、量化 CLS/LCP/INP/long-tasks高,需要运行环境 + 登录态

L1 被称为 "fast, offline baseline",理由很直接:读代码不需要渲染环境、不需要账号、不需要截图,只要仓库在本地即可全量覆盖一个界面的所有代码路径,因此它在每次审计中都是必跑项,成本最低却给出完整的结构性覆盖。L3 动态审计假设acceptance技能的 Step 0(环境 + 认证)已就绪,且所有 CDP 驱动命令(agent-browser --cdp 9222 snapshot/screenshot/eval)在云端xvfb-run下也可无头运行——这些是 L1 无需承担的运行前提。

层间衔接遵循一条铁律:一条结论必须来自能看见它的那一层。这正是覆盖矩阵的意义——你可以在 L1 断言"没有 error 分支"(代码里能看见),但绝不能从代码断言"页面上主导按钮是主操作"(那是 L2 在渲染上的事)。

一、L1 能下结论与不能下结论的边界

能(代码里看得见的)

从源码结构看,L1 能直接判定的是"某个状态机或模式在代码里是否存在",例如:

  • 某个数据分支缺失(没有error分支);
  • 初始化标志只在成功时翻转(失败即永久骨架屏);
  • 草稿只存在于内存 store 而没有任何persist持久化;
  • 某个界面模式完全缺席;
  • fetch 没有 retry;
  • 动作没有 in-progress 中间态。

这些都是结构性问题,读取file:line即可命中,无需渲染。

不能(推迟给 L2/L3)

L1 读不到"渲染后的观感"类结论,包括:屏幕上的主导控件是否真的是主操作、空态是否读起来像"一个真实的页面"、间距/对比度/截断、响应式行为,以及一切带数字的指标(CLS/LCP)。原文档特别强调一条纪律:永远不要根据variant之类的 prop 去勾选"视觉结论"——比如在代码里看到variant="ghost"isPrimary就认定"这是次按钮/这是主按钮",这种判断必须标记为"pending L2"

不能(需要 surface-class 基准,见 SKILL.md)

这是 L1 结构性盲区中最隐蔽的一种:某个界面按领域惯例本该具备、但完全没做出来的能力,在代码里不留下任何file:line、死分支或半接线的按钮,L1 根本无从 grep。原文档给出了一个真实失误案例:

OAuth consent 界面没有 switch-account 能力——按 GitHub / Google / Okta 这类同类产品的类规范(class norms),consent 屏应展示"正在以哪个身份授权"并允许切换账号/重新认证、点名请求方应用、列出 scope、允许/拒绝、并可跳转后续撤销;而 LobeHub 首次审计仅对照内部状态清单,报出了按钮层级、retry 等次优问题,却漏掉了最大的那个:consent 屏把用户锁死在当前身份上,没有切换账号的入口。competitor-norms 一遍过能在第一分钟抓住它,纯代码审计永远抓不到。

因此正确做法是:把 surface-class / 竞品对照得到的"期望能力清单"带进 L1,逐项勾"存在 / 缺失",否则 L1 永远只在给"已经存在的路径"打分。

不能(声明的意图 ≠ 交付的结果)

代码注释或 prop 命名表达的"设计意图"是一句主张,不是结论。例如// reads as one more choiceisPrimaryvariant="ghost"——支撑它的实现机制可能根本不存在,L1 看不到二者之间的鸿沟。意图性注释只能作为L2 待确认项,绝不能照着注释打 ✅。原文档的教训:

❌ 某 ask-user 自定义输入的行内注释声称它"读起来像一个额外选项",但那行其实没有任何真实选项携带的编号 chips——注释描述的意图,渲染层从未交付。代码自述 ≠ 代码行为。

二、L1 标准流程(四步)

步骤 1 — 圈定范围并映射界面(Scope & map the surface)

先钉死界面委派到的两层代码:

  • 路由层:src/routes/**(本仓库实际路径为src/routes,包含 800+ 个.tsx/.ts路由文件);
  • 特性组件层:路由委派去的src/features/**,这是 LobeHub 最大的源码目录(3500+ 文件),一个路由往往只做数据装配,真正的界面块都在这里。

然后自上而下枚举用户看到的所有 block,外加 chrome(导航、头部、侧边栏)。原文档建议用一个Explore agent做广度扫描:让它返回组件树,并对每个 block 标注其数据获取机制、以及 empty / loading / error / retry 哪些存在、哪些缺失,全部带file:line。对于你最想深挖的 2~3 个发现,自己动手重读它们背后的源文件——广度靠 agent,深度靠自己。

以仓库中的实际审计样例(example/task-detail.md)为例:/task/:taskId/agent/:aid/task/:taskId两条路由树(router config 中分别指向同一组件)最终都渲染TaskDetailPage,即src/features/AgentTasks/AgentTaskDetail/TaskDetailPage.tsx,整个详情面由useActiveTaskDetail.tsTaskActivities.tsxCommentInput.tsxTaskDetailRunPauseAction.tsxTopicCard.tsx等一组文件支撑——这就是"先定路由,再定委派组件,再逐 block 读代码"的落地形态。

步骤 2 — 清点使用中的模式(Inventory patterns in use)

逐家族(family)走一遍pattern-catalog.md——它把 Jenifer Tidwell《Designing Interfaces》的模式语言按 7 大族组织:

  • Navigation(导航):Clear Entry Points、Global Navigation、Hub & Spoke、Fat Menus/Sitemap Footer、Sequence Map/Breadcrumbs、Escape Hatch、Modal Panel、Deep-linking;
  • Layout(布局):Visual Framework、Center Stage、Titled Sections、Card/Card Stack、Grid of Equals、Accordion/Collapsible/Movable Panels、Right/Left Alignment、Responsive Disclosure;
  • Input(输入):Input Prompt/Input Hints(⚠️ 陷阱:placeholder 必须静态、不可夹带可点击/可检索内容)、Good/Smart Defaults、Forgiving/Structured Format、Autocompletion、Dropdown Chooser/List Builder/Illustrated Choices、Same-Page/Inline Error;
  • Commands & actions(命令与动作):Prominent "Done" Button、Button Groups、Smart Menu Items、Preview、Progress Indicator/Cancelability、Multi-Level Undo、Action Panel/Overflow Menu;
  • Showing complex data(复杂数据展示):Overview + Detail、Sortable Table、News Stream/Activity Stream(⚠️ 期望配 Update Indicator + 手动刷新 + 用户操作下不重排)、Dynamic Queries、Data Spotlight;
  • Feedback & system response(反馈):Loading Indicator/Spinner/Skeleton(LobeHub 规范:骨架屏或NeuralNetworkLoading,绝不用 antdSpin)、Progress Indicator、Update Indicator、Failure + Retry、Cancelability/Deferred Choices;
  • Getting started(上手与成长):Welcome/Sign-on、Guided Tour、Empty-state as onboarding(首启空态既教学又带 CTA;⚠️ 陷阱:promo/onboarding 槽位要可预期,不能随机轮换)。

模式目录的用法不是"背规范",而是按意图匹配、不抠名称(跨版本/跨库命名有差异);某模式缺失不一定是 bug,但"这个界面明显该有而缺失"(如数据流没有新消息提示)就是 finding。对每个 block,给每个模式打三档标签:

  • solid(扎实)
  • ⚠️partial-or-misused(部分或误用)
  • absent-but-expected(缺失但应有)

然后输出一张表:Pattern | Where (block + file) | rating | note。这张表同时会把"缺失的模式"暴露出来(比如没有 Update Indicator 的 feed)。

仓库实测提示:pattern-catalog.md末尾总结了本代码库的高发弱点区——**Feedback 族(failure/retry 缺失)和 Input 族(草稿安全、placeholder 误用)**最常出问题,检查时优先扫这两族。

步骤 3 — 对照 ux 检查清单审计状态(Audit states against the ux checklists)

对每个 block,走一遍ux技能相关的模块(Read/Edit/Act/Feedback/Grow 五个分模块),把每个缺口记为present / missing / misleading,并附file:line。原文档按"投入产出比最高优先"排了高频检查项:

① Loading can fail(Feedback §4.2)——每次 fetch 都要有终态失败 + retry?重点盯"只在成功时置位的 init 标志"→ 出错时永久骨架屏。相关规则全貌在feedback.md的 §4.2:加载态必须有终态失败路径;init/ready 标志不能只在成功时置位;error 分支不能排在isLoading = !map[id]数据在场闸门之后(首次加载失败时不可达,只有 revalidation 失败才渲染);errorMap/isXErrorselector 若零调用点就是"建好但孤儿"的错误路径,与缺失无异。

② Empty vs failed vs not-loaded(Read §1.1)——区分了"空 / 失败 / 未加载"吗?失败的加载会不会伪装成"这里啥也没有"?规则在read.md§1.1:先读error再判空(失败绝不允许渲染成空态),详情页读error后再落到 NotFound(加载失败 ≠ 被删除/404)。

③ Draft safety(Edit §2.1)——用户输入的草稿跨刷新持久化了吗,还是只在内存里?规则在edit.md§2.1:编辑器要把进行中的输入备份到 durable storage,刷新/崩溃/保存失败后能恢复。

④ Forward momentum & action states(Act §3.1)——confirm → in-progress(locked)→ done/error 的完整状态链在代码里存在吗?规则在act.md§3.1:异步/批量/不可逆动作必须 confirm → in-progress(locked) → done/error;长耗时操作要提供运行中 Cancel;乐观更新必须把失败面给用户(catch + toast),绝不静默回滚。

⑤ Live / polling streams(Read §1.7)——有新条目信号、有手动刷新、且不在用户操作下重排吗?规则在read.md§1.7:轮询 feed 要"信号新条目 + 手动刷新 + 不重排 + 刷新失败要有独立呈现(不能伪装成空)"。

⑥ Closed-loop / cross-surface entry points(Grow §5.3)——该界面是否把用户引向它配置的那个功能的数据/管理区,而不是死胡同?规则在grow.md§5.3:给某功能做配置的界面要在上下文中链到它的数据/管理区,闭环"config → manage"。这是 L1最容易漏的一类缺口:纯配置面板(一个开关、没有任何链向它管辖对象的链接),或只在文案里"承诺"目的地——因为"本该存在的链接没有 file:line"。必须对每个界面都主动问这一条,包括那些不起眼的"只是个小表单"——缺口往往藏在那里。

⑦ 其他按界面酌定——Pinned actions、draft scope 等,视界面类型决定是否检查。

步骤 4 — 分级并记录(Rank & record)

按共享的严重度分级标准(定义在 SKILL.md)排定优先级,然后把步骤 2 的模式表和步骤 3 的缺口清单灌进共享的输出模板(见example/home.mdexample/task-detail.md的产物形态)。凡是无法仅凭代码得出结论的判定,标记"pending L2/L3",让下一层知道该去确认什么。

共享严重度分级(SKILL.md)为:

  • 🔴Breaks trust——数据/输入丢失、卡死/永久状态、用误导性的"空"掩盖失败、静默发送失败;
  • 🟠Dead-ends or misleads——没有前进路径、状态含混、缺少 in-progress 反馈、不像真实页面的空态;
  • 🟡Friction / inconsistency / missed delight——可预期性、冗余控件、渐进式披露缺口、CLS 抖动。这一档最容易被低估:只盯着正确性跑的审计会偏重 🔴/🟠,掠过微一致性(兄弟元素样式不一、有 affordance 没 label)。界面整体扎实时,要刻意切换进"界面细节"镜片再扫一遍。

三、落地形态与共享输出

审计报告要"报好"而不只报缺

SKILL.md 有一整节在纠正"只列缺陷 = bug 报告"的偏移:职责是**"用到的模式 + 用得好不好",所以亮点是头等 finding。一个精良的状态机、一个保存失败仍存活的草稿、一个 open-redirect 防护、一个聪明的默认值,都该点名、附file:line、标为 ✅ 亮点**。理由有三:

  1. 它们教学:好案例是"回灌(回灌/闭环)"循环的 ✅ 半边,会成为ux清单项引用的正面例子——审计若从不报告好案例,就只能在清单的 ❌ 半边打磨;
  2. 它们防退化:"别把这个改回退了"本身就是一条 finding;下一次重构必须知道哪些行为是承重的;
  3. 它们校准严重度:缺口要放到"这个界面整体很强"还是"处处都弱"的基准上去读。

task-detail.md是这条纪律的范本:它把读/监控主脊骨的每一条亮点都带file:line写出——useActiveTaskDetail.ts以 "resolving"(!hasTaskDetail && !isNotFound)为闸而不是data===undefined、副依赖闸门锁的是 in-flight 标志agentConfigLoading从而让解析为null的 assignee放行闸门而非死锁(✅ 已回灌成 Feedback §4.2);10 秒轮询只在hasInFlightActivity期间进行(✅ 已回灌成 Read §1.7);run-all 的 preview → locked confirm →partialFailure/kickedOfftoast(✅ 已回灌成 Act §3.1);评论草稿只在await成功后才清空(失败保留草稿)。而它列的缺口也逐条挂到ux清单与严重度:run/pause 失败静默(🔴)、详情 fetch 失败伪装成 404(🟠)、配置 autosave 状态枚举只有idle|saving|saved没有failed(🟠)、无任务级 abort(🟠)、评论提交失败无提示(🟡)、artifacts 为零即整节消失(🟡)。

审计没写完不算完:必须"落地"(Land the findings)

SKILL.md 明确:审计写完 finding 不算完成,落地才算。三步缺一不可:

  1. 具体 bug→ 修掉最顶上的 🔴,或按页建 Linear 子议题归档(per-page 容器议题 → 每条 finding 一个子议题);
  2. 可泛化的缺口 → 回灌ux(强制):每条超出本界面的 finding 都要反哺ux清单——加/强化某条规则(规则 + ✅/❌ 例子 + 镜像一行到 Quick review),以被审界面为 ❌ 实例。一轮跑完清单应该比跑之前更锋利;若真没有任何可泛化缺口,要在 Skill-feedback 部分明说,沉默不是可接受的收尾
  3. 示范性好案例 → 回灌ux并细化规则:好案例只有"教会规则新东西"才值得落地——问"这条模式做到的、现有规则还没要求的技巧是什么",把那个区别提炼回来(例如 Fleet 从滚入视野中提炼出"re-run 触发有两种风味——异步到达 vs 命令式 add-then-paint"、滚动轴跟随列表方向 → Read §1.3;skeleton 提炼出"按文本宽度比例匹配,不只匹配高度" → Feedback §4.1);
  4. 本次审计本身→ 存为references/example/<page>.md,供下次审计当模板。

审计与ux技能因此是一个闭环ux是审计所对照的基准,审计是让ux保持诚实的机制——跳过回灌就等于断环,审计退化为一次性评审。

为什么 L1 也要有"状态强迫清单"

L2/L3 层的存在意义是让 L1 的推断有机会被证实或推翻。对照layer-3-dynamic.md的状态强迫食谱(state-forcing cookbook)可以看到 L1 盲区如何被覆盖:离线/错误用 CDPNetwork.emulateNetworkConditions {offline:true}触发并观察 failure+retry UI;慢/卡加载用 slow 3G 冻结骨架屏;空态用全新空账号;能力门控则选一个缺能力的模型看软警告是否出现。CLS 这类数字则靠先注入PerformanceObserver再读累积值。L1 里标记的每个pending L2/L3项,最终都会落到这些可执行验证上——这正是task-detail.md末尾"§5 Pending"一节的做法:把"详情 fetch 打 500 → 证实 D② 显示 Task not found 且无 reload"等写成一串明确的 L3 验证项。

四、跑 L1 时的操作要点速记

  • 每次审计都跑 L1,不需要任何环境前置;只有问题涉及布局/层级/渲染态/响应式时才加 L2,需要走旅程/强制 L1/L2 到不了的状态/量化 CLS 时才加 L3。CLI 可用--l1 / --l2 / --l3把一次运行限定到某一层(默认 L1,若附了截图则加跑 L2)。
  • 证据不是感觉(evidence, not vibes):每条 finding 都要带证据——L1 是file:line,L2 是用 Read 工具验证过的截图,L3 是捕获到的值/快照。任何承重断言都要先在拥有它的那一层确认——一个错误的"它缺失了"比没有 finding 更糟。
  • 对照界面"类"打基准,不只是对照自家产物:读自家代码只能暴露"我们做了的东西"里的瑕疵,结构上对"我们压根没做过的能力"是瞎的。先在代码前写下这个界面所属类(OAuth consent、file picker、checkout、share dialog…)的期望能力清单,再对着它审计缺口,否则审计永远只是在给已有路径抛光,还会悄悄放走一个缺失项。
  • "冗余"控件先想组合层,再谈删减:L1 发现两个控件看似做同一件事时,条件反射是删/藏/合并。先问"意图相同、scope 不同?"——scope 更宽的应该提升为可见的平级兄弟(Titled Sections 一类的组合层动作),而不是删除。务必先走完 pattern-catalog 再对任何"冗余/重叠"发现开药方。

结语

L1 静态审计是整个 LobeHub 三阶段 UX 审计的基石:它以近乎零成本换得对一个界面全部代码路径的结构性覆盖,把"缺分支、缺 retry、缺持久化、缺模式"这类代码级缺口一次性扫清,并把视觉观感类结论诚实地上交 L2、把运行时/量化类结论上交 L3。对照本仓库的实操样例task-detail.mdhome.md等,可以把它当作可直接复制的报告模板;而每一轮 L1 产出经"回灌ux"后,都会让下一轮审计所对照的基准更锋利——这就是 LobeHub 把 UX 审查做成"可重复、证据驱动、且持续进化"的闭环机制的关键所在。下一篇可继续深入 L2 视觉审计(截图如何取证、如何从渲染上确认主导控件)与 L3 动态审计(CDP 驱动旅程 + Core Web Vitals 打点),三篇合起来即构成完整的界面审计方法论。

【免费下载链接】lobehub🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 14:20:42

CAN与UDS诊断协议:从底层通信到车载测试实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:20:37

域名与DNS解析原理全解:从注册到配置的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 14:17:33

腾讯开源多模态本地搜索工具:让图片视频文本统一检索

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华