首页
/ LobeHub UX Audit 实践:用三层证据法审计"自学习建域"页面的完整拆解

LobeHub UX Audit 实践:用三层证据法审计"自学习建域"页面的完整拆解

2026-09-06 17:17:26作者:平淮齐Percy

本文以 LobeHub 仓库内 UX 审计范例报告 为主体,完整还原一次基于 ux-audit 技能 的界面体验审计:先讲清楚这套三层(静态 / 视觉 / 动态)审计方法论与判定规则,再逐条对照报告中引用的源码证据,说明每个"模式命中 / 亮点 / 体验缺口"是如何从代码里找出来并落成结论的,最后给出读者在自己的项目中复现这套审计的可行路径。

一、UX Audit 是什么:一次只审一个界面、以证据为准

ux-audit 是 LobeHub 仓库 .agents/skills/ 目录下定义的一个 Agent 技能,目标是对单个页面(surface)做可重复、有基准的体验评审。它回答两个问题:

  1. 这个界面用了哪些交互模式,用得怎么样——基准是 Jenifer Tidwell《Designing Interfaces》的"模式语言",清单化后存放在 pattern-catalog.md,按导航、布局、输入、命令与动作、复杂数据展示、反馈、入门引导、视觉风格等家族分组;
  2. 体验在哪里薄弱——基准是 ux 技能 的行为检查清单(如 edit.md 的草稿保护、act.md 的动线完整性),每个缺口都必须挂到某条清单项上。

调用约定写在技能 frontmatter 中:<page-or-surface> [--l1 | --l2 | --l3],即一次只审一个页面,可用层级参数限定范围。默认跑 L1(有截图时加 L2),"持续审计"体现在随产品迭代逐页重跑。

三层审计:结论必须来自"看得见它"的那一层

层级 程序文件 做什么 能抓什么 成本
L1 静态 layer-1-static.md 读代码 缺失的空态/错误/重试分支、无草稿持久化、缺失的模式、结构性问题 低,离线,每次必跑
L2 视觉 layer-2-visual.md 渲染截图 真实视觉层级与主导控件、间距/对比度/对齐、截断溢出、深浅色模式、响应式断点 中,需要渲染环境
L3 动态 layer-3-dynamic.md 用 acceptance 自动化驱动真实用户旅程 进行中/锁定态、强制错误/空态、步骤衔接、焦点/键盘可达性、量化 CLS / LCP / INP 高,需要运行环境 + 鉴权

配套一张覆盖矩阵,核心规则是"判定必须来自能看见它的层":视觉层级、间距对比这类结论只能由 L2 确认,L1 对它们是"会误导"的;动线衔接、键盘可达性、性能指标只有 L3 能下结论;而缺失分支、草稿未持久化这类结构问题 L1 就能确认。SKILL.md 里特别点名的"常见陷阱"就是:不能因为代码里有一个 variant 属性就勾掉"只有一个主按钮"——那是 L2 级别的判定。

几条硬性判定规则

技能文件里还有四条值得单独说的 Ground rule:

  • Evidence, not vibes(证据而非感觉):每个发现都要引用证据——L1 是 file:line,L2 是"用 Read 工具核验过的截图",L3 是捕获值/快照。错误的"它缺失了"比没有发现更糟。
  • 对标的是"界面类别"的惯例,而不只是自家产物:读代码只能暴露"已实现部分的缺陷",对"根本没实现的能力"是结构性盲的。审计前要先把这个界面所属类别的行业惯例列出来,对着找缺口。
  • 对比两个变体时,赢家是结果判定而不是做工判定:对引导页、同意页、付费墙这类"挡在用户和目标之间"的门面,往往"最少的那版"最好;判定赢家必须挂在 L3/分析数据上,L1/L2 只能比较机制,不能宣布胜者。
  • 报告好的一面,而不只列缺口:做得好的状态机、失败后仍保留草稿的输入框,都是与缺口同级的一等发现,要标出 file:line 并打 ✅ 亮点,因为它们既是下一轮重构的"不要回归"清单,也是回灌进 ux 清单的正面示例。

严重度分三级:🔴 破坏信任(数据/输入丢失、卡死态、误导性空态、静默发送失败);🟠 死路或误导(无前进路径、状态含糊、缺进行中反馈);🟡 摩擦/不一致/错失的愉悦感——且明确提示这一级最容易被漏报。

二、审计案例全文:Create expertise modal(自学习建域界面)

example/self-learning-create.md 是一份典型的"worked example"——技能要求每份审计报告保存为 references/example/<page>.md,供下一轮运行当模板。它审计的对象是"自学习"模块中新建专家域(expertise domain)的创建界面。报告开头声明了运行层级:

Layers: L1 ✅ · L2 desktop dark ✅ · L3 forward journey partial.

即跑了完整的静态层与桌面端深色模式视觉层,动态层只覆盖了前向旅程的一部分。

1. 该界面命中的模式(Patterns in use)

报告用一张表逐条对照模式目录给出评级与证据:

模式 评级 证据
Modal Panel(模态面板) 命令式的 base-ui 模态框(当时实现 CreateDomainModal.tsx:97-105
Forgiving Input(宽容输入) 一句自然语言简述即可变成可编辑草稿(:26-89
Preview(预览) 解析出的名称/过滤器在"创建"之前先行呈现(:53-70
Prominent Done(显眼完成按钮) Continue/Create 是唯一主操作(:80-89
Draft safety(草稿安全) —(缺席) 状态仅存内存,且 maskClosable 为 true(:29-31:101

注意最后一行:草稿安全这一条评级是"—"(缺席),它直接对应下面第一条体验缺口。这体现覆盖矩阵的分层思想——草稿持久化是 L1 就能确认的结构问题,不需要截图或运行环境。

2. 亮点 / 好的案例(Strengths / good cases)

  • ✅ 亮点 — 先给松散输入,再要结构(Loose input before structure)。 Forgiving Format 与 Preview 组合良好:用户先写一段话,系统解析成结构,用户再修正结构——而不是反过来逼用户先填一堆表单。
  • ✅ 亮点 — 失败保留字段(Failure preserves fields)try/catch/finally 上报错误后草稿原样保留(:33-45),生成失败不会让用户白输。

这两条正是"报告好的一面"规则的落地:每条都带 file:line 证据,并说明它为什么重要(load-bearing)。

3. 体验缺口(Experience gaps,按严重度排序)

  1. 🔴 点遮罩关闭 / 刷新会丢失草稿。草稿状态仅在内存中、且遮罩可点击关闭,一次误点或刷新就会把用户输入蒸发。补救方案:持久化草稿 + 脏状态关闭确认。
  2. 🟠 收录边界只以文本预览,没有用样例验证。"什么算属于这个域"的过滤器是一段文字,但用户在承诺之前看不到"哪些 Topic 会命中 / 哪些不会"。补救:在落库前展示匹配/不匹配的 Topic 示例。
  3. 🟡 解析步骤没有显式的"返回"。用户可以编辑解析出来的结果,但在流程模型里无法回头修改原始那句话。

🔴 级落在"输入丢失"上,符合严重度量表的定义(数据/输入丢失=破坏信任);🟠 级落在"边界不可验证"上,属于"误导/无确认手段";🟡 级是流程摩擦。

4. 技能反馈(Skill feedback)

报告末尾一行:"Validates Edit §2.1 and Act §3.1; no new generic rule landed."——即本次审计是 ux 技能 edit 模块 §2.1「保护进行中的编辑」 与 act 模块 §3.1 既有清单项的又一次实例化验证,没有沉淀出新的通用规则。技能要求:即使本轮没有新规则,也必须在报告中显式说明,沉默不是允许的收尾方式。

三、逐条对照源码:报告中的每个证据指认什么

报告引用的是当时名为 CreateDomainModal.tsx 的模态框实现;当前仓库中该界面已演进为整页组件 CreateDomainPage.tsx,由路由 agent/self-learning/new/index.tsx/agent/self-learning/new/index.tsx) 挂载。下面把报告里的每个结论映射回现有源码,验证审计证据链是否成立。

宽容输入 + 预览:一句简述 → 可编辑草稿

CreateDomainPage 的注释直接描述了这套"两步建域"模型(与 createGoal 交互对齐):

① 一段话说清方向 → ② 检查它读出来的锚。锚不只是名字和过滤器:分层决定经验挂在哪一层、经典依据决定「覆盖」意味着什么——所以 step 2 把整个锚候选摊开。

对应实现是 generate()CreateDomainPage.tsx#L234-L244):调用 expertiseService.draftDomain({ agentId, brief }) 把自然语言简述变成一份 ExpertiseDomainDraft,成功后 setStep('review') 进入审阅步;失败则 toast.error 并回退——若已有草稿就回到 review,否则回到 describe"失败保留字段"的亮点在此处得到印证catch 分支只改步骤、不碰 draftbrief,已解析的结构完好无损。

审阅步的"预览"体现为整块摊开的可编辑字段:标题(maxLength=80)、domainFilter(域过滤器)、outOfScope(范围外)、rationale(依据)、经典条目列表(canonEntries,E1/E2 编号,可增删改标题/来源/陈述)与分层列表(layers,L1/L2 编号)。创建按钮的启用条件是明确的 canCreateL270):

const canCreate = !!draft && !!draft.title.trim() && !!draft.domainFilter.trim() && !creating;

即必须有草稿、标题与域过滤器都非空才允许落库——"解析结果先呈现、再提交"的 Preview 模式在代码层面成立。审阅步底部只有唯一的主操作按钮(Confirm),且支持 ⌘/Ctrl+Enter 快捷提交(L298-L304),对应 Prominent Done 评级。

逐块精修:Adjustment 机制

审阅步里每个字段旁有一个"✨ 调整"按钮,点开 Popover 用自然语言要求局部重生成(如"把过滤器写得更严格")。其数据结构是一个五元组(createDomainAdjustment.ts):

export type AdjustmentTarget =
  'canonEntries' | 'domainFilter' | 'layers' | 'outOfScope' | 'rationale';

export const mergeAdjustedBlock = (
  current: ExpertiseDomainDraft,
  adjusted: ExpertiseDomainDraft,
  target: AdjustmentTarget,
): ExpertiseDomainDraft => ({ ...current, [target]: adjusted[target] });

refine()CreateDomainPage.tsx#L246-L268)把当前草稿连同调整指令一起发给 draftDomain,返回后只合并被调整的那一个块,其余字段不受影响——这避免了整稿重生覆盖用户已手工修改的内容。页面上还有一处细节注释值得注意:精修请求在途期间对应字段被 disabled,因为"在途调整回答的是请求发出时的那份草稿,期间的编辑会在响应合并时被静默覆盖"。

草稿安全:从"🔴 缺口"到已落地的持久化

这是本报告最有价值的演进证据。审计时刻的评级是"Draft safety —(缺席):状态仅内存、遮罩可关",对应 🔴 级缺口 1(丢草稿)。当前代码中,这一缺口已有专门的 Hook 承接:useCreateDomainDraft.ts

  • 按 Agent 维度作用域的持久化storageKey = 'self-learning:create:' + agentId,草稿(brief + 结构化 draft)在每次变化时写入 localStorage,两者皆空时删除键(L48-L63)。草稿作用域限定到具体 Agent,不会跨实体串稿——这正是 ux 技能 edit §2.1 检查单里"draft scoped to its target id"的要求。
  • 旧格式迁移:早期模态框把简述以纯文本形式存在同一个键下,parseStoredCreateDraftL10-L21)兼容解析——JSON 失败就按纯文本兜底,JSON.parse 出来是字符串也按 { brief } 处理。
  • 脏状态退出守卫CreateDomainPage 额外挂了 beforeunload 监听,brief 非空时 event.preventDefault()CreateDomainPage.tsx#L225-L232),补上了"刷新丢稿"这一半。
  • 创建成功后清理create() 成功落库后 localStorage.removeItem(storageKey) 再跳转(L287-L288);"返回总览"走 clearDraft() 同时清内存与存储。

测试用例 useCreateDomainDraft.test.ts 对这三点都有断言:

it('migrates the legacy plain-text brief', () => {
  expect(parseStoredCreateDraft('Improve production incident response')).toEqual({
    brief: 'Improve production incident response',
  });
});

以及"路由 Agent 就绪时恢复草稿而不删除它""clearDraft 同时清内存与持久化副本"两个用例。换句话说,报告当年 🔴 级指认的"丢草稿"问题,后续被一条"回灌 → 修复 → 测试固化"的链路解决了,审计范例与修复后代码可以在同一个仓库里对读。

四、从报告到闭环:审计发现的"落地"机制

SKILL.md 强调:审计在发现被落地之前没有结束。一次运行的收尾有三步必做动作:

  1. 具体 Bug → 修掉最高 🔴,或以子任务形式建单;
  2. 可泛化的缺口 → 回灌 ux 技能(强制):把缺口变成清单项的规则 + ❌ 示例,让检查清单每次运行后都比运行前更锋利;好的案例同理回灌 ✅ 示例,目标是"提炼出当前规则文本还没说清的子规则",而不只是装饰;
  3. 报告本身 → 存为 references/example/<page>.md

ux 是被审计的基准,审计是让 ux 保持诚实的机制,二者构成闭环。本例的 Skill feedback 一节("Validates Edit §2.1 and Act §3.1; no new generic rule landed")就是这一闭环的收尾记录:本轮没有新规则,但既有规则又被一个真实界面实例验证了一次。

对想在自己的项目里借鉴这套方法的读者,几个可直接搬走的要点:

  • 先定层,再下结论:把"视觉层级对不对""键盘能不能到"这类问题分别钉死在能看见它们的层级上,避免"读代码打勾"式的虚假覆盖;
  • 缺口必须能引用证据file:line(静态)、核验过的截图(视觉)、捕获值(动态)三选一,引用不出证据的结论不写进报告;
  • 好案例与缺口同级:每次审计强制产出 "Strengths / good cases" 一节,作为下一轮重构的"不要回归"清单;
  • 报告即模板:把每份报告按统一结构(Patterns in use 表 → 亮点 → 排序缺口 → 技能反馈)存档,让第 N 次审计比第 1 次更快更准。

另外,pattern-catalog.md 结尾还基于历次审计沉淀了一张"本代码库最常薄弱的模式家族"提示:集中在 Feedback(缺失败/重试)与 Input(草稿安全、placeholder 误用)两族——审计时优先查这两族。这份范例报告中"草稿安全缺席 + 失败保留字段"恰好分别踩中这两族的一正一反两面,是这一提示的缩影。

五、小结

example/self-learning-create.md 这份不到 30 行的范例报告,完整演示了 LobeHub UX 审计方法论的最小闭环:以《Designing Interfaces》模式目录和 ux 检查清单双基准打分,L1 静态层确认"宽容输入 + 预览 + 唯一主操作"三项命中、草稿安全缺席,L2 桌面深色层确认视觉结论,L3 部分覆盖前向旅程;三条缺口按 🔴/🟠/🟡 定级并各给一行补救,最后以"验证既有清单项、无新规则"收尾。对照当前仓库源码可以看到,报告中标记的 🔴 草稿丢失缺口已由 useCreateDomainDraft.ts 的 localStorage 持久化、beforeunload 守卫与配套测试用例实际解决——这正是"审计让检查清单保持诚实、检查清单让下一次审计更快"这一闭环的实例化。

登录后查看全文
热门项目推荐
相关项目推荐