首页
/ LobeHub ux-audit 实战范例:以自学习成长画像页为例的三层 UX 审计方法

LobeHub ux-audit 实战范例:以自学习成长画像页为例的三层 UX 审计方法

2026-09-06 17:31:21作者:幸俭卉

本文以 LobeHub 仓库内 ux-audit 技能的一份参考范例——针对 Agent「自学习/成长画像」(self-learning overview)页面的 UX 审计报告——为骨架,完整拆解「三层审计 + 模式目录 + 严重度分级 + 回灌闭环」的方法论,并逐条对照当前仓库源码验证报告中的每条结论。读完你可以掌握:如何对单个页面(surface)做一次可复现、证据驱动的 UX 审查,如何把发现落地回 ux 检查清单,以及如何在源码层面复核审计结论是否仍然成立。

这份文档是什么:ux-audit 技能的一份参考范例

该文件位于 .agents/skills/ux-audit/references/example/self-learning-overview.md,是 LobeHub 为 Agent 准备的 ux-audit 技能(见 .agents/skills/ux-audit/SKILL.md)下的一份已完成的审计范例(worked example)。它的开篇第一行就交代了审计对象与覆盖层次:

Surface: /agent/:aid/self-learningLayers: L1 ✅ · L2 desktop/760px dark ✅ · L3 keyboard/navigation ✅。

ux-audit 技能的目标是对一个界面一次(one surface per run)做标准化 UX 审查,其基准是两套东西:

  1. 模式语言(pattern catalog)——「好界面由什么构成」,见 references/pattern-catalog.md
  2. ux 技能的执行检查清单.agents/skills/ux/SKILL.md)——「一个流程应当如何表现」。

审计要回答两个问题:这个 surface 用了哪些模式(用得如何),以及体验在哪里薄弱(每个 gap 都要挂到某条检查清单项上)。反复出现的 gap 会作为新的检查项回灌进 ux 技能,而审计本身则沉淀为 references/example/<page>.md 范例——本文引用的文档正是这样一份范例,仓库内同目录还有 home.mdfleet.mdtask-detail.md 等一批姊妹范例。

三层方法:L1 静态 / L2 视觉 / L3 动态

ux-audit 技能把审计拆成三层,核心理则是「结论必须来自能够看见它的层」(a verdict must come from a layer that can see it),并配有一张覆盖矩阵规定每类发现只能由哪一层下结论:

层次 做什么 能捕获什么 成本
L1 Static 读代码 缺失的状态/分支(empty/error/retry)、草稿未持久化、模式缺失、结构问题 低,离线,每次必跑
L2 Visual 对渲染结果截图 真实视觉层级与主导控件、间距/对比/对齐、截断溢出、empty/loading/error 实际长什么样、响应式断点、深浅色 中,需要可渲染环境
L3 Dynamic 通过 acceptance 自动化驱动真实用户旅程 + 插桩测量 进行中/锁定状态、强制触发的错误/空态、步骤衔接、焦点/键盘可达性、量化的 CLS / LCP / INP / 长任务 高,需要运行环境与登录态

几个关键规则值得单独强调,因为本文档的每条发现都受制于这些规则:

  • 视觉类结论不许从代码里勾选。例如「只有一个主按钮」「空态是一个真正的页面」都是 L2 结论,不能因为代码里有 variant prop 就在 L1 打勾。
  • 严重度分级(Severity rubric):🔴 破坏信任(数据丢失、卡死状态、误导性空态);🟠 死胡同或误导(无前进路径、无进行中反馈);🟡 摩擦/不一致/错失 delight——并且明确提示 🟡 层最容易被漏报,审计者需要单独切一次「interface-details」镜头。
  • 报告好的部分:只列问题的审计会退化成 bug 报告。亮点(✅ 亮点)是一等发现,要写行为、证据和「为什么它承重」,因为它们既教人、又保护(下轮重构知道哪些行为不许回退)、还能校准 gap 的严重度。

本文档声明三层全部跑过:L1 代码、L2 桌面端 760px 深色截图、L3 键盘/导航旅程。下面按文档自身的章节结构逐段拆解。

审计对象:self-learning「成长画像」页面

在被审计的界面上,Agent 的自学习(self-evolving)概览页展示某个 Agent 在各「方向」(domain)上的学习状态:判断句标题(这个方向现在靠不靠谱)、习惯清单、成长曲线(累计学到 / 做对率)、方向清单,以及「教它新东西」的输入框。路由入口在 src/routes/(main)/agent/self-learning/index.tsx(目录名含括号,此处以代码路径文本形式给出),它只是薄薄地渲染核心组件:

import SelfLearning from '@/features/SelfLearning';

const AgentSelfLearningPage = memo(() => <SelfLearning />);

核心实现在 src/features/SelfLearning/index.tsx,其组件注释直接点明了页面的产品语义:

成长画像 —— 自进化的 L0,也是单方向时的全部。感知单位是「习惯 + 它靠不靠谱」,不是「学到几条」:判断句、按层画像、习惯分组、做对率曲线全部由 hits.outcome 折出来。这里没有任何必办事项 —— 教学台,不是审批台。带 :domainId 进来时就是同一张画像收窄到一个方向。

几个与审计结论直接相关的实现细节(均为当前源码可确认的事实):

  • 两段式数据获取与轮询:先用 useExpertiseOverview 读一次以统计已学条数,再交给 Portrait/useHistoryWarmup.ts 的 warm-up hook 决定轮询节奏——phase === 'running' 时以 WARMUP_POLL_MS 作为 SWR 的 refreshInterval 持续刷新画像(该文件 L119 附近),否则停止轮询。
  • 判断句生成sentenceForindex.tsx L89-L116)按优先级给方向排序打分——有「反复出现」的习惯 > 有不稳定的习惯 > 从未实践 > 全部稳定——并据此生成一句具体的判断句(如「反复在同一步出错」而非「已学 N 条」)。
  • 空态即首屏AsyncBoundary 的 empty 分支(index.tsx L275-L290)渲染一个带 DnaIcon、标题、说明文案和**主按钮「新建方向」**的空页面;empty、loading、error 三种状态互斥且各有归属。
  • Create 入口的归属:头部工具栏里的「新建方向」按钮被显式限定在概览态:
{/* Starting a new direction belongs to the overview, not to one direction's page. */}
{!domainId && (
  <Button type={'text'} onClick={openCreate}>
    {t('nav.newDomain')}
  </Button>
)}

index.tsx L249-L254)注释说明得很直白:新建方向属于概览,不属于某个方向的页面。

报告逐段解读:一份范例审计长什么样

Patterns in use:模式评级表

文档的第一节是一张「模式 × 评级 × 证据」表,每个模式给出 ✅ / ⚠️ / — 评级,并用 file:line 定位证据(下表完整继承原文档):

模式 评级 证据
Empty-state as onboarding(空态即引导) 空/加载/错误三种状态互相区分,且 Create 是主 CTA(SelfLearning/index.tsx:154-187)。
Overview + Detail(总览 + 详情) ⚠️ 曲线/列表支持下钻,但「仅一个方向就自动跳转」会毁掉枢纽(:95-100)。
Data Spotlight(数据聚光) 只有三条高练习量曲线被强调(:107-125)。
Titled Sections(带标题的分节) Trend、insights、domains 各自成组。
Clear Entry Point(清晰入口) 有数据的状态下没有 Create 动作。
Responsive Disclosure(响应式折叠) ⚠️ 760px 下宽侧边栏仍然保留,图表标签变得极小。

读这张表的方法正是 ux-audit 技能要求的:不是给模式打勾,而是每个评级都挂一条可复核的证据。⚠️ 和 — 两行就是这张表贡献的增量信息——Overview+Detail 的下钻能力是好的,但跳转行为把它从「枢纽」变成了「死胡同」;「Clear Entry Point」之所以是 —(无法评级),因为该状态根本不存在 Create 入口,评级无从谈起。

Strengths / good cases:三个 ✅ 亮点

第二节列出「不许回退」的亮点,每个都写明行为、证据位置和为什么承重(完整继承原文档):

  • ✅ 亮点 — 诚实的无数据表现(Honest no-data projection):在有意义的序列出现之前,图表根本不出(SelfLearning/index.tsx:91-93)。不画一条假曲线去「看起来在学习」,是数据页面最基本的诚实。
  • ✅ 亮点 — 聚焦的对比(Focused comparison):只有三条被强调的曲线避免了「彩虹图」(SelfLearning/index.tsx:107-125)。
  • ✅ 亮点 — 真实的首次运行页面(Real first-run page):empty / failed / loading 三态分离;empty 既解释价值又提供 Create(SelfLearning/index.tsx:154-187)。

在当前的 src/features/SelfLearning/Portrait/GrowthCharts.tsx 中可以看到「聚焦对比」的具体实现:图表是固定 380×96 的自绘 SVG(const W = 380; const H = 96;),只画两条默认曲线——累计学到(它懂了多少)与做对率(它可靠了没),两者同向走高,一眼读出「在变成专家」;多方向时按实践序号对齐后求和/求均值。而「无数据不画图」这条约束在 index.tsx L339 依然是显式的:

{runs > 0 && <GrowthCharts domains={scoped} />}

即只有 runs > 0(有过实践)才渲染曲线区块——与审计的「Honest no-data projection」完全吻合。

Experience gaps:五条按严重度排序的发现

第三节是审计报告的核心,按 🔴 → 🟠 → 🟡 排序,每条包含「发现 + 违反的清单项 + 修复方向」(完整继承原文档):

  1. 🔴 有了第一条数据后,枢纽和 Create 一起消失。 无条件的「仅一项则跳转」加上 Create 只存在于空态,导致面包屑变成循环。修复方向:保持概览可达,并在概览头部暴露 Create。
  2. 🟠 方向行与 insight 行是「纯鼠标伪控件」。 运行时的行是 DIV、无 role、tabIndex=-1、不出现在交互快照里(:243-264:313-322)。修复方向:换成真正的 Link/规范行,并带 focus-visible 样式。
  3. 🟠 占据中心舞台(Center Stage)的是模型输出,而不是注意力。 曲线填满首屏,可操作的 insight 却在首屏之下。修复方向:先放「需要关注的」与「下一步动作」,证据放到下面。
  4. 🟡 窄桌面下可读性崩塌。 760px 时标题折行到四行、图表标签缩小,而侧边栏仍占约 280px。修复方向:响应式外壳,以及在「足够画图的宽度」以下切换到「列表优先」的替代形态。
  5. 🟡「学完了」隐藏了拟合的不确定性。 概览把由阈值推导出来的形态转译成了确定性,而详情页的置信度又不可用/是推测性的。

前两条(🔴🟠)属于「破坏信任 / 死胡同」级:键盘用户完全无法进入方向详情,而首次创建者创建完第一条数据后反而失去了枢纽页。这正是 ux-audit 技能里说的「一个错误结论比没有发现更糟」的反面教材——每条 gap 都标了来源层(键盘可达性来自 L3,视觉层级来自 L2),并给出可执行的一行修复方向,而不是抽象建议。

Skill feedback:回灌闭环

文档最后一节回答「这次审计让检查清单变强了没有」:

  • Landed(已落地)ux 技能的 Read §1.6 现在要求「数据快捷方式(data shortcuts)必须保留稳定的枢纽与集合级动作」——这正是 gap 1 泛化后的规则。
  • Validated(验证了既有规则):Act §3.4、键盘语义、Responsive Disclosure 三条既有检查项被本次审计实例化验证过。

这对应 ux-audit 技能中的「回灌(loop-back)」机制:ux 是审计的基准,审计是让 ux 保持诚实的机制——每条可泛化的 gap 都要回写成检查项的 ❌ 例子,每个做得好的模式则回写为 ✅ 例子或提炼出新的子规则;跳过回灌,审计就退化成一锤子买卖。

源码复核:审计结论放在当前代码上还对吗

范例文档的价值在于「证据可复核」。以下用当前仓库源码逐条核对关键结论(注意:文档中的行号对应审计当时的修订版,当前文件已经演进,行号有漂移,但结构级结论大多仍可直接对照)。

🔴 gap 1 的修复已在源码中体现

审计时「仅一个方向就无条件跳转」的逻辑,在当前 index.tsx L69-L74 中已演变为同页收窄而非跳转:

const scoped = useMemo(
  () => (domainId ? allDomains.filter((d) => d.id === domainId) : allDomains),
  [allDomains, domainId],
);
const single = scoped.length === 1;

从源码结构看,「带 :domainId 进来时就是同一张画像收窄到一个方向」意味着:概览不再被破坏,DomainList 也仅在「多方向且不在某方向页内」时渲染(index.tsx L361-L369),面包屑在单方向态下会变成指向概览的 Linkindex.tsx L233-L239domainId && current 时渲染回链)——「breadcrumb 变成循环」的问题被正面解决。再看「Create 只在空态」这一点:当前头部工具栏在 !domainId 且已有方向时提供「新建方向」(前文 L249-L254 片段),且空态的主按钮(L283-L287)与头部入口指向同一个 openCreate。也就是说,gap 1 提出的两条修复方向(保持概览可达 + 概览头部暴露 Create)在当前代码中都已成立——这正是「审计 → 修复 → 范例留存」闭环的鲜活样本。

另外可观察到:当前代码的导航目标路径段已从 self-learning 更名为 self-evolvingopenCreate 与删除后的回跳都使用 urlJoin('/agent', activeAgentId, 'self-evolving', …)),而 src/routes/(main)/agent/self-learning/ 目录仍保留,其中 legacy.tsx 导出 LegacyRouteRedirect 做旧路径兼容。审计范例里写的 Surface /agent/:aid/self-learning 即更名前的路径。

🟠 gap 2:伪控件行的现状

Portrait/DomainList.tsx 中的方向行当前仍是命令式导航而非规范链接:

<Flexbox
  horizontal
  align={'center'}
  as={'button'}
  className={styles.row}
  ...
  onClick={() => onOpen(d.id)}
>

行是一个被渲染成 button 样式的 Flexbox,点击后通过 onOpen(id) 回调回到 index.tsx L362-L368navigate(...)——即审计所指的「伪控件」形态(非 <a href> 的规范路由行)。组件注释也自陈了产品语义:「多个方向时的方向清单:一根可靠度条 + 一个词。单方向时不渲染——方向就是判断句的主语。」因此 gap 2 的修复(真实 Link + focus-visible)在当前结构上仍然成立,这也是该范例作为 ❌ 例子继续有效的部分。

🟡 gap 4:固定尺寸 SVG 与窄桌面可读性

GrowthCharts.tsx 中图表坐标系是硬编码的 380 × 96,坐标轴字号 10px。在 760px 这类窄桌面布局中,侧边栏占去约 280px 后,绘图区被进一步压缩——「图表标签变小、标题折四行」的 L2 发现与这个固定坐标系完全自洽。从源码结构看,该组件没有随容器宽度变化的响应式分支,修复方向(响应式外壳 + 低于「可用绘图宽度」时切换列表优先)依然是正确的。

数据聚光与两阶段轮询:✅ 亮点仍然成立

  • Data Spotlight(三条强调曲线)对应 GrowthCharts 只渲染两条聚合曲线 + 有限强调点的做法,与「避免彩虹图」的评级一致;
  • 空态三分离与 Create 主 CTA 对应 AsyncBoundaryisEmpty / errorVariant={'page'} / onRetry 三参数(index.tsx L270-L291),empty 分支文案与动作齐备;
  • warm-up 轮询(useHistoryWarmup 返回 refreshInterval 供 SWR 使用,见 index.tsx L57-L67)保证了「候选历史挖掘中」这类进行中状态有真实的加载/推进语义,而不是假进度条。

如何复现这样一次审计

把范例抽象成流程,对任意一个 LobeHub 页面执行同等级别的 UX 审计只需五步:

  1. 选一个 surface,声明要跑哪几层。L1 永远跑;涉及布局/层级/响应式加 L2(需要截图并用 Read 工具核验);涉及旅程衔接、键盘可达性、CLS/LCP/INP 量化加 L3(依赖 acceptance 自动化框架与登录态)。参考层定义文件 layer-1-static.mdlayer-2-visual.mdlayer-3-dynamic.md
  2. 按覆盖矩阵下结论:每条发现只挂在能看见它的层上;「只有一个主按钮」这类视觉结论必须截图验证,不许从代码里的 variant prop 反推。
  3. 先命名 surface 的类别(class)再审计。读自己的代码只能暴露「已造出来的东西」的瑕疵,对「压根没造的能力」是结构盲的——先按同类成熟产品的惯例列出该类别应有的能力清单,再对照找 gap。
  4. 按固定结构输出:① Patterns in use 评级表(每行带 file:line 证据);② 独立的 Strengths / good cases 章节(✅ 亮点 + 为什么承重);③ 按 🔴/🟠/🟡 排序的 Experience gaps(每条含违反项、来源层、一行修复方向);④ Skill feedback(落地了哪条规则、验证了哪条既有规则)。
  5. 落地(Land)才算完成:修复最高 🔴 或拆成子问题;把可泛化 gap 回灌进 ux 检查清单(含 Quick review 对应行);把做得好的模式回写为 ✅ 例子或提炼子规则;最后把报告存为 references/example/<page>.md 供下一次审计当模板。

这套流程的要点可以浓缩成三句:结论必须来自能看见它的层亮点与缺陷同权(亮点是「不许回退」清单,也是校准严重度的基线);审计不是终点,回灌才是——每一轮审计都应该让检查清单比开始之前更锋利。本文档(self-learning-overview.md)连同 pattern-catalog.mdSKILL.md,构成了一份从「读代码」到「改规则」的完整可执行样板。

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