From 82614b4ce3f4e2b4f76c17cb24df017c30f35456 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Tue, 25 Aug 2026 18:35:39 +0500 Subject: [PATCH] editor: instrument block ids, table of contents and height map --- .../src/extensions/block-id/block-id.ts | 66 +++++++++++-------- .../extensions/virtualization/height-map.ts | 4 ++ packages/editor/src/utils/toc.ts | 5 ++ 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/packages/editor/src/extensions/block-id/block-id.ts b/packages/editor/src/extensions/block-id/block-id.ts index f91b892a7..3637bf8a0 100644 --- a/packages/editor/src/extensions/block-id/block-id.ts +++ b/packages/editor/src/extensions/block-id/block-id.ts @@ -24,6 +24,7 @@ import { AttributeUpdate, BatchAttributeStep } from "../../utils/batch-attribute-step.js"; +import { profiler } from "../../utils/profiler.js"; const NESTED_BLOCK_ID_TYPES = ["callout"]; const BLOCK_ID_TYPES = [ @@ -78,40 +79,49 @@ export const BlockId = Extension.create({ const isDocChanged = transactions.some((tr) => tr.docChanged); if (!isDocChanged) return null; - const blockIds = new Set(); - const updates: AttributeUpdate[] = []; - const { tr } = newState; + return profiler.time("blockId.appendTransaction", () => { + const blockIds = new Set(); + const updates: AttributeUpdate[] = []; + const { tr } = newState; + let scanned = 0; - tr.doc.forEach(function addBlockId(n, offset) { - if (!n.isBlock || !BLOCK_ID_TYPES.includes(n.type.name)) return; + tr.doc.forEach(function addBlockId(n, offset) { + if (!n.isBlock || !BLOCK_ID_TYPES.includes(n.type.name)) return; - const currentId = n.attrs.blockId; - const shouldUpdateId = !currentId || blockIds.has(currentId); - const finalId = shouldUpdateId ? nanoid(8) : currentId; + scanned++; + const currentId = n.attrs.blockId; + const shouldUpdateId = !currentId || blockIds.has(currentId); + const finalId = shouldUpdateId ? nanoid(8) : currentId; - if (shouldUpdateId) { - updates.push({ - pos: offset, - attrName: "blockId", - value: finalId - }); + if (shouldUpdateId) { + updates.push({ + pos: offset, + attrName: "blockId", + value: finalId + }); + } + + blockIds.add(finalId); + + if (NESTED_BLOCK_ID_TYPES.includes(n.type.name)) + n.forEach((n, pos) => addBlockId(n, offset + pos + 1)); + }); + + profiler.count("blockId.blocksScanned", scanned); + profiler.gauge("blockId.blocksPerScan", scanned); + + if (updates.length > 0) { + profiler.count("blockId.idsAssigned", updates.length); + profiler.count("blockId.transactionsAppended"); + tr.step(new BatchAttributeStep(updates)); + tr.setMeta("ignoreEdit", true); + // Transaction.addStep always clears storedMarks + if (newState.storedMarks) tr.setStoredMarks(newState.storedMarks); + return tr; } - blockIds.add(finalId); - - if (NESTED_BLOCK_ID_TYPES.includes(n.type.name)) - n.forEach((n, pos) => addBlockId(n, offset + pos + 1)); + return null; }); - - if (updates.length > 0) { - tr.step(new BatchAttributeStep(updates)); - tr.setMeta("ignoreEdit", true); - // Transaction.addStep always clears storedMarks - if (newState.storedMarks) tr.setStoredMarks(newState.storedMarks); - return tr; - } - - return null; } }) ]; diff --git a/packages/editor/src/extensions/virtualization/height-map.ts b/packages/editor/src/extensions/virtualization/height-map.ts index d30bc3b1f..bb6f3d17b 100644 --- a/packages/editor/src/extensions/virtualization/height-map.ts +++ b/packages/editor/src/extensions/virtualization/height-map.ts @@ -18,6 +18,7 @@ along with this program. If not, see . */ import { Node as ProsemirrorNode } from "@tiptap/pm/model"; +import { profiler } from "../../utils/profiler.js"; const DEFAULT_ESTIMATES: Record = { paragraph: 24, @@ -49,8 +50,10 @@ export class HeightMap { heightFor(node: ProsemirrorNode): number { const blockId = node.attrs.blockId as string | undefined; if (blockId && this.measured.has(blockId)) { + profiler.count("virtualization.heightMap.hit"); return this.measured.get(blockId) as number; } + profiler.count("virtualization.heightMap.miss"); return this.estimate(node); } @@ -58,6 +61,7 @@ export class HeightMap { const blockId = node.attrs.blockId as string | undefined; if (!blockId || !Number.isFinite(height) || height <= 0) return; this.measured.set(blockId, Math.round(height)); + profiler.gauge("virtualization.heightMap.size", this.measured.size); } toJSON(): Record { diff --git a/packages/editor/src/utils/toc.ts b/packages/editor/src/utils/toc.ts index 09c94c59a..4df3cc712 100644 --- a/packages/editor/src/utils/toc.ts +++ b/packages/editor/src/utils/toc.ts @@ -18,6 +18,7 @@ along with this program. If not, see . */ import { Node as ProsemirrorNode } from "@tiptap/pm/model"; +import { profiler } from "./profiler.js"; export type TOCItem = { level: number; @@ -57,6 +58,7 @@ export function getTableOfContents( doc: ProsemirrorNode, content: HTMLElement ): TOCItem[] { + const end = profiler.start("toc.build"); const tableOfContents: TOCItem[] = []; let level = -1; let prevHeading = 0; @@ -91,6 +93,9 @@ export function getTableOfContents( }); }); + end(); + profiler.count("toc.builds"); + profiler.gauge("toc.headings", tableOfContents.length); return tableOfContents; }