首页
/ LobeHub UX Audit 实战复盘:Agent 文档视图的三层审计方法、六大体验缺口与 Skill 回灌闭环

LobeHub UX Audit 实战复盘:Agent 文档视图的三层审计方法、六大体验缺口与 Skill 回灌闭环

2026-09-06 15:55:12作者:丁柯新Fawn

本篇以 LobeHub 仓库内 ux-audit 技能的一份真实运行记录为主线,完整复盘对助理文档视图/agent/:aid/docs/:docId,路由实现位于 src/routes/(main)/agent/docs/agent/docs))的一次 UX 审计:先讲清该技能的三层审计方法与“按界面类别对标”的基准原则,再逐条拆解这次审计产出的模式清单、六个“不可回退”的亮点、六个按严重度排序的体验缺口及其源码证据链,最后展示审计结论如何回灌(回灌)进 ux 技能形成持续改进闭环。读完你可以掌握一套可复制的、基于证据(而非观感)的前端体验审查方法,并理解 LobeHub 如何把审计沉淀为团队可复用的工程资产。

一、审计对象、运行层级与判定口径

这份记录是 ux-audit 技能在 2026-07 对“独立助理文档视图”的一次真实运行(对应 LOBE-11214,隶属 Chat UX Audit 表面 LOBE-11145),其定位是输出形态的模板而非现状快照——文档明确提醒:代码会演进,引用前必须重新验证(事实上,如后文缺口①所述,当前仓库中该处已演进为带重试的 AsyncError 页面级错误态)。

运行层级:L1(静态/代码层)✅ 已完整执行,即本文下述全部内容;L2(视觉层)与 L3(动态层 + CLS 指标)⏳ 尚未运行。因此,文中所有关于渲染效果的结论都是 L1 推断,待 L2 确认。这一层位标注是 ux-audit 的核心纪律——在 SKILL.md 的覆盖矩阵中,视觉层级、对比度、断点等结论只有 L2/L3 有权下判定,不能从代码打勾;“缺失的空/错误分支、无重试、草稿未持久化”等静态结论则归 L1。

表面类别与对标基准:这是一个嵌入在 agent 上下文里的文档编辑器,同类成熟产品包括 Notion 页面、Google Docs、Craft、Coda。审计开始前先对照类别规范逐项检查:

  • 带可见保存状态的自动保存:⚠️ 存在,但结构上无法表达“失败”(缺口②);
  • 草稿/崩溃恢复:⚠️ 存在,但仅覆盖协同锁降级窗口(缺口⑥);
  • 协同编辑安全:✅ useDocumentLock 打开时探锁 + 他人只读 + CONFLICT 处理;
  • 版本历史:✅ PageEditor/History
  • 完整 CRUD + 删除确认 + 乐观回滚:✅ 亮点
  • 分享/复制链接/导出:✅;
  • 带重试的加载失败态:❌ 最大缺口——错误 UI 已构建但被渲染顺序“判死”(缺口①)。

总体判定:能力项几乎全部在场,弱点高度聚集在加载与保存的失败处理上。这是一个典型的“成功路径打磨良好、失败路径静默吞掉”的界面样本。

二、模式清单(Patterns in use)

L1 审计的第一张表回答“这个表面用了哪些模式、用得多好”。下表完整继承自该次运行的输出,每行都带 文件:行号 证据(行号为 2026-07 快照,供溯源,引用前建议重新核对):

模式(族) 位置 评级 备注
Visual Framework(布局) NavHeader + 双栏 Layout(编辑器 + 右侧面板)(Layout/index.tsx:9 一致的外观框架
Breadcrumbs / Deep-linking agent→doc 面包屑;/agent/:aid/docs/:docId 可恢复表面(Header/index.tsx:46 agent 标签可返回聊天
Center Stage(布局) PageEditor 富文本画布占据主体(PageEditor.tsx:273
富文本编辑器 + 工具栏 Lexical 编辑器,slash / ask-copilot / block 插件(EditorCanvas
Overview + Detail(数据) 右侧面板 Documents/Skills 浏览器(RightPanel/index.tsx:122 无普通文档时自动切到 Skills 页签
全生命周期 CRUD(操作) 新建/文件夹/重命名/移动/删除,乐观更新 + 回滚(useDocumentTreeOps.ts 亮点,见下节
破坏性操作确认(操作) 每个删除点都包裹 confirmModalHeader/useMenu.tsx:91useDocumentTreeOps.ts:388 亮点
协同锁(反馈) useDocumentLock 打开时探锁、他人只读、锁翻转时重新水合(useDocumentLock.ts 仅限 workspace 页面
保存冲突处理(反馈) CONFLICT 保存 → 转只读 + 保留 isDirty 使内容可复制(editor/action.ts:353 亮点——唯一被正确处理的失败路径
受管文档守卫(输入) skill 的 SKILL.md 索引:metaReadOnly 锁定标题/emoji,避免 bundle 失步(index.tsx:80 亮点
草稿/崩溃恢复(编辑) usePageDraft → sessionStorage 快照 + 打开时确认恢复(usePageDraft.ts ⚠️ 仅锁降级时生效 → 个人文档无备份(缺口⑥)
加载骨架(反馈) 内容解析期间显示 EditorSkeletonDocumentIdMode.tsx:20 ⚠️ 数据存在性充当加载标志 → 出错/不存在时永久停留(缺口①)
自动保存(反馈) AutoSaveHint saving→saved,防抖写盘(editor/action.ts:310 ⚠️ failed 状态 → 保存失败静默(缺口②)
加载失败 + 重试(反馈) EditorError 告警存在(DocumentIdMode.tsx:29)但排在加载门之后 — 缺失 首屏加载时不可达;无重试(缺口①)
空/不存在状态(读取) — 缺失 已删除/非法 docId → 永久骨架(缺口①)
列表数据态(读取) 右侧浏览器:loading/empty/error 全部渲染(AgentDocumentsGroup.tsx:369 亮点——列表侧做到了文档侧没做到的
跨表面入口(增长) 面包屑→聊天;浏览器→其他文档;复制链接;锚定聊天话题(lab) 文档↔聊天闭环

如何读这张表:布局、CRUD、协同锁、受管文档安全与列表侧数据态都已成熟,确实是强项;弱点完全聚集在 Feedback(加载 + 保存失败):一条“已写好但被顺序判死”的错误/不存在路径,和一个结构上无法上报失败的自动保存。

三、亮点与好案例(不可回退基线)

ux-audit 要求“报告好的,而不只是报告缺口”——亮点是✅ 半边回灌素材,也是下次重构的“不要回退”清单。本次运行识别出六个:

  1. ✅ 亮点 — CRUD 是乐观更新 + 真实回滚 + 失败 toast。 新建文档/新建文件夹/重命名/移动/删除全部先施加乐观变更,失败后回滚到快照并弹出 toastuseDocumentTreeOps.ts——新建 :199,220、文件夹 :152,161、重命名 :270,283、移动 :329,352、删除 :430,459)。不存在静默回滚(Act §3 的陷阱)。这是任何树/列表 CRUD 值得抄的范式。
  2. ✅ 亮点 — 每个删除都确认。 头部 More 菜单(Header/useMenu.tsx:91-110)、浏览器树(useDocumentTreeOps.ts:388)、web 列表项与 skill 行(AgentDocumentsGroup.tsx:163,457)共四个调用点,全部把 removeDocument 包进 confirmModal,带危险色确认按钮与错误 toast。破坏性操作纪律在四处保持一致。
  3. ✅ 亮点 — 唯一被建模的保存失败,被建模得很好(锁 CONFLICT)。 协同 CONFLICT 时,保存路径经 saveBlockedByLock 把编辑器翻转为只读,并保留 isDirty,让未保存内容留在屏上可被复制走,而不是丢弃编辑(src/store/document/slices/editor/action.ts,原引 :353-364),并有专门的恢复路径清掉陈旧阻塞(clearSaveBlockedByLockuseDocumentLock.ts :152)。这正是 Feedback §4.4“保留编辑值 + 点名原因”的行为——也让缺口②更显眼:通用的网络/500 失败在同一个 catch 里被重置回 idle,享受不到这份照顾。
  4. ✅ 亮点 — 受管文档身份守卫。 skill 的 SKILL.md 索引文档显示 bundle 标题,且其标题/emoji 被 metaReadOnly 锁定只读(AgentDocumentPage/index.tsx:80-92),因为普通标题保存会覆写 SKILL.md 文件名、使 bundle 失步。这是一个体贴的“别让通用编辑器弄坏受管实体”的守卫。
  5. ✅ — 拉取时的陈旧响应竞态守卫。 useFetchDocumentonData 会丢弃 documentId 已不再是当前激活文档的响应(store/document/slices/document/action.ts:234-239),快速切换文档不会把前一篇内容水合进编辑器。
  6. ✅ — 草稿恢复在其作用域内足够谨慎。 usePageDraft 打开时只提示一次(而非每次重挂载)、执行 24 小时时效、并在锁恢复文档干净时清除快照(usePageDraft.ts,原引 :123-166)。当前源码中这一行为依然成立:恢复确认通过 promptedRef 保证同一 documentId 只弹一次,并在 lockHealth === 'healthy' && !isDirtyclearPageDraft。工艺没问题——限制在作用域(缺口⑥),不在手法。
  7. ✅ — 右侧浏览器把四种数据态全做对了。 加载中(NeuralNetworkLoading)、空态(带图标+文案的 Empty)、错误(Text type=danger)全部渲染,且文档树为空时工具栏仍可触达(AgentDocumentsGroup.tsx:369-383,532-537DocumentExplorerTree.tsx 空态)。这是同一表面上文档正文只渲染 loading + success(缺口①)的✅ 对照组——审计的严重度锚点由此确立:缺口是局部遗漏,而非整个表面的疏忽。

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

每条缺口都给出:违反的 ux 清单项、所在层级 + 源码证据链、一行修复方向。

① 首屏拉取失败与 not-found 都落成永久骨架;EditorError 已构建但被顺序判死(🔴)

违反 ux §4.2 / Read §1.1。证据链:

  • DocumentIdMode 基于真实 SWR error 渲染 {error && <EditorError/>}DocumentIdMode.tsx :223),但它位于 if (isLoading) return <EditorSkeleton/>:209之后
  • isLoading = editorSelectors.isDocumentLoading(id) = !documents[id]selectors.ts——该“数据存在性冒充的初始化标志”至今仍在源码中,isDocumentLoading 的实现就是 !id || !s.documents[id]),即 §4.2 警告的数据存在性充当代理
  • useFetchDocument 只在 onData 里写 documents[id],而 not-found 时返回的是 **null(不是 throw)**并被提前 return 丢弃(store/document/slices/document/action.ts:219-222,228-260)。

于是:首屏 500 → 数据条目永远落不了地 → isLoading 永远为真 → 骨架短路,下方的 EditorError 不可达;它只能在一个“已加载内容之上的 focus 重验证失败”时才被画出来。已删除/非法 docId 撞同一堵墙 → 永久骨架而不是 404(Read §1.1:失败/不存在/仍在加载三者被混同)。两条路径都没有重试(SWR 仅在 focus 时自动重验证,action.ts:261)。

这个缺口之所以尖锐,是因为修复几乎免费——错误组件已经存在、且已经读 error。修复方向:在加载门之前先分支 error 与已解析的 null → 得到失败态(原因 + 经 mutate 的 Reload)与真正的 not-found;骨架只保留给 !error && 尚无数据

现状旁注:本次研究读取当前仓库时,DocumentIdMode.tsx 已演进为 if (error && isLoading && !isFetchingDocument) 时渲染带 onRetry={mutate}AsyncError(variant=page),remoteDocument === null 时渲染 NotFound——即缺口①的“错误分支前置 + 真实 404 + 重试”方向已经落地。这恰好印证了该记录“模板而非现状”的自我定位。

② 自动保存无法表达失败——内容/标题保存失败被读作“已保存”(🟠)

违反 ux §4.4 / §4.2。文档保存状态枚举为 'idle' | 'saving' | 'saved'没有 failedinitialState.ts——当前源码中这一枚举仍未包含 failedperformSave 的 catch 分支仍把 saveStatus 重置为 'idle'),并在 AutoSaveHint.tsx :12 中镜像。performSavecatch 对所有非 CONFLICT 失败(网络/500)把它重置回 'idle',只写 console 日志(editor/action.ts:359-364)。

结果:文档仍 dirty、写盘已失败,而头部 AutoSaveHintHeader/index.tsx:67)显示“已保存最新 /saved”——类型层面的静默写盘陷阱,与页面编辑器、agent profile 已有记录同类。isDirty 保持为真,防抖保存在下次击键可能重试、UnsavedChangesGuard 在导航时也会自动保存(DocumentIdMode.tsx:110-127)——这是真实兜底——但一个停止编辑、也停止导航的用户,会无限期地坐在“丢了却显示已保存”的内容上。这是已经落地的 §4.4 规则(该规则直接点名 store/document/slices/editor/action.ts),本表面是新的确认而非新缺口。修复方向:给枚举加 failed + 一个保留编辑值的内联 Retry,由 catch 驱动。

③ 任何加载错误都没有重试入口——连可达的状态也是静态的(🟠)

违反 ux §4.2。EditorError 告警(按缺口①仅在重验证失败时可达)是静态 <Alert type=error>、无操作按钮(DocumentIdMode.tsx:29-41);浏览器的错误分支也是静态 <Text type=danger>AgentDocumentsGroup.tsx:377-383)。两者完全依赖 SWR focus 重验证;盯着它们看的用户没有任何原地重拉手段。修复方向:给两者加一个调用 SWR mutate 的 Reload 按钮(两个调用点都已能拿到 mutate)。

④ 头部/标题列表拉取吞掉错误——列表失败时标题静默显示占位(🟡)

违反 ux Read §1.1。useAgentDocumentItem 只解构 { data, mutate }、从不读 erroruseAgentDocumentItem.ts :20);listDocuments 失败时 itemundefined,面包屑渲染占位标题(Header/index.tsx:61),没有任何“元数据加载失败”的信号。比缺口①轻(正文拉取才是真内容),但同属“失败被压平成无物”的强制转换。修复方向:在标题上呈现一个克制的加载失败提示,或至少不要把占位当成真实(空)标题来呈现。

现状旁注:当前仓库中的 useAgentDocumentItem.ts 已把 errorisNotFound 一并返回(isNotFound 的注释明确说明这是为了“避免面包屑在 404 正文上渲染占位标题”,与 Read §1.1 的“failed-to-load ≠ deleted/404”完全同源),缺口④的修复方向也已落地。

⑤ 锚定聊天话题失败时面板静默消失(🟡)

违反 ux §4.2。useDocumentChatTopic 返回 { topicId, error, isLoading },但调用方只以 topicId 真假为渲染门槛(AgentDocumentPage/index.tsx :95),而 hook 只把错误写进 console(FloatingChatPanel/useDocumentChatTopic.ts :44-50)。话题查找/创建失败时 FloatingChatPanel 直接不出现——无错误、无重试。面板尚在 lab 标志(enableAgentDocumentFloatingChatPanel)之后,严重度偏低,但“静默消失”模式值得在它转正前修掉。修复方向:error 时渲染一条带 Retry 的紧凑加载失败条,而不是什么都不渲染。

⑥ 草稿/崩溃恢复不覆盖个人(非 workspace)agent 文档(🟡)

违反 ux Edit §2.1。usePageDraft 只在 lockHealth !== 'healthy' 时写 sessionStorage 快照(usePageDraft.ts :109-118——当前源码 :111 依然写着 if (lockHealth === 'healthy' || !isDirty) return;,行为未变),而锁的启用条件是 workspacePage = documentId && canEdit && isWorkspacePageuseDocumentLock.ts :60,88——当前源码中 enabled: workspacePageisWorkspacePage && documentId 的判定同样在位)。对个人/桌面本地 agent 文档,锁永远不会生效,lockHealth 停留在 'healthy' 默认值(PageEditor/store/initialState.ts:71),快照永远不会触发——未保存编辑只存在于内存中,仅靠 beforeunload 自动保存守卫保护。在自动保存防抖窗口(EDITOR_DEBOUNCE_TIMEEDITOR_MAX_WAIT)内硬崩溃/被杀/断电,最后几笔编辑将丢失且无本地恢复。比输入框场景窄(服务端自动保存仍是主副本),但用户可能以为存在的草稿安全网,对个人文档而言是不存在的。修复方向:在常规 dirty 路径也写快照(而非仅锁降级期间),或明确文档化草稿的作用域是锁窗口。

五、Skill 反馈(回灌):审计如何反哺 ux 技能

ux-auditux 技能构成闭环:ux 是审计的度量基准,审计是让 ux 保持诚实的机制。本次运行的回灌产出分三类:

1. 新通用缺口落入 ux

  • §4.2 —“错误分支排在‘数据存在性加载门’之后,首屏加载即不可达。” 既有 §4.2 的例子全部覆盖“错误路径缺席”或“孤儿在 store 里”。本表面是更新锐的形态:错误分支存在于同一组件、且读的是真实 SWR error,却位于 if (isLoading) return <Skeleton/>isLoading = !map[id])之下,于是首屏失败(以及解析为 null 的 not-found)永远到不了它——它只在重验证时才会被画出。已作为新段落 + ❌ 示例(Agent 文档视图)+ 清单项落入 feedback.md §4.2,并镜像进 Quick review 的 Feedback 行。→ 已作为 ux feedback §4.2 ❌ 落地。

2. 既有规则的验证性实例(无需新规则):

  • §4.4(自动保存状态无 failed——缺口②。规则本就点名 store/document/slices/editor/action.ts;本表面(同一份代码,从 agent-doc 头部看过去)是新的确认。
  • §4.2(加载失败须带 Reload/Retry)——缺口③。静态错误告警 + 静态列表错误文本、无重试,即已记录的“失败态必须携带 Reload”一行。
  • Read §1.1(失败 ≠ 空 / not-found)——缺口①、④。not-found → 永久骨架、列表拉取失败 → 占位标题,都是 §1.1 覆盖的“失败被压平成无物”。

3. 值得保留的好案例(见上节): 乐观 CRUD 回滚、删除全确认、CONFLICT 保存只读处理、受管文档 metaReadOnly 守卫是下次重构的“不要回退”清单。本次没有从它们中提炼出✅ 规则(每一个都只是对已完整规则的再例证——Act §3 下的乐观回滚、§4.4 下的保留值),因此按好案例回灌标准,只在此报告、不强制落地为 ✅ 示例。

六、方法论要点回顾

把这次复盘收敛成可迁移的做法,即 ux-audit 的三条地面规则:

  1. 证据,而非观感——每条发现都引用其证据:L1 是 file:line,L2 是用工具核验过的截图,L3 是抓取值/快照。在能“看见”该结论的层级里确认它再断言:一个错误的“它缺失”比没有发现更糟。
  2. 对标表面类别,而不只对标自家产物——读代码只能暴露“已构建之物”的缺陷,对“从未构建”的能力结构性失明。先写下这个类别的成熟产品提供什么(自动保存可见失败态、崩溃恢复、版本历史、带重试的加载失败……),再对着清单审计缺口,否则审计只会打磨已存在的路径、悄悄放过缺失的那一条。
  3. 报告好的,而不只是缺口——亮点是✅ 半边回灌素材与“不要回退”基线;只列缺陷的审计已经漂移成 bug 报告。

审计的闭环在于:单次运行产出的模板(本记录即 references/example/doc.md 形态)与回灌进 ux 清单的每条规则,让下一次审计的基准比上一次更锋利。这正是 LobeHub 把 UX 审查从“一次性评审”变成“可持续工程实践”的机制所在。

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