feat(web): mark real document blocks without overlay

Add data-testid=document-block plus block id on every wrapper so
pure-mode editors stay addressable without changing visuals.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 17:24:54 +00:00
co-authored by Cursor
parent 61905e99c1
commit 9e0901ce25
3 changed files with 45 additions and 0 deletions
+2
View File
@@ -12,6 +12,7 @@ import { MathBlockView } from './blocks/MathBlockView';
import { ListBlockView } from './blocks/ListBlockView'; import { ListBlockView } from './blocks/ListBlockView';
import { BlockContextMenu } from './BlockContextMenu'; import { BlockContextMenu } from './BlockContextMenu';
import { canAddBlockComment } from '../../lib/commentPolicy'; import { canAddBlockComment } from '../../lib/commentPolicy';
import { documentBlockDomAttrs } from '../../lib/documentBlockDom';
import { Plus, ArrowUp, ArrowDown, Trash2, MessageSquare } from 'lucide-react'; import { Plus, ArrowUp, ArrowDown, Trash2, MessageSquare } from 'lucide-react';
export interface BlockRendererProps { export interface BlockRendererProps {
@@ -150,6 +151,7 @@ export const BlockRenderer: React.FC<BlockRendererProps> = ({
<div <div
key={block.id || idx} key={block.id || idx}
id={block.id ? `block-${block.id}` : undefined} id={block.id ? `block-${block.id}` : undefined}
{...documentBlockDomAttrs(block)}
data-overlay-kind={hunk?.kind} data-overlay-kind={hunk?.kind}
onMouseEnter={() => setHoveredIndex(idx)} onMouseEnter={() => setHoveredIndex(idx)}
onMouseLeave={() => setHoveredIndex(null)} 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,
};
}