merge: fix/document-revision-e2e-block-selector

This commit is contained in:
weichen
2026-09-13 17:25:41 +00:00
7 changed files with 147 additions and 4 deletions
@@ -0,0 +1,45 @@
---
title: "UI-06 用 overlay/p/h 选择器找不到真实文档块"
status: in_progress
created: 2026-09-13
updated: 2026-09-13
severity: high
---
# UI-06 用 overlay/p/h 选择器找不到真实文档块
## 现象
第六轮真实 UI(schema 30):
- UI-00~05b **PASS**
- UI-06 在 `editFirstPaneBlockAndSave` 里对 `[data-overlay-kind], p, h1, h2` **双击前超时**
- 失败页正文真实存在;工具条版本徽章可见
## 根因分析
| 层 | 问题 |
|---|---|
| DOM | `BlockRenderer` 块 wrapper 是 generic `div`;`HeadingBlockView` / `ParagraphBlockView` 也不是 `h1`/`p` |
| overlay | `data-overlay-kind` 仅在非 `pure` 且命中 hunk 时出现;默认 `pure` 模式该属性不存在 |
| 脚本 | `openFirstPaneEditorAndFill` 依赖 overlay 装饰或原生标题/段落标签,与产品 DOM 契约不符 |
这是 **UI 自动化可寻址缺口**,不是正文未加载。继续用 overlay 选择器会误点标题栏/骨架/报告,或在 pure 模式直接超时。
## 严重度
`high`
## 关联整改
- Short-term: [../ops/short-term/2026-09-13-document-revision-e2e-block-selector.md](../ops/short-term/2026-09-13-document-revision-e2e-block-selector.md)
- 分支: `fix/document-revision-e2e-block-selector`
## 证据
- Evidence root(已归档、本轮勿提交):`.doc/test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui06-block-selector-failure/`
- `ui-acceptance-results.json`:UI-00~05b PASS;UI-06 未写入 PASS
## 开放问题
- 占槽后真实 UI-06/13b 重跑;本任务保持 `in_progress`,不得标 `done`
+4 -2
View File
@@ -1,11 +1,11 @@
# 当前焦点
**更新**: 2026-09-13
**production_ready**: no(DOCREV:schema **30**;第五轮 UI-05b 缺指示语路由到 feasibility_reg;不操作容器)
**production_ready**: no(DOCREV:schema **30**;第六轮 UI-06 块选择器超时;不操作容器)
## 焦点
1. **DOCREV-RT**: [E2E UI-05b + 职责/ETag 产品修复](short-term/2026-09-13-document-revision-e2e-reject-routing.md)(`fix/document-revision-e2e-reject-routing`:权威职责 + 打开时基线 ETag + fail-closed/viewer 补强;不重跑 E2E / 不操作容器;仍 `in_progress`)
1. **DOCREV-RT**: [E2E 真实文档块稳定寻址](short-term/2026-09-13-document-revision-e2e-block-selector.md)(`fix/document-revision-e2e-block-selector`:UI-06/13b 不依赖 overlay;不重跑 E2E / 不操作容器;仍 `in_progress`)
2. **CHAT**: [刷新恢复整改已实施](short-term/2026-09-13-chat-history-refresh-remediation.md)(待合 dev + PI E2E 补录)
3. **CI**: `TEST_DATABASE_URL` + 真实 PI E2E 实录;INTENT 发布 blocker 见 conversation-intent short-term
@@ -22,6 +22,7 @@
| Cursor-Auto | document-revision-quality-context | fix/document-revision-quality-context | - | docgov | in_progress |
| Cursor-Auto | document-revision-e2e-pending-flow | fix/document-revision-e2e-pending-flow | - | pending-report-ui | in_progress |
| Cursor-Auto | document-revision-e2e-reject-routing | fix/document-revision-e2e-reject-routing | - | document-revision-runtime-ui | in_progress |
| Cursor-Auto | document-revision-e2e-block-selector | fix/document-revision-e2e-block-selector | - | document-revision-block-selector | in_progress |
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
@@ -61,3 +62,4 @@
| 待确认报告选择失败审计 | [../audit/2026-09-13-document-revision-pending-report-selection.md](../audit/2026-09-13-document-revision-pending-report-selection.md) |
| UI-05b 拒绝路由失败审计 | [../audit/2026-09-13-document-revision-e2e-reject-routing.md](../audit/2026-09-13-document-revision-e2e-reject-routing.md) |
| 职责伪造与 ETag 覆盖审计 | [../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../audit/2026-09-13-document-revision-duty-etag-overwrite.md) |
| UI-06 块选择器失败审计 | [../audit/2026-09-13-document-revision-block-selector.md](../audit/2026-09-13-document-revision-block-selector.md) |
@@ -0,0 +1,51 @@
---
title: "E2E:给真实文档块加稳定寻址并修正 UI-06/13b"
status: in_progress
created: 2026-09-13
updated: 2026-09-13
owner: Cursor-Auto
branch: fix/document-revision-e2e-block-selector
worktree: "-"
mutex: document-revision-block-selector
---
# E2E:给真实文档块加稳定寻址并修正 UI-06/13b
## 目标
第六轮 UI-06 首错:`pure` 模式下块 wrapper 无 `data-overlay-kind`、正文也不是 `p`/`h1`。为每个真实 `DocumentBlock` wrapper 增加与 overlay 无关的稳定 DOM 标识;E2E 只在 `active-document-pane` 内定位该标识。不改视觉/权限/双击行为。不得将本任务标 `done`。
## 范围
- `web/src/lib/documentBlockDom.ts` + 契约单测
- `web/src/components/doc/BlockRenderer.tsx`
- `web/e2e/document-revision-runtime.spec.ts`(`openFirstPaneEditorAndFill`;UI-06/13b 复用,UI-13a 同步)
- 文档:audit / CURRENT
## 非目标
- 不改编辑/权限/overlay 视觉
- 不降低已有断言;不伪造、不直接打 API
- 不触碰/提交 `.doc/test-reports/assets`;不操作容器;本轮不重跑 E2E
- 不改 FEATURE-MATRIX
## 步骤
1. [x] 认领 CURRENT(mutex `document-revision-block-selector`);前序 reject-routing 保持 in_progress
2. [x] 真实块 wrapper 增加 `data-testid=document-block` + block id
3. [x] E2E 仅在 active pane 内按该标识双击
4. [x] 契约单测:pure 模式也有块标识,且不依赖 `data-overlay-kind`
5. [ ] 占槽后真实 UI 重跑(本轮不做)
## 验收标准
- [x] 块标识与 overlay 无关;带 block id
- [x] `openFirstPaneEditorAndFill` 不使用 overlay/`p`/`h1`/`h2`
- [x] web tests + `tsc`/build + `git diff --check`
- [ ] 真实 UI-06/13b PASS 后才可将本任务标 `done`
## 相关链接
- Audit: [../../audit/2026-09-13-document-revision-block-selector.md](../../audit/2026-09-13-document-revision-block-selector.md)
- Evidence: [../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui06-block-selector-failure](../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui06-block-selector-failure)
- 前序: [2026-09-13-document-revision-e2e-reject-routing.md](./2026-09-13-document-revision-e2e-reject-routing.md)
+2 -2
View File
@@ -363,7 +363,7 @@ async function closeNamedDialog(page: Page, name: string, timeout = 10_000) {
async function openFirstPaneEditorAndFill(target: Page, text: string) {
const pane = target.getByTestId('active-document-pane');
const block = pane.locator('[data-overlay-kind], p, h1, h2').first();
const block = pane.getByTestId('document-block').first();
await block.dblclick({ timeout: 15_000 });
const editor = target.locator('textarea, [contenteditable="true"]').last();
await expect(editor).toBeVisible({ timeout: 10_000 });
@@ -1091,7 +1091,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
await expect(pageB.getByText('双击任意段落就地编辑')).toHaveCount(0);
const pane = pageB.getByTestId('active-document-pane');
await pane.locator('[data-overlay-kind], p, h1, h2').first().dblclick({ timeout: 15_000 });
await pane.getByTestId('document-block').first().dblclick({ timeout: 15_000 });
await expect(pageB.getByRole('button', { name: /保存修改|保存并关闭/ })).toHaveCount(0);
await shot(pageB, 'UI-13a-reviewer-readonly.png');
await ctxB.close();
+2
View File
@@ -12,6 +12,7 @@ import { MathBlockView } from './blocks/MathBlockView';
import { ListBlockView } from './blocks/ListBlockView';
import { BlockContextMenu } from './BlockContextMenu';
import { canAddBlockComment } from '../../lib/commentPolicy';
import { documentBlockDomAttrs } from '../../lib/documentBlockDom';
import { Plus, ArrowUp, ArrowDown, Trash2, MessageSquare } from 'lucide-react';
export interface BlockRendererProps {
@@ -150,6 +151,7 @@ export const BlockRenderer: React.FC<BlockRendererProps> = ({
<div
key={block.id || idx}
id={block.id ? `block-${block.id}` : undefined}
{...documentBlockDomAttrs(block)}
data-overlay-kind={hunk?.kind}
onMouseEnter={() => setHoveredIndex(idx)}
onMouseLeave={() => setHoveredIndex(null)}
+28
View File
@@ -0,0 +1,28 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
DOCUMENT_BLOCK_TESTID,
documentBlockDomAttrs,
} from './documentBlockDom';
describe('documentBlockDomAttrs', () => {
it('marks a real DocumentBlock in pure mode without overlay kind', () => {
const attrs = documentBlockDomAttrs({ id: 'blk_intake_p1' });
assert.equal(attrs['data-testid'], DOCUMENT_BLOCK_TESTID);
assert.equal(attrs['data-testid'], 'document-block');
assert.equal(attrs['data-document-block'], 'blk_intake_p1');
assert.equal(attrs['data-block-id'], 'blk_intake_p1');
assert.equal('data-overlay-kind' in attrs, false);
});
it('keeps the same stable attrs when overlay is later attached separately', () => {
const pure = documentBlockDomAttrs({ id: 'blk_h1' });
const withOverlay = {
...documentBlockDomAttrs({ id: 'blk_h1' }),
'data-overlay-kind': 'modify',
};
assert.equal(pure['data-document-block'], withOverlay['data-document-block']);
assert.equal(pure['data-testid'], withOverlay['data-testid']);
assert.notEqual(Object.prototype.hasOwnProperty.call(pure, 'data-overlay-kind'), true);
});
});
+15
View File
@@ -0,0 +1,15 @@
/** 真实 DocumentBlock wrapper 的稳定寻址标识,与 overlay 是否存在无关。 */
export const DOCUMENT_BLOCK_TESTID = 'document-block';
export function documentBlockDomAttrs(block: { id?: string }): {
'data-testid': typeof DOCUMENT_BLOCK_TESTID;
'data-document-block': string;
'data-block-id': string;
} {
const id = block.id || '';
return {
'data-testid': DOCUMENT_BLOCK_TESTID,
'data-document-block': id,
'data-block-id': id,
};
}