Record real Playwright ledgers, mark 20 audits and 9 remediations done, and leave management-implementation-plan as plan-only.
4.4 KiB
4.4 KiB
title, status, created, updated, severity
| title | status | created | updated | severity |
|---|---|---|---|---|
| UI-06 编辑弹窗打开期间 hydration 换块 ID 导致保存静默丢稿 | done | 2026-09-13 | 2026-09-22 | high |
UI-06 编辑弹窗打开期间 hydration 换块 ID 导致保存静默丢稿
W3 UI-rerun 关闭(2026-09-22):
status=done。真实 Playwright 账本 PASS:ui-acceptance-results.json(main UI-00..16)+ui-acceptance-results-post-restart.json(UI-15)。任务 tip 已合入本地dev;详见 short-term 与 测试报告。
现象
第十二轮真实 UI(schema 30,dev@4c465d2 基线):
- 双击正文块、fill 标记、点击「保存修改」动作均成功,编辑器随后关闭
- 30 秒内修订仍为 r0
- 网络 trace 中
PATCH /api/v1/documents/.../content数量为 0 - 动作 monotonic:dblclick 611755–611807;fill 611860–611910;save 611917–611979
- 恰在 fill 开始时,后台
GET /api/v1/docgov/documents/doc_proj6140_intake/content于 611862.379 发起并完成 hydration
本轮不是选择器点错按钮(第七轮 save-selector),而是保存路径在客户端被静默短路。
根因分析
| 层 | 问题 | 后果 |
|---|---|---|
| AST 身份 | markdownToBlockAST / generateId() 明确 Math.random(),每次 parse 新建 blk_* |
弹窗持有打开时的块 ID,当前 docAST 已是新 ID |
| hydrate 丢账本 | fetchRevisionState 依赖被窄化为 Promise<{etag:string}>;buildControlledDocumentAST 之后没有 applyHeadBlockMap(rev.st.head_block_map) |
服务端已有稳定块身份,前端每次 hydration 仍随机换 ID |
| 保存 | handleSaveBlockEdit 按 updated.id 在 docAST.rootBlock.children 查找;idx === -1 时 setEditingBlock(null) 后 return |
关窗、不发 PATCH、无提示 |
| hydration 触发 | useEffect([session, sessionValidated, activeDocId, deliverables]) 在 lease/工作流同步改 deliverables 时重跑 |
fill 窗口打出新 GET |
| 迟到响应 | hydrate .then 无 generation / 编辑门闩 |
弹窗打开后或 PATCH 成功后仍可覆盖编辑基线(即使 ID 已对齐,也会回滚新正文) |
| 会话绑定 | 只按块 ID 保存;activeDocId 可被 canonical/session/任务流改写,不经 handleSelectDoc |
可能对另一份文档发 PATCH,或块 ID 碰巧对上 |
| 关窗续跑 | 编辑中拒绝/跳过的首次 hydrate,effect 依赖不再变化 | 关窗后可能永久停在 loading skeleton |
| 切文档 | handleSelectDoc 在编辑中静默 closeBlockEditor |
草稿被丢;modal 虽 fixed inset:0 zIndex 100 挡住 SlimTabBar,但 chat/drawer 等仍会调 handleSelectDoc |
deliverables 被 5s lease 同步、任务完成后 syncControlledDocsAfterTask、工作流列表合并(清 cache)以及 governance 事件改写。任一变化都会在编辑期间再拉正文并换块身份。
严重度
high
用户可见「已保存」(弹窗关闭),账本仍 r0。属于静默丢稿,不是提示后的失败。
关联整改
- Remediation: ../remediation/2026-09-13-document-revision-editor-hydration-race.md
- Short-term: ../ops/short-term/2026-09-13-document-revision-editor-hydration-race.md
- 分支:
fix/document-revision-editor-hydration-race
证据
- 失败证据目录(根代理保留、本轮不得改/移/删/提交):
.doc/test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui06-r-bump-noop-failure/ - Playwright 断言:
[data-testid="doc-rev-revision"]30s 内仍为r0
开放问题
- 本轮只做前端门闩与单测/构建验证;未跑真实 UI-06,不得标 E2E 通过或任务
done - 关窗后的下一次 deliverables 驱动 hydrate 仍会重建块 ID(编辑会话已结束;缺 map 时仍随机,门闩已消费)
- 首次 hydrate 在
editor_open被拒后会卡 skeleton:可达(骨架块可双击,或清 cache 后编辑中跳过 GET)。关窗时takeHydrationResume()递增hydrateResumeKey重跑 effect。 - modal 不能硬保证挡死所有
handleSelectDoc;编辑中切文档 fail-closed,不静默关窗。