首页
/ LobeHub UX 审计实战:代理档案页的三层审计方法与「回灌」闭环

LobeHub UX 审计实战:代理档案页的三层审计方法与「回灌」闭环

2026-09-06 17:01:38作者:龚格成

本文以 LobeHub 仓库中一份真实的 UX 审计样例——代理档案/角色编辑页(Agent Profile)——为主体,完整还原 ux-audit 技能「三层审计 + 证据驱动 + 发现回灌」的工作方式:如何给页面做模式清点与缺口排序、如何用 file:line 级证据支撑每一条结论、以及审计结果如何回流到 ux 检查清单形成闭环。读完你可以掌握一套可复现的、以代码证据为基础的界面体验审计方法,并能对照当前源码看懂该页已落地与待验证的改进。

一、ux-audit:一个三层、证据驱动的审计框架

该样例隶属于仓库内的 ux-audit 技能定义。它的定位是对**单个页面(surface)**做一次可重复、基于标准的体验审查,基准是两样东西的组合:

  1. 模式语言——参考 pattern-catalog.md 中的界面设计模式目录,回答「这个页面用了哪些模式、用得多好」;
  2. 行为检查清单——ux 技能中各模块(Loading、Empty、Error、Retry、Draft safety 等)的检查项,回答「流程应该怎么表现、哪里体验偏弱」。

每次审计只做一个 surface,全应用横扫超出单次能力,逐页重跑才是「持续」的来源。审计分三层,按「哪一层真的看得见某类结论」来选择:

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

框架里有一条核心纪律(Coverage Matrix):任何结论必须来自能真正「看见」它的层——不能从代码里的 variant 属性打勾「只有一个主按钮」,视觉与运行时结论必须落到 L2/L3。分层的成本意识也写进了规则:L1 永远跑;发现集中在布局/层级/渲染态/响应式时加 L2;需要走完整旅程、强制 L1/L2 够不到的状态或测性能时才加 L3。

另外两条 ground rule 直接决定了这份样例的写法:

  • 证据而非感觉(evidence, not vibes):每条发现必须引用证据——L1 是 file:line,L2 是已核对的截图,L3 是采集值/快照;
  • 对标 surface 的「类」,而不只是自己的实现:读代码只能发现「已建成的东西」的缺陷,对整个能力缺失是结构性盲的(没有 file:line 可 grep)。所以审计前要先给出「这个类目的成熟产品(如 GPT 编辑器、Character.ai、Dify)在这个屏面上标配哪些能力」的期望清单,再逐项核对存在/缺失。

二、审计对象定位:代理档案页与它的「类基准」

这份 worked example 是 2026-07 对代理档案/角色编辑器的一次真实执行(内部编号 LOBE-11215),对应路由 /agent/:aid/profile,代码位于 src/routes/(main)/agent/profile/index.tsx/agent/profile/index.tsx)。文档明确声明:该样例是输出形态的模板,不是当前状态的事实快照——「代码在动,引用前先重新验证」,这个自我约束本身就是方法论的一部分。

该 surface 的组成(自外向内):

  • 导航头:面包屑(AgentBreadcrumb)+ 自动保存提示(AutoSaveHint)+ 状态标签 + More 菜单;
  • 档案编辑器:头像/名称/背景、模型与工具配置(或异构代理配置面板);
  • 系统提示词富文本编辑器(Lexical,带 mention/表格/slash 插件);
  • 常驻挂载的编辑锁驱动器(EditLockDriver)与右侧 Agent Builder 协作副驾。

类基准核对(custom-agent/character editor 类的类规范,逐项对照):

类规范 核对结果
实时预览 / 试跑代理 ✅ Agent Builder 副驾 + 相邻的 chat 标签页
版本历史 / fork AgentVersionReviewTag / AgentForkTag
协作编辑安全 ✅ 编辑锁
可见保存态的自动保存 ⚠️ 存在,但当时无法展示失败(缺口②)
配置→回看数据的闭环 ✅ More 菜单直达 /agent/:id/stats,无缺口

结论:该类的能力规范大体齐备,短板集中在失败处理,而不是能力缺失——这是审计报告开篇就给出的定性判断。

该次执行的层覆盖:L1(静态/代码)已完成(下节全部内容);L2(视觉)/L3(动态+CLS)当时未执行(见第八节待验证清单)。因此所有关于渲染的表述都是 L1 推断,标注「pending L2」。

三、L1 产出之一:模式清点表(Patterns in use)

L1 程序的第二步要求逐族走一遍模式目录,给每个块打上 Tidwell 模式标签并评级:✅ 扎实 / ⚠️ 部分或有误用 / — 缺失但预期。该审计的清点结果如下(引自样例文档,Where 列中的文件:行号是审计时点的证据位置,均相对 profile 特性目录):

模式(族) 位置 评级 备注
Visual Framework(布局) NavHeader + WideScreenContainer 外壳(index.tsx:46,61 一致的 chrome
Breadcrumbs / Deep-linking AgentBreadcrumb/agent/:aid/profile 可恢复 surface
Center Stage(布局) 提示词编辑器主导画布
Form / Titled Sections(输入) 头像+名称头、模型/工具面板、提示词编辑器
Good / Smart Defaults(输入) inbox → 「Lobe AI」默认名称+默认头像(Content.tsx:77,82
Autosave(反馈) AutoSaveHint saving→saved,防抖写(store/action.ts:42 ⚠️ failed(缺口②)
Collaborative lock(反馈) EditLockDriver 在渲染前 peek;他人持锁则只读 亮点,见第四节
Streaming-aware editing 流式期间抑制保存,结束冲刷(store/action.ts:76-112 亮点,见第四节
Overview + Detail(数据) More → Advanced Settings 模态AgentSettings 保持 surface 契约(模态而非跳转)
Cross-surface entry(增长) More 菜单 → /agent/:id/stats,面包屑回 chat(Header/index.tsx:200 亮点,配置→回看闭环(第四节)
Rich-text editor + toolbar Lexical 编辑器,mention/表格/slash 插件(EditorCanvas
Loading Skeleton(反馈) 配置解析期间品牌化 Loadingindex.tsx:42 ⚠️ 成功-only 门控 → 出错时永久转圈(缺口①)
Failure + Retry(反馈) store 里建模了(agentConfigErrorMap + selectors + retryFetchAgentConfig — 缺失 建好了但没接到任何界面(缺口①)
Empty / Not-found 态 — 缺失 非法 :aid → 永久 loading(缺口①)

报告给出的整体判读(Read):布局、深链、协作锁、流式处理与配置→回看闭环都很成熟,属真正强的部分;弱点全部聚集在 Feedback(失败态)——一条存在于 store 却到不了任何像素的错误路径,和一个结构上无法报告失败的自动保存。

四、L1 产出之二:亮点(Strengths,「别回退」基线)

框架有一条 ground rule:只列坏处的审计会漂移成 bug report。亮点是一等发现,要给出 file:line 并标注「✅ 亮点」,因为它们承担三重作用——教(成为 ux 检查清单的 ✅ 示例)、保(下次重构的「不许回退」清单)、校准(缺口的严重度相对强基线才有意义)。该审计记录的四个亮点:

  1. ✅ 亮点 — 编辑锁在编辑器渲染前就被 peek。 EditLockDriver 挂载在配置加载门控之外(审计时点 index.tsx:68-70,附解释注释),因此「别人正在编辑」的代理从第一帧起就是只读,而不是先闪一下可编辑再上锁。lockedByOther / lockPending 驱动真实的 EditingIndicator,并隐藏会与锁争抢的 Agent Builder。这是任何共享资源编辑器都值得抄的协作安全模型。当前源码中该意图依然清晰可见:EditLockDriver.tsx/agent/profile/features/EditLockDriver.tsx) 顶部注释写明「挂在 profile 树高位(不在 loading 门控的编辑器内),使锁在编辑器渲染前被 peeked on open」;store/initialState.ts/agent/profile/features/store/initialState.ts#L42-L46) 中锁初始态刻意设为 pending: true——「先只读,等锁解析完成,编辑器绝不闪可编辑」。
  2. ✅ 亮点 — 流式感知的保存门控。 Agent Builder 把生成的系统提示词流式写入编辑器期间,用户编辑检测与防抖保存被抑制,流结束时单次冲刷保存(审计时点 store/action.ts:76-112EditorCanvasfinishStreaming)。生成内容不会卷入保存竞争,用户的真实编辑也不会被误判为流式输出。当前的 profile store/action.ts/agent/profile/features/store/action.ts) 仍保留完整的流式保存机器:startStreaming 清空编辑器并置 streamingInProgressappendStreamingContent 逐块写入、finishStreamingenqueueSave 落一次完整保存后重置流式态——一个值得保护的细微正确性设计。
  3. ✅ 亮点 — 配置→回看闭环从配置上下文直达。 More 菜单直接链到 /agent/:id/statsHeader/index.tsx:200-206),面包屑回到该代理的 chat——「配置代理」和「看它干了什么」只隔一次点击。这是与其他设置页「承诺了目的地却没有门」形成对照的正面案例。
  4. ✅ (当时尚未接线、值得保留)— store 已正确建模 error ≠ loading。 agentConfigErrorMap + currentAgentConfigError / isAgentConfigError + retry 动作(审计时点 selectors.ts:235-238action.ts:341-357)是正确的数据层,文档注释承诺「retry UI 而非无尽骨架」。意图正确;缺口①只是没有任何 surface 消费它们——所以这是一个要「收尾」的亮点,不是供观赏的。审计报告特别叮嘱:清理代码时别把它删了——把它接上。第七节会看到后续源码状态。

五、L1 产出之三:体验缺口(Experience gaps,按严重度排序)

严重度共用一套 rubric:🔴 破坏信任(数据/输入丢失、卡死/永久态、误导性「空态」掩盖失败、静默发送失败);🟠 死路或误导(无前进路径、状态含糊、缺进行中反馈);🟡 摩擦/不一致/错失愉悦。该审计排出的五个缺口及整改建议:

① 配置拉取失败 → 永久品牌 loading;error+retry 机器建好了却没接线 🔴 ProfileArea 当时只按 isAgentConfigLoading 分支(index.tsx:42),而该选择器本质是「数据在不在 map 里」(!activeAgentId || !agentMap[activeAgentId])——正是 ux 清单 §4.2 警告的「用数据存在性冒充初始化标志」。getAgentConfigById 失败或 404 时 onData 永不执行,agentMap[id] 保持 undefinedisAgentConfigLoading 永远为 true → 整页 <Loading/> 无原因、无重试。坏的/已删除的 :aid 与「加载很慢」不可区分。关键在:onError 确实把错误记进 agentConfigErrorMap[id],选择器和 retryFetchAgentConfig 也都在——但全仓库 grep 零消费者,store 为之构建的 retry UI 是死代码。整改:在 loading 门控之前先按 isAgentConfigError 分支 → 展示失败态(原因 + 调用 retry 的 Reload);loading 只留给「无错且无数据」。

② 自动保存无法表达失败——整个 surface 的每次保存都静默失败 🔴 AutoSaveHint 的保存态枚举是 'idle' | 'saving' | 'saved'AutoSaveHint.tsx:12),没有 failed 变体,机器在类型层面就无法显示写失败(页面编辑器、任务详情样例携带过同款陷阱)。下游所有写入端都吞掉失败:防抖提示词保存与 finishStreamingcatch → console.error;标题(AgentHeader.tsx:49)、头像/背景(:58, :96)、模型/工具(ProfileEditor/index.tsxupdateConfig)、高级设置模态的乐观写入(AgentSettings/Content.tsx:59, 65)都不上抛。保存失败的提示词编辑与保存成功的看起来一模一样——「saved」标签贴在数据丢失上。整改:枚举加 failed + 内联 Retry(保留已编辑值),由各写入端 catch 驱动,一个约定贯穿整个 surface。

③ 头像上传没有 catch——上传失败是静默的 🟠 handleAvatarUploadtry { … } finally { setUploading(false) }catchAgentHeader.tsx:72-79),只有客户端超尺寸分支会 toast。被拒绝的上传(网络/服务端/编码后超尺寸)只是关掉 spinner——无 message.error,rejection 还外泄成 unhandled promise。整改:catch → message.error 并提示重试;保留上一张头像。

④ 提示词/标题草稿没有本地持久备份——重载最多丢 30 秒编辑 🟡 名称存在 useStatelocalTitleAgentHeader.tsx:37)里,提示词留在编辑器里直到防抖写(1 秒防抖、maxWait 30 秒)到达服务端;没有像聊天输入框 useChatInputDraft 那样的 localStorage 草稿。在防抖窗口内重载/崩溃,最后一段编辑直接蒸发且不可恢复。比输入框温和(它确实自动存到服务端),但对用户书写长系统提示词的这个 surface,30 秒 maxWait + 零本地备份是真实的丢失窗口。整改:镜像 useChatInputDraft——按 agent id 把编辑器草稿备份到 localStorage,unmount 时冲刷,确认保存后清除。

⑤ 高级设置模态硬编码 loading={false} 🟡 AgentSettings/Content.tsx:93 无条件传 loading={false},若 config/meta 尚在解析,模态会装作已就绪。当前影响低(整个 surface 被 isAgentConfigLoading 门控,store 通常先填充好),但属潜在的成功-only 假设。整改:从真实的 config 解析态推导 loading

六、Skill feedback:发现如何「回灌」到 ux 清单

框架要求每次审计必须以「回灌」收尾才算完成:具体 bug 修掉(或挂到 Linear 的 UX Audit 父 issue 下);可泛化的缺口强制回流到 ux 技能——加/强化检查项(规则 + ✅/❌ 示例,并镜像一行到 Quick review),以被审计的 surface 作为 ❌ 示例;审计本身存为 references/example/<page>.md 供下次当模板。ux 是审计的标尺,审计是让标尺保持诚实的机制——跳过回灌,审计就退化成一次性评审。

该样例产出的回灌内容:

  • 新落地的子规则(可泛化的那条):Feedback §4.2 强化——「存在于 store 但没有任何 surface 消费的 error 态与 retry 动作,仍然是缺失的 error 态」;建了一半而孤儿化的失败路径与完全没建无法区分,检查手段就是 grep error 选择器的消费者数。❌ 示例即缺口①(isAgentConfigError + retry 动作已建,rg → 0 调用点,拉取失败时永久品牌 loading)。此规则此前并不存在——旧 §4.2 文本假设错误路径是「被忘了」,而不是「建了一半被孤儿化」。
  • 验证了既有规则(作为好的 ❌ 示例引用,不算新规则):§4.4(缺口②,idle|saving|savedfailed 枚举——页面编辑器、任务详情之外的第三例);§4.2 数据存在性冒充初始化标志(缺口①的 !agentMap[id] 门控);§4.2 写入侧无 catch(缺口③);Edit §2.1(缺口④,自动存服务端仍欠一份本地草稿)。
  • 记录但不新立规则的好案例:「渲染前 peek 编辑锁」与「流式感知保存门控」虽强,但它们复证的是已经完整的规则(协作安全/保存竞争正确性),没有揭示缺失的区分度——所以只进「别回退」清单,不并入检查表。

七、源码级跟踪:审计缺口在当前代码中的落地状态

样例文档自我声明「不是当前状态的事实」,而当前仓库代码恰好提供了难得的「审计→整改」对照。以下均为从当前源码结构可见的事实:

缺口①(失败→永久 loading)已被接线。 当前 profile/index.tsx/agent/profile/index.tsx#L51-L68) 的 ProfileArea 使用 AsyncBoundary 做三分支门控,源码注释直接把审计的教训写进了代码:

<AsyncBoundary
  data={isAgentConfigLoading ? undefined : true}
  error={configError}
  errorVariant={'page'}
  isLoading={isAgentConfigLoading && !configError}
  loading={<ProfileSkeleton />}
  onRetry={() => retryAgentConfigFetch()}
>

即「error 分支优先于 loading 分支:出错时展示错误态与重试,loading 只留给真正加载中」。数据层同步增强:src/store/agent/selectors/selectors.tsisAgentConfigLoading 现在还会检查 agentNotFoundMap——注释写明「not-found 的 agent 永远不会进 agentMap,按已 settled 处理,让 UI 渲染 404 卡片而非无尽骨架」,并新增 isCurrentAgentNotFound 区分「不存在/失权」与「拉取失败」;currentAgentConfigError 的文档注释也与审计整改描述一致:「区分 fetch failed 与 no data yet,使失败展示 retry UI 而非无尽骨架」。重试动作现名为 retryAgentConfigFetch(见 src/store/agent/slices/agent/action.ts 第 489 行起的实现,即审计时点 retryFetchAgentConfig 的更名继任者),并有测试守护:slices/agent/action.test.ts 中「should record fetch error in agentConfigErrorMap and clear it on retry」验证了错误记入 map、重试后清除的完整生命周期;selectors.test.ts 覆盖 isAgentConfigLoading 的各分支。

缺口②(保存态无 failed)已在类型与 store 层落地。 共享的保存态类型 src/types/saveState.ts 现为 SaveStatus = 'idle' | 'saving' | 'saved' | 'failed'——其文件头注释正是在描述审计指出的陷阱(「无 failed 成员时每个 catch 都只能静默」)。当前 profile store/action.ts/agent/profile/features/store/action.ts#L57-L112) 的保存队列实现了完整的失败生命周期:每次保存带递增 revision,入队时清 failedSave 并置 promptSaveStatus: 'saving';写入失败时若该 revision 仍是最新一次,则记录 failedSave 并置 'failed'retryPromptSave 动作重放最近一次失败的保存。防抖窗口也由常量 EDITOR_DEBOUNCE_TIME / EDITOR_MAX_WAIT(来自 @lobechat/const,审计时点记录为 1 秒/30 秒)统一配置,且每个 agent 拥有独立防抖器——「在 agent B 里打字绝不替换 agent A 的 trailing save」。

需要保持证据边界:样例中的行号引用(index.tsx:42store/action.ts:76-112 等)是 2026-07 审计时点的位置,当前文件行号已漂移;第七节的对照基于当前仓库代码的独立阅读,而非审计文档的转述。缺口③(头像上传 catch)、④(本地草稿备份)、⑤(模态 loading 推导)是否在最新代码中全部修复,本文未逐一核验,可参照样例第五节的验证清单自行确认。

八、未完成的 L2/L3 验证清单

样例第五节把「还不能由 L1 下结论的部分」显式留给了后续层,这正体现了 Coverage Matrix 的纪律。待办:

  • L2(视觉)——确认提示词编辑器读起来是主导性的 Center Stage;检查 AutoSaveHint 标签的可读性(saving vs saved vs 「latest」);锁定编辑器的 opacity 0.65 可读性;窄屏下头像+名称行与异构配置标签页的布局;深色模式。
  • L3(动态)——
    • 强制 getAgentConfigById 失败 / 导航到伪造的 :aid实测确认缺口①(当时预期:永久品牌 loading、无重试),并检查是否有路由重定向/错误边界先行兜底;
    • 强制保存写入被拒绝,确认缺口②(surface 显示「saved」/无失败反馈);
    • 驱动一次头像上传失败,确认缺口③;
    • 在防抖窗口内编辑时重载,确认缺口④(丢失的提示词编辑)。

九、小结

这份 profile 审计样例完整示范了 ux-audit 技能的一次标准运行:先给出 surface 的类基准期望清单,再用 L1 产出「模式清点表 + 亮点(别回退)+ 排序缺口(带 file:line 证据与一行整改)」,然后把可泛化的发现回灌ux 检查清单,最后把不能由代码下结论的部分明确挂起给 L2/L3。对照当前源码可以看到这套方法的实际闭环:审计标记的两条 🔴 缺口(失败态无 UI 消费、保存态无 failed 成员)在最新代码里分别以 AsyncBoundary 三分支门控 + retryAgentConfigFetchSaveStatusfailed 成员 + retryPromptSave 落到了实处,并有单测守护。相关文件入口:SKILL.mdL1 程序模式目录审计样例、profile 路由入口/agent/profile/index.tsx)。

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

项目优选

收起
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
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388