首页
/ LobeHub UX 审计实战:以三层证据法审修 HomeInbox「需要你处理」错误简报卡片

LobeHub UX 审计实战:以三层证据法审修 HomeInbox「需要你处理」错误简报卡片

2026-09-06 16:13:51作者:韦蓉瑛

本文以 LobeHub 仓库中一次真实的 UX 审计为例,完整复盘对 Home Inbox「需要你处理」(needsYou)栏错误简报卡片(src/features/HomeInbox)的审计过程:从表面类别规范出发,逐层核对静态代码、真实渲染证据与动态旅程缺口,最终定位 5 个体验缺口并回灌到 ux 检查清单。读完后你将掌握一套可重复执行的界面审计方法——如何判断一张错误通知卡片「读起来是否像错误」,如何把确定性失败映射到真正的修复动作(而非徒劳的重试),以及如何用 file:line 级证据驱动修复落地。

一、审计对象:一次真实的错误卡片运行

这次审计由用户实际看到的一段原始字符串触发,渲染后的卡片头部显示:

T-1 topic #1 (tpc_5UBuAjUU4z6B) error / Execution failed: Workspace budget exceeded

对应的表面(surface)是「需要你处理」列中一张 error 类型的简报卡片,其结构为:元信息行(StatusGlyph 状态图标 + 任务引用 + 任务名 + 时间)→ Agent 头像 + 标题 + 摘要 → 操作行(忽略 / 重试)。

审计记录本身保存在 home-inbox-error.md,是 ux-audit 技能(SKILL.md)下 references/example/ 目录中一份标准的「worked example」——每次审计完成后都会归档为这样的模板,供下一次审计参照。

1.1 三层证据法:L1 / L2 / L3

ux-audit 技能的核心设计是「分层取证」:一个结论只能来自真正能"看见"它的层。三层定义如下(出自 SKILL.md):

做什么 能抓住什么 成本
L1 静态 读代码 缺失的状态/分支(empty/error/retry)、缺失的模式、结构性问题 低、离线,每次必跑
L2 视觉 渲染表面的截图 真实视觉层级与主控件、间距/对比/对齐、截断溢出、空态/加载/错误长什么样 中;需要渲染环境
L3 动态 驱动真实用户旅程 + 度量 进行中/锁定状态、强制错误/空态、步骤衔接、焦点/键盘、CLS/LCP/INP 量化 高;需要运行环境 + 登录态

本次运行的层覆盖为:L1(静态/代码)✅L2(视觉)✅——用户提供的渲染卡片截图即为真实渲染证据;L3(动态)⏳ 未执行——要复现它需要把一个任务强制推到终止性错误状态(见本文第五节,L3 会补充什么结论)。

这个分层不是为了流程仪式感。SKILL.md 中的核心规则是「evidence, not vibes」:每条发现都要引用它的证据——file:line(L1)、经过验证的截图(L2)、或捕获到的数值/快照(L3)。错误的「它缺失了」比没有发现更糟,因此「从代码打勾一个视觉结论」是被明确禁止的。

二、先给表面定类:一张成熟的错误通知卡片应该具备什么

在开始读代码之前,审计先回答了「这张卡片属于哪一类表面」。该卡片是一封收件箱中的可行动错误通知,同类成熟产品包括 GitHub Actions 失败运行、Vercel 失败部署、Sentry 问题通知、Linear 通知。原文档给出了这一表面类别的 8 条规范基准,审计差距全部对照这份清单来量,而不是只对照代码:

  1. 一眼读起来像错误(颜色/图标的严重度,而非仅靠文字);
  2. 用人的话说清什么失败了、哪个实体、何时发生;
  3. 说明为什么(原因),在可能时映射到已知/可行动的原因;
  4. 提供恢复路径(Retry);
  5. 提供关闭路径(Ignore);
  6. 提供查看路径(打开失败运行/日志);
  7. 当原因是用户可行动时(如预算),提供直达修复的路径(充值/升级);
  8. 提供反馈/上报通道。

这条「先定类、再审计」的规则在 SKILL.md 中被称为 ground rule:读代码只能暴露「我们建了什么」的缺陷,结构上对「我们压根没建的能力」是盲的——一个完全缺失的 affordance 没有 file:line 可 grep。因此必须先写下该表面类别的「期望能力清单」,再对着它审计差距。

三、模式盘点:卡片实际使用了哪些模式

对照 Jenifer Tidwell《Designing Interfaces》模式语言,本次审计盘点到的模式如下(完整表格见 home-inbox-error.md 第 1 节):

模式(家族) 位置 评级 备注
Card Stack / Titled Sections(布局) 「需要你处理」下的 needsYou 列表(HomeInbox/index.tsx 分组展示,错误排在最后(splitBriefs.ts
Failure + Retry(反馈) 错误简报 → 重试BriefCardActions.tsx ⚠️ Retry 存在,但对确定性(预算)原因是裸重试
Button Groups(动作) 忽略 / 重试 关闭 + 恢复动作分组在一起
Same-Page / Inline error(反馈) 错误在卡片内渲染,而非整页 wipe 收件箱其余内容保持完好
Status glyph / severity(反馈) 元信息行 StatusGlyphStatusGlyph.tsx ⚠️ 错误任务状态为 paused/scheduled → 渲染为手/中性图标,而非失败
Overview + Detail(数据) 「View run」→ 主题抽屉 ⚠️→✅ 曾死链(简报无 topicId);本次运行已接通
Error-message copy(反馈 §4.5) 标题 + 摘要(InboxBriefCard.tsx ⚠️→✅ 曾是带原始 id 的日志风格;本次运行已修复
可行动原因的修复路径 预算超限 → 充值/升级 缺失——没有指向计费的链接(缺口②)

读法(Read):卡片的骨架(分组、关闭/恢复按钮、卡内渲染)是健全的;薄弱点完全聚集在 Feedback 家族——失败「读起来是什么样」(文案、严重度图标),以及所给的动作能否真正解决原因。

四、亮点:这些行为不允许回归

ux-audit 要求审计必须「report the good, not only the gaps」——只列缺陷的审计已经漂移成 bug report。本次运行确认了三处 ✅ 亮点

4.1 错误是一等公民、且被显式排序的桶

splitBriefs.tsdecision / error 型简报路由进 needsYou,且错误永远沉底——卡住的决策此刻正阻塞 Agent,而已失败的运行已经停下、可以等一等:

const NEEDS_YOU_ORDER: Record<string, number> = {
  decision: 0,
  error: 9,
};

splitBriefs() 用稳定排序在服务端既有的「优先级 + 时间」顺序之上做桶内重排,因此一次失败运行总是浮现在「需要你处理」列,而不是被新闻流静默吞掉——收件箱永远不会悄悄咽下一次失败。这一点有配套测试 splitBriefs.test.ts 守护。

4.2 卡内恢复 + 关闭,不掀翻表面

重试 / 忽略BriefCardActions.tsx 中就地解决,收件箱其余内容保持完好——这是一个做对了的 Same-Page error:失败把控制权交还给用户,而不是把整个表面炸掉。

4.3 错误文案已修复为面向人(→ 已回灌 ux §4.5 ✅)

修复后的行为:标题在卡片侧本地化(t('inbox.error.title')),原因显示为干净的从句,topic id 移入结构化的 topicId 字段并由此点亮「View run」——这正是新的 Feedback §4.5 规则旁边引用的 ✅ 示例。

⚠️ 复查捕获(Review catch)——本地化标题必须限定作用域,不能一刀切。 首次实现对所有 type: 'error' 简报覆盖了标题,但仓库中 error 简报的生产者有 5 个,其中 4 个携带各自合法的独立标题:verify 类(failed verification / verification errored,见 verify/settle.ts)、心跳类(heartbeat timeout)、agent-signal 类(brief.agentSignal.selfReview.error.title——服务端已本地化)。一刀切的「运行失败」会把这些全部抹平。修复:把覆盖限定到运行失败简报(服务端存稳定的英文 <id> run failed,客户端只本地化这一个,否则回落到存储的标题)。

留给检查清单的教训:在按 type 覆盖某个共享记录类型的用户可见字段之前,先枚举该类型的所有生产者——一个 type 很少是单一来源。

五、体验缺口(按严重度排序)

缺口① 日志风格文案夹带内部 id —— ux §4.5(Meaningful)🟠【本次已修复】

原始标题模板 `${taskIdentifier} topic #${seq} (${topicId}) error` 渲染出 "T-1 topic #1 (tpc_5UBuAjUU4z6B) error":标题里带原始 topic id、topic #N 日志语法、T-1 与元信息行重复;摘要 `Execution failed: ${raw}` 带着控制台行前缀;且两者对所有 locale 都是英文。证据来自 L1(taskLifecycle/index.ts 的 error 简报生成)+ L2(截图)。

修复(已完成):在卡片侧本地化框架文案,存储干净的英文回落文案,把 id 移入结构化 topicId 字段。当前服务端实现可见于 taskLifecycle/index.ts:error 简报创建时 title: tHome('inbox.error.title'),并把结构化错误码写入 metadata: { error: { code: errorCode } },服务端还会把错误码映射到与聊天错误卡片相同的人读本地化消息(与界面共用同一份 error locale 文案来源,避免两处措辞漂移)。

缺口② 确定性原因只提供一个徒劳的 Retry —— ux §4.2(Meaningful・Certainty)🟠【已落地】

"Workspace budget exceeded" 是一个确定性的、用户可行动的原因——重跑同一运行只会再次撞同一堵墙——但卡片只给 重试 / 忽略,没有任何通往修复(充值/升级/计费)的路径。这正是第二节类别规范第 7 条的缺口。

修复(已完成):把已知终止性错误类型映射到修复动作。当前源码中,taskLifecycle/index.ts 定义了计费类错误码集合:

// Terminal error codes whose fix lives in billing, not in a retry — running the
// same task again just reproduces the same wall. For these the error brief leads
// with an "Upgrade" remedy instead of a futile Retry (ux Feedback §4.2).
const BILLING_ERROR_CODES = new Set<string>([
  ChatErrorType.InsufficientBudgetForModel,
  ChatErrorType.FreePlanLimit,
  ChatErrorType.SubscriptionPlanLimit,
  ChatErrorType.WorkspaceSubscriptionInactive,
]);

完成生命周期事件的结构化 errorCodeonTopicComplete 两个调用方透传进简报;计费类原因得到 upgrade 链接动作 + metadata.error.code。卡片侧配套修复见 BriefCardActions.tsx:当主动作是 link 类型时(如预算错误的「升级」),渲染为填充式主按钮并导航而非 resolve——「A link only navigates; it does not resolve the brief」,修复路径成为明确的 call to action,而 Retry 仅对瞬时原因保留为次级动作。已用 agent-testing T-220 验证:预算卡片渲染 忽略 + 升级方案 → 指向 settings/plans,不再出现 重试。

缺口③ 卡片「读起来不像错误」—— 严重度可读性(Certainty)🟠【已落地】

卡片唯一的状态线索是元信息行的 StatusGlyph,它由任务状态驱动——但出错时任务被设为 paused(ad-hoc 运行)或 scheduled(自动化的手动运行),而 StatusGlyph.tsx 源码注释明确写着:

Note `task:paused` deliberately renders
as the "waiting for human" hand: it means *pending review*, not "suspended".

也就是说:一次失败的运行与一次健康暂停显示同一个中性/等待图标——没有红色、没有告警图标(L2 截图确认了那个 🤚 就是这只「手」),卡片完全靠文案在说「这是错误」。

修复(已落地):对 type === 'error' 简报,在标题处渲染红色 CircleAlert。当前 InboxBriefCard.tsx 中可见对应实现(const isError = brief.type === 'error' 驱动错误强调样式),注释强调这是「the one true failure glyph, no extra icon set」。已用 agent-testing T-220 验证。

缺口④ 错误简报上的「View run」是死链 —— Overview + Detail(Meaningful)🟡【本次已修复】

BriefCardActions.tsxshowViewRun = !!taskId && !!topicId,但 error 简报的 create() 从未传 topicId——于是唯一能查看为什么失败的入口从未渲染:用户可以重试或忽略,却不能「看」。

修复(已完成):把 topicId 传入 error 简报使「View run」出现。agent-testing T-220 验证:两个携带 topicId 的简报都渲染出可见的「查看运行轨迹」入口;没有 topicId 的旧数据简报则不渲染——这也顺带成为 L2 对结构字段缺省行为的真实验证。

缺口⑤ 忽略 是永久关闭且无撤销 —— Act(Certainty)🟡【已记录,未落地】

忽略 调用 handleResolve('ignore') 直接解决简报,无确认、无撤销;误触关闭的错误就永久离开收件箱。严重度低(运行仍可在任务列表中看到),记录在案但不阻塞。

六、技能回灌:审计如何反哺 ux 检查清单

ux-audit 与 ux 技能构成一个闭环ux 是审计的度量基准,审计是让 ux 保持诚实的机制。本次运行回灌的内容(SKILL.md 称之为「回灌」,且为强制步骤):

  • 新增 ux Feedback §4.5 ——「错误文案写给人看,不是写日志行」:新小节 + 4 条检查项 + Quick-review 镜像,以本收件箱错误简报为 ❌ 示例、修复后为 ✅ 示例。覆盖:不含内部 id / 不含日志框架 / 不与元信息行重复 / 已本地化。
  • 扩展 ux Feedback §4.2 —— 确定性原因条款:当一个失败的修复在别处(预算/配额/权限)时,应以修复动作领衔,而非裸 Retry;同步镜像进 Quick review。
  • 验证既有规则:§4.2「失败状态要命名失败并提供 Retry」被该卡片实例验证(卡片确有 Retry),Retry 门控原则由缺口②扩展。
  • 本次具体落地的修复:缺口①(文案)+ 缺口④(View-run 接线),并附带服务端回归测试 onTopicComplete.test.ts——断言简报标题/摘要不携带内部 id 或日志框架、topicId 落在结构化字段上。
  • 后续(未落地):缺口②(修复动作映射)与缺口③(错误严重度图标)——建议挂在「HomeInbox UX」父 issue 下的子 issue。

值得强调的机制细节:回灌不止是「给规则贴一个 ✅/❌ 例子」。好的案例只有在其教会规则新东西时才值得落地——本次缺口②教会了 §4.2 一个新区分(「修复在别处的失败」vs「瞬时失败」),缺口①教会了 §4.5 四条可检查的子项,这正是审计让清单「比发现它时更锋利」的方式。

七、待办:L3 动态层会补充什么

L3 未在本次运行,原文档列出了它的计划验证项——这也展示了三层法各自的边界:

  • 强制一个任务进入终止性 budget-exceeded 错误,并在线确认:(a)「View run」现在渲染(缺口④修复);(b) 卡片仍读起来像 pending 而非 failed(缺口③);(c) 重试 重跑并立刻再次失败(缺口②——以行为方式证明「徒劳 Retry」论断)。
  • 确认 zh-CN 会话下本地化标题渲染,以及携带旧 Execution failed: 前缀的遗留行能走防御性剥离。

从源码结构看,服务端对自动化失败的容错由 taskLifecycle/index.ts 中的熔断器控制:AUTOMATION_FAILURE_FUSE = 3——连续 3 次自动 tick 失败才暂停任务/停止重新武装并让紧急简报浮现给人类;单次瞬时错误(429/网络/上游 500)不会永久停掉一个循环任务。这意味着 L3 强制复现时,触发条件本身就是「连续失败」语义,而非单点故障。

八、可复用要点:如何审计你自己的错误通知表面

这次 worked example 沉淀出的可执行清单:

  1. 先定表面类别,写下期望能力清单,再读代码——代码审计对「从未构建的能力」结构性失明。
  2. 结论必须来自能看见它的层file:line 支撑 L1 结论,渲染截图支撑 L2 结论,强制状态/旅程驱动支撑 L3 结论;不要用 L1 打 L2 的勾。
  3. 错误卡片八问:像错误吗?说什么/哪个实体/何时?为什么?能恢复吗?能关闭吗?能查看吗?原因可行动时有直达修复吗?有反馈通道吗?
  4. type 覆盖用户可见字段前,先枚举所有生产者——type 很少是单一来源。
  5. 确定性原因领修复动作,瞬时原因领 Retry——把错误码显式映射到动作(本仓库的做法:BILLING_ERROR_CODESupgrade 链接 + metadata.error.code)。
  6. 报告亮点,而不仅报告缺口——亮点是「不要回归」清单,也是下次重构的承重墙;且只有教会规则新东西的好案例才值得回灌。
  7. 审计以「落地」为完成:具体 bug 修复或开 issue,可泛化缺口回灌检查清单,报告归档为下次模板——跳过闭环,审计就退化成一次性 review。

参考资料

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391