From 2a90727d5fcb8b3e7de0a4a2c6b7a8e5230eb14b Mon Sep 17 00:00:00 2001 From: Nick Bernal Date: Wed, 18 Mar 2026 15:38:38 -0700 Subject: [PATCH 1/2] fix(document-api): return fresh table ref in mutation responses --- apps/docs/document-api/reference/index.mdx | 2 +- apps/docs/document-api/reference/info.mdx | 11 +- .../src/types/table-operations.types.ts | 7 + .../helpers/table-target-resolver.ts | 37 +++ .../tables-adapter.ref-handoff.test.ts | 210 ++++++++++++++++++ .../document-api-adapters/tables-adapter.ts | 88 +++++--- 6 files changed, 320 insertions(+), 35 deletions(-) create mode 100644 packages/super-editor/src/document-api-adapters/tables-adapter.ref-handoff.test.ts diff --git a/apps/docs/document-api/reference/index.mdx b/apps/docs/document-api/reference/index.mdx index 9fe319813b..3b1c1a7a2b 100644 --- a/apps/docs/document-api/reference/index.mdx +++ b/apps/docs/document-api/reference/index.mdx @@ -68,7 +68,7 @@ The tables below are grouped by namespace. | getMarkdown | editor.doc.getMarkdown(...) | Extract the document content as a Markdown string. | | getHtml | editor.doc.getHtml(...) | Extract the document content as an HTML string. | | markdownToFragment | editor.doc.markdownToFragment(...) | Convert a Markdown string into an SDM/1 structural fragment. | -| info | editor.doc.info(...) | Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, and list counts, plus outline and capabilities. | +| info | editor.doc.info(...) | Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, list, and page counts, plus outline and capabilities. | | clearContent | editor.doc.clearContent(...) | Clear all document body content, leaving a single empty paragraph. | | insert | editor.doc.insert(...) | Insert content into the document. Two input shapes: legacy string-based (value + type) inserts inline content at a text position within an existing block; structural SDFragment (content) inserts one or more blocks as siblings relative to a BlockNodeAddress target. When target is omitted, content appends at the end of the document. Legacy mode supports text (default), markdown, and html content types via the `type` field. Structural mode uses `placement` (before/after/insideStart/insideEnd) to position relative to the target block. | | replace | editor.doc.replace(...) | Replace content at a contiguous document selection. Text path accepts a SelectionTarget or ref plus replacement text. Structural path accepts a BlockNodeAddress (replaces whole block), SelectionTarget (expands to full covered block boundaries), or ref plus SDFragment content. | diff --git a/apps/docs/document-api/reference/info.mdx b/apps/docs/document-api/reference/info.mdx index 8b4c4d0459..28db0e02ca 100644 --- a/apps/docs/document-api/reference/info.mdx +++ b/apps/docs/document-api/reference/info.mdx @@ -1,7 +1,7 @@ --- title: info sidebarTitle: info -description: Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, and list counts, plus outline and capabilities. +description: Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, list, and page counts, plus outline and capabilities. --- {/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */} @@ -10,7 +10,7 @@ description: Return document summary info including word, character, paragraph, ## Summary -Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, and list counts, plus outline and capabilities. +Return document summary info including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, list, and page counts, plus outline and capabilities. - Operation ID: `info` - API member path: `editor.doc.info(...)` @@ -22,7 +22,7 @@ Return document summary info including word, character, paragraph, heading, tabl ## Expected result -Returns a DocumentInfo object with counts (words, characters, paragraphs, headings, tables, images, comments, trackedChanges, sdtFields, lists), document outline, capability flags, and revision. +Returns a DocumentInfo object with counts (words, characters, paragraphs, headings, tables, images, comments, trackedChanges, sdtFields, lists, and optionally pages when pagination is active), document outline, capability flags, and revision. ## Input fields @@ -49,6 +49,7 @@ _No fields._ | `counts.headings` | integer | yes | | | `counts.images` | integer | yes | | | `counts.lists` | integer | yes | | +| `counts.pages` | integer | no | | | `counts.paragraphs` | integer | yes | | | `counts.sdtFields` | integer | yes | | | `counts.tables` | integer | yes | | @@ -73,6 +74,7 @@ _No fields._ "headings": 3, "images": 2, "lists": 1, + "pages": 1, "paragraphs": 12, "sdtFields": 1, "tables": 1, @@ -157,6 +159,9 @@ _No fields._ "lists": { "type": "integer" }, + "pages": { + "type": "integer" + }, "paragraphs": { "type": "integer" }, diff --git a/packages/document-api/src/types/table-operations.types.ts b/packages/document-api/src/types/table-operations.types.ts index 1f88fb6174..e3853f1312 100644 --- a/packages/document-api/src/types/table-operations.types.ts +++ b/packages/document-api/src/types/table-operations.types.ts @@ -81,6 +81,13 @@ export type TableCreateLocation = /** * Generic success result for table mutation operations. + * + * For non-destructive table-targeted mutations, `table` is the canonical + * post-mutation table reference. Use `table.nodeId` to target the same table + * in subsequent operations — no intermediate `find()` needed. + * + * `table` is `undefined` for destructive operations (delete, convertToText) + * and in rare cases where post-mutation re-resolution fails. */ export interface TableMutationSuccess { success: true; diff --git a/packages/super-editor/src/document-api-adapters/helpers/table-target-resolver.ts b/packages/super-editor/src/document-api-adapters/helpers/table-target-resolver.ts index 08c16e5cdd..9125dc8a56 100644 --- a/packages/super-editor/src/document-api-adapters/helpers/table-target-resolver.ts +++ b/packages/super-editor/src/document-api-adapters/helpers/table-target-resolver.ts @@ -450,6 +450,43 @@ export function getTableColumnCount(tableNode: ProseMirrorNode): number { return count; } +// --------------------------------------------------------------------------- +// Post-mutation re-resolution +// --------------------------------------------------------------------------- + +/** + * Re-resolves a table's address after a mutation has been dispatched. + * + * Uses transaction position mapping as the primary strategy, with + * nodeId-based fallback for DOCX tables with stable primary IDs. + * + * Returns `undefined` if the table cannot be re-resolved — callers + * should NOT fall back to the pre-mutation address. + */ +export function resolvePostMutationTableAddress( + editor: Editor, + preMutationPos: number, + preMutationNodeId: string, + tr: { mapping: { map(pos: number, assoc?: number): number } }, +): BlockNodeAddress | undefined { + const index = getBlockIndex(editor); + + // Strategy 1: Map pre-mutation position through the transaction. + const mappedPos = tr.mapping.map(preMutationPos); + const candidate = index.candidates.find((c) => c.pos === mappedPos && c.nodeType === 'table'); + if (candidate) return toBlockAddress(candidate); + + // Strategy 2: Look up by pre-mutation nodeId (works for DOCX tables with stable paraId). + try { + const found = findBlockByNodeIdOnly(index, preMutationNodeId); + if (found.nodeType === 'table') return toBlockAddress(found); + } catch { + // Not found or ambiguous — fall through. + } + + return undefined; +} + // --------------------------------------------------------------------------- // Failure helper // --------------------------------------------------------------------------- diff --git a/packages/super-editor/src/document-api-adapters/tables-adapter.ref-handoff.test.ts b/packages/super-editor/src/document-api-adapters/tables-adapter.ref-handoff.test.ts new file mode 100644 index 0000000000..ee4453c494 --- /dev/null +++ b/packages/super-editor/src/document-api-adapters/tables-adapter.ref-handoff.test.ts @@ -0,0 +1,210 @@ +/* @vitest-environment jsdom */ + +import { afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { initTestEditor, loadTestDataForEditorTests } from '@tests/helpers/helpers.js'; +import type { Editor } from '../core/Editor.js'; +import { + createTableAdapter, + tablesMoveAdapter, + tablesSetBorderAdapter, + tablesInsertColumnAdapter, + tablesMergeCellsAdapter, + tablesSetCellSpacingAdapter, + tablesSetShadingAdapter, + tablesGetCellsAdapter, +} from './tables-adapter.js'; + +type LoadedDocData = Awaited>; + +const DIRECT = { changeMode: 'direct' } as const; + +function requireTableNodeId(result: { success: boolean; table?: { nodeId?: string } }, label: string): string { + if (!result.success) { + throw new Error(`${label} failed: expected success.`); + } + const nodeId = (result as { table?: { nodeId?: string } }).table?.nodeId; + if (!nodeId) { + throw new Error(`${label}: expected result.table.nodeId to be defined.`); + } + return nodeId; +} + +describe('SD-2126: post-mutation table ref handoff', () => { + let docData: LoadedDocData; + let editor: Editor | undefined; + + beforeAll(async () => { + docData = await loadTestDataForEditorTests('blank-doc.docx'); + }); + + afterEach(() => { + editor?.destroy(); + editor = undefined; + }); + + function createEditor(): Editor { + const result = initTestEditor({ + content: docData.docx, + media: docData.media, + mediaFiles: docData.mediaFiles, + fonts: docData.fonts, + useImmediateSetTimeout: false, + }); + editor = result.editor; + return editor; + } + + it('chains create → setBorder → insertColumn → mergeCells → setCellSpacing without find()', () => { + const ed = createEditor(); + + // Step 1: create.table + const createResult = createTableAdapter(ed, { rows: 3, columns: 3, at: { kind: 'documentEnd' } }, DIRECT); + const id1 = requireTableNodeId(createResult, 'create.table'); + + // Step 2: setBorder using create's ref + const borderResult = tablesSetBorderAdapter( + ed, + { nodeId: id1, edge: 'top', lineStyle: 'single', lineWeightPt: 1, color: '000000' }, + DIRECT, + ); + const id2 = requireTableNodeId(borderResult, 'tables.setBorder'); + + // Step 3: insertColumn using setBorder's ref + const colResult = tablesInsertColumnAdapter(ed, { tableNodeId: id2, columnIndex: 0, position: 'right' }, DIRECT); + const id3 = requireTableNodeId(colResult, 'tables.insertColumn'); + + // Step 4: mergeCells using insertColumn's ref + const mergeResult = tablesMergeCellsAdapter( + ed, + { tableNodeId: id3, start: { rowIndex: 0, columnIndex: 0 }, end: { rowIndex: 0, columnIndex: 1 } }, + DIRECT, + ); + const id4 = requireTableNodeId(mergeResult, 'tables.mergeCells'); + + // Step 5: setCellSpacing using mergeCells' ref + const spacingResult = tablesSetCellSpacingAdapter(ed, { nodeId: id4, spacingPt: 2 }, DIRECT); + const id5 = requireTableNodeId(spacingResult, 'tables.setCellSpacing'); + + // Final ref should still be resolvable + expect(id5).toBeTruthy(); + }); + + it('returns a resolvable ref for a runtime-created table with volatile sdBlockId', () => { + const ed = createEditor(); + + // Runtime tables get UUID sdBlockId (volatile). The returned nodeId should + // be the canonical position-based fallback, not the raw UUID. + const createResult = createTableAdapter(ed, { rows: 2, columns: 2, at: { kind: 'documentEnd' } }, DIRECT); + const id1 = requireTableNodeId(createResult, 'create.table'); + + // Mutate — this changes positions/structure. + const colResult = tablesInsertColumnAdapter(ed, { tableNodeId: id1, columnIndex: 0, position: 'right' }, DIRECT); + const id2 = requireTableNodeId(colResult, 'tables.insertColumn'); + + // The returned ref should work for a follow-up operation. + const borderResult = tablesSetBorderAdapter( + ed, + { nodeId: id2, edge: 'bottom', lineStyle: 'single', lineWeightPt: 0.5, color: 'FF0000' }, + DIRECT, + ); + expect(borderResult.success).toBe(true); + expect((borderResult as { table?: { nodeId?: string } }).table?.nodeId).toBeTruthy(); + }); + + it('cell-targeted setBorder preserves current cell address behavior', () => { + const ed = createEditor(); + + // Create a table and find a cell to target. + const createResult = createTableAdapter(ed, { rows: 2, columns: 2, at: { kind: 'documentEnd' } }, DIRECT); + expect(createResult.success).toBe(true); + + // Find a cell nodeId via tablesGetCellsAdapter. + const tableNodeId = requireTableNodeId(createResult, 'create.table'); + const cellsResult = tablesGetCellsAdapter(ed, { nodeId: tableNodeId }); + expect(cellsResult.cells.length).toBeGreaterThan(0); + const cellNodeId = cellsResult.cells[0]!.nodeId; + expect(cellNodeId).toBeTruthy(); + + // Target the cell with setBorder. + const borderResult = tablesSetBorderAdapter( + ed, + { nodeId: cellNodeId!, edge: 'top', lineStyle: 'single', lineWeightPt: 1, color: '000000' }, + DIRECT, + ); + + expect(borderResult.success).toBe(true); + // Cell-targeted ops return the cell address (not parent table) — unchanged behavior. + const table = (borderResult as { table?: { nodeType?: string } }).table; + expect(table?.nodeType).toBe('tableCell'); + }); + + it('tables.move returns a chainable ref after relocating the table', () => { + const ed = createEditor(); + + // Create two tables so there is a meaningful destination. + createTableAdapter(ed, { rows: 2, columns: 2, at: { kind: 'documentEnd' } }, DIRECT); + const createResult2 = createTableAdapter(ed, { rows: 2, columns: 2, at: { kind: 'documentEnd' } }, DIRECT); + const tableId = requireTableNodeId(createResult2, 'create.table (second)'); + + // Move the second table to the document start. + const moveResult = tablesMoveAdapter(ed, { nodeId: tableId, destination: { kind: 'documentStart' } }, DIRECT); + const movedId = requireTableNodeId(moveResult, 'tables.move'); + + // The returned ref should be usable for a follow-up mutation. + const borderResult = tablesSetBorderAdapter( + ed, + { nodeId: movedId, edge: 'top', lineStyle: 'single', lineWeightPt: 1, color: '000000' }, + DIRECT, + ); + expect(borderResult.success).toBe(true); + expect((borderResult as { table?: { nodeId?: string } }).table?.nodeId).toBeTruthy(); + }); + + it('every in-scope mutation returns a defined table ref (invariant check)', () => { + const ed = createEditor(); + + const createResult = createTableAdapter(ed, { rows: 3, columns: 3, at: { kind: 'documentEnd' } }, DIRECT); + const id = requireTableNodeId(createResult, 'create.table'); + + // setBorder (table-targeted) + const r1 = tablesSetBorderAdapter( + ed, + { nodeId: id, edge: 'top', lineStyle: 'single', lineWeightPt: 1, color: '000000' }, + DIRECT, + ); + expect(r1.success).toBe(true); + expect((r1 as { table?: unknown }).table).toBeDefined(); + + const id2 = requireTableNodeId(r1, 'setBorder'); + + // insertColumn + const r2 = tablesInsertColumnAdapter(ed, { tableNodeId: id2, columnIndex: 0, position: 'right' }, DIRECT); + expect(r2.success).toBe(true); + expect((r2 as { table?: unknown }).table).toBeDefined(); + + const id3 = requireTableNodeId(r2, 'insertColumn'); + + // mergeCells + const r3 = tablesMergeCellsAdapter( + ed, + { tableNodeId: id3, start: { rowIndex: 0, columnIndex: 0 }, end: { rowIndex: 0, columnIndex: 1 } }, + DIRECT, + ); + expect(r3.success).toBe(true); + expect((r3 as { table?: unknown }).table).toBeDefined(); + + const id4 = requireTableNodeId(r3, 'mergeCells'); + + // setCellSpacing + const r4 = tablesSetCellSpacingAdapter(ed, { nodeId: id4, spacingPt: 2 }, DIRECT); + expect(r4.success).toBe(true); + expect((r4 as { table?: unknown }).table).toBeDefined(); + + const id5 = requireTableNodeId(r4, 'setCellSpacing'); + + // setShading (table-targeted) + const r5 = tablesSetShadingAdapter(ed, { nodeId: id5, color: 'CCCCCC' }, DIRECT); + expect(r5.success).toBe(true); + expect((r5 as { table?: unknown }).table).toBeDefined(); + }); +}); diff --git a/packages/super-editor/src/document-api-adapters/tables-adapter.ts b/packages/super-editor/src/document-api-adapters/tables-adapter.ts index 3c59a0eee1..c9961cd600 100644 --- a/packages/super-editor/src/document-api-adapters/tables-adapter.ts +++ b/packages/super-editor/src/document-api-adapters/tables-adapter.ts @@ -65,6 +65,7 @@ import { resolveColumnLocator, resolveCellLocator, resolveMergeRangeLocator, + resolvePostMutationTableAddress, getTableColumnCount, toTableFailure, } from './helpers/table-target-resolver.js'; @@ -919,7 +920,7 @@ export function tablesClearContentsAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table content clearing could not be applied.'); } @@ -935,7 +936,7 @@ export function tablesMoveAdapter( ): TableMutationResult { rejectTrackedMode('tables.move', options); - const { candidate } = resolveTableLocator(editor, input, 'tables.move'); + const { candidate, address } = resolveTableLocator(editor, input, 'tables.move'); if (options?.dryRun) { return buildTableSuccess(toBlockAddress(candidate)); @@ -963,9 +964,22 @@ export function tablesMoveAdapter( editor.dispatch(tr); clearIndexCache(editor); - // Resolve the table at its new position to return its address. - // The nodeId is preserved because we moved the same node. - return buildTableSuccess(); + // For move, position mapping fails (the node was deleted and re-inserted + // at a new location). Try nodeId-based resolution first (works for DOCX + // tables with stable paraId), then fall back to sdBlockId lookup for + // runtime tables whose position-based nodeId changed. + let freshAddress = resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr); + if (!freshAddress) { + const sdBlockId = (tableSlice.attrs as Record).sdBlockId; + if (typeof sdBlockId === 'string') { + const index = getBlockIndex(editor); + const found = index.candidates.find( + (c) => c.nodeType === 'table' && (c.node.attrs as Record).sdBlockId === sdBlockId, + ); + if (found) freshAddress = toBlockAddress(found); + } + } + return buildTableSuccess(freshAddress); } catch { return toTableFailure('INVALID_TARGET', 'Table move could not be applied.'); } @@ -1028,7 +1042,7 @@ export function tablesSetLayoutAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table layout update could not be applied.'); } @@ -1071,7 +1085,7 @@ export function tablesSetAltTextAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table alt text update could not be applied.'); } @@ -1131,7 +1145,7 @@ export function tablesInsertRowAdapter( else applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Row insertion could not be applied.'); } @@ -1204,7 +1218,7 @@ export function tablesDeleteRowAdapter( else applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Row deletion could not be applied.'); } @@ -1249,7 +1263,7 @@ export function tablesSetRowHeightAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Row height update could not be applied.'); } @@ -1317,7 +1331,7 @@ export function tablesDistributeRowsAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Row distribution could not be applied.'); } @@ -1366,7 +1380,7 @@ export function tablesSetRowOptionsAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Row options update could not be applied.'); } @@ -1423,7 +1437,7 @@ export function tablesInsertColumnAdapter( else applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Column insertion could not be applied.'); } @@ -1475,7 +1489,7 @@ export function tablesDeleteColumnAdapter( else applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Column deletion could not be applied.'); } @@ -1545,7 +1559,7 @@ export function tablesSetColumnWidthAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Column width update could not be applied.'); } @@ -1648,7 +1662,7 @@ export function tablesDistributeColumnsAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Column distribution could not be applied.'); } @@ -2085,7 +2099,7 @@ export function tablesInsertCellAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell insertion could not be applied.'); } @@ -2203,7 +2217,7 @@ export function tablesDeleteCellAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell deletion could not be applied.'); } @@ -2298,7 +2312,7 @@ export function tablesMergeCellsAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell merge could not be applied.'); } @@ -2354,7 +2368,7 @@ export function tablesUnmergeCellsAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell unmerge could not be applied.'); } @@ -2509,7 +2523,7 @@ export function tablesSplitCellAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell split could not be applied.'); } @@ -2571,7 +2585,7 @@ export function tablesSetCellPropertiesAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell properties update could not be applied.'); } @@ -2673,7 +2687,7 @@ export function tablesSortAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table sort could not be applied.'); } @@ -2713,7 +2727,7 @@ export function tablesSetStyleAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table style assignment could not be applied.'); } @@ -2749,7 +2763,7 @@ export function tablesClearStyleAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table style removal could not be applied.'); } @@ -2808,7 +2822,7 @@ export function tablesSetStyleOptionAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table style option could not be applied.'); } @@ -2903,6 +2917,9 @@ export function tablesSetBorderAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); + if (resolved.scope === 'table') { + return buildTableSuccess(resolvePostMutationTableAddress(editor, resolved.pos, resolved.address.nodeId, tr)); + } return buildTableSuccess(resolved.address); } catch { return toTableFailure('INVALID_TARGET', 'Border update could not be applied.'); @@ -2956,6 +2973,9 @@ export function tablesClearBorderAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); + if (resolved.scope === 'table') { + return buildTableSuccess(resolvePostMutationTableAddress(editor, resolved.pos, resolved.address.nodeId, tr)); + } return buildTableSuccess(resolved.address); } catch { return toTableFailure('INVALID_TARGET', 'Border clear could not be applied.'); @@ -3015,7 +3035,7 @@ export function tablesApplyBorderPresetAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Border preset could not be applied.'); } @@ -3085,6 +3105,9 @@ export function tablesSetShadingAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); + if (resolved.scope === 'table') { + return buildTableSuccess(resolvePostMutationTableAddress(editor, resolved.pos, resolved.address.nodeId, tr)); + } return buildTableSuccess(resolved.address); } catch { return toTableFailure('INVALID_TARGET', 'Shading update could not be applied.'); @@ -3153,6 +3176,9 @@ export function tablesClearShadingAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); + if (resolved.scope === 'table') { + return buildTableSuccess(resolvePostMutationTableAddress(editor, resolved.pos, resolved.address.nodeId, tr)); + } return buildTableSuccess(resolved.address); } catch { return toTableFailure('INVALID_TARGET', 'Shading clear could not be applied.'); @@ -3200,7 +3226,7 @@ export function tablesSetTablePaddingAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Table padding could not be applied.'); } @@ -3251,7 +3277,7 @@ export function tablesSetCellPaddingAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(table.address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, table.candidate.pos, table.address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell padding could not be applied.'); } @@ -3291,7 +3317,7 @@ export function tablesSetCellSpacingAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell spacing could not be applied.'); } @@ -3329,7 +3355,7 @@ export function tablesClearCellSpacingAdapter( applyDirectMutationMeta(tr); editor.dispatch(tr); clearIndexCache(editor); - return buildTableSuccess(address); + return buildTableSuccess(resolvePostMutationTableAddress(editor, candidate.pos, address.nodeId, tr)); } catch { return toTableFailure('INVALID_TARGET', 'Cell spacing removal could not be applied.'); } From edd47ef3ae30f34fb46cca5e31f8f1c3a4c718b6 Mon Sep 17 00:00:00 2001 From: Nick Bernal Date: Wed, 18 Mar 2026 17:11:10 -0700 Subject: [PATCH 2/2] chore: update docs --- apps/docs/document-api/common-workflows.mdx | 42 ++++++- apps/docs/document-api/overview.mdx | 6 +- .../document-api/reference/create/table.mdx | 6 +- .../reference/tables/apply-border-preset.mdx | 9 +- .../reference/tables/clear-border.mdx | 9 +- .../reference/tables/clear-cell-spacing.mdx | 9 +- .../reference/tables/clear-contents.mdx | 9 +- .../reference/tables/clear-shading.mdx | 9 +- .../reference/tables/clear-style.mdx | 9 +- .../reference/tables/convert-from-text.mdx | 9 +- .../reference/tables/convert-to-text.mdx | 9 +- .../reference/tables/delete-cell.mdx | 9 +- .../reference/tables/delete-column.mdx | 9 +- .../reference/tables/delete-row.mdx | 14 +-- .../document-api/reference/tables/delete.mdx | 9 +- .../reference/tables/distribute-columns.mdx | 9 +- .../reference/tables/distribute-rows.mdx | 9 +- .../reference/tables/get-cells.mdx | 3 +- .../reference/tables/get-properties.mdx | 3 +- .../document-api/reference/tables/get.mdx | 3 +- .../document-api/reference/tables/index.mdx | 4 + .../reference/tables/insert-cell.mdx | 9 +- .../reference/tables/insert-column.mdx | 9 +- .../reference/tables/insert-row.mdx | 14 +-- .../reference/tables/merge-cells.mdx | 9 +- .../document-api/reference/tables/move.mdx | 9 +- .../reference/tables/set-alt-text.mdx | 9 +- .../reference/tables/set-border.mdx | 9 +- .../reference/tables/set-cell-padding.mdx | 9 +- .../reference/tables/set-cell-properties.mdx | 9 +- .../reference/tables/set-cell-spacing.mdx | 9 +- .../reference/tables/set-column-width.mdx | 9 +- .../reference/tables/set-layout.mdx | 9 +- .../reference/tables/set-row-height.mdx | 14 +-- .../reference/tables/set-row-options.mdx | 14 +-- .../reference/tables/set-shading.mdx | 9 +- .../reference/tables/set-style-option.mdx | 9 +- .../reference/tables/set-style.mdx | 9 +- .../reference/tables/set-table-padding.mdx | 9 +- .../document-api/reference/tables/sort.mdx | 9 +- .../reference/tables/split-cell.mdx | 9 +- .../document-api/reference/tables/split.mdx | 9 +- .../reference/tables/unmerge-cells.mdx | 9 +- .../scripts/lib/reference-docs-artifacts.ts | 115 +++++++++++++++++- 44 files changed, 383 insertions(+), 143 deletions(-) diff --git a/apps/docs/document-api/common-workflows.mdx b/apps/docs/document-api/common-workflows.mdx index aae4c77972..9784381c73 100644 --- a/apps/docs/document-api/common-workflows.mdx +++ b/apps/docs/document-api/common-workflows.mdx @@ -139,6 +139,46 @@ Use `query.match` (not `find`) for this workflow. `query.match` returns `BlockNo For direct single-operation calls, prefer `item.target`. For plans or multi-step edits, prefer `item.handle.ref` so every step reuses the same resolved match. +## Chain table mutations with returned refs + +For non-destructive table-targeted mutations, reuse `result.table.nodeId` from the previous success result. You do not need an intermediate `find()` between calls. + +```ts +const created = editor.doc.create.table({ + rows: 2, + columns: 2, +}); + +if (!created.success || !created.table) return; + +const bordered = editor.doc.tables.setBorder({ + nodeId: created.table.nodeId, + edge: 'top', + lineStyle: 'single', + lineWeightPt: 1, + color: '000000', +}); + +if (!bordered.success || !bordered.table) return; + +const inserted = editor.doc.tables.insertColumn({ + tableNodeId: bordered.table.nodeId, + columnIndex: 0, + position: 'right', +}); + +if (!inserted.success || !inserted.table) return; + +editor.doc.tables.setCellSpacing({ + nodeId: inserted.table.nodeId, + spacingPt: 2, +}); +``` + + +This handoff contract applies to table-targeted calls. Cell-targeted `tables.setBorder`, `tables.clearBorder`, `tables.setShading`, and `tables.clearShading` still return the targeted `tableCell` address today. + + ## Build a selection explicitly with ranges.resolve Use `ranges.resolve` when you already know the anchor points and want a transparent `SelectionTarget` plus a reusable mutation-ready ref: @@ -240,7 +280,7 @@ editor2.destroy(); ``` -`nodeId` stability depends on the ID source. For DOCX-imported content, `nodeId` comes from `paraId` when available and is best-effort stable across loads. For nodes created at runtime, it falls back to `sdBlockId`, which is volatile. +`nodeId` stability depends on the ID source. For DOCX-imported content, `nodeId` comes from `paraId` when available and is best-effort stable across loads. Runtime-created content is still not guaranteed stable across loads; many nodes use session-scoped editor identity, while some structures such as tables or table cells may expose deterministic fallback IDs instead of raw `sdBlockId`. diff --git a/apps/docs/document-api/overview.mdx b/apps/docs/document-api/overview.mdx index f4e68b91b2..5f2d388496 100644 --- a/apps/docs/document-api/overview.mdx +++ b/apps/docs/document-api/overview.mdx @@ -69,15 +69,15 @@ Use `item.handle.ref` when you want multiple operations or a mutation plan to re For DOCX documents, `nodeId` is derived from the file's native `w14:paraId` attribute. In practice, this is usually stable when you reopen the same unchanged DOCX across separate editor sessions, machines, or headless CLI pipelines. -For nodes created at runtime (not imported from DOCX), `nodeId` falls back to `sdBlockId`, a UUID generated when the editor opens. This fallback is volatile and changes on every load. +For nodes created at runtime (not imported from DOCX), `nodeId` is still best-effort only. Many runtime nodes use session-scoped editor identity, and some structures such as tables or table cells may expose deterministic fallback IDs instead of a raw UUID-like `sdBlockId`. Either way, cross-session stability is not guaranteed. | ID source | Stable across loads? | When used | |-----------|---------------------|-----------| | `paraId` (from DOCX) | Best effort (usually stable for unchanged DOCX blocks) | Paragraphs and table rows imported from DOCX | -| `sdBlockId` (runtime) | No (session-scoped) | Nodes created programmatically before first export | +| Runtime-derived ID | No guarantee (often session-scoped; some table addresses use deterministic fallbacks) | Nodes created programmatically before first export | -If you need to reference blocks across separate editor sessions, use `editor.doc.query.match()` (or persist `nodeId` and reconstruct a `NodeAddress`) — don't read `node.attrs.sdBlockId` directly. The Document API resolves `paraId` first for DOCX-imported content. +If you need to reference blocks across separate editor sessions, use `editor.doc.query.match()` (or persist `nodeId` and reconstruct a `NodeAddress`) — don't read `node.attrs.sdBlockId` directly. The Document API resolves `paraId` first for DOCX-imported content and may derive safer public IDs for some runtime structures such as tables. diff --git a/apps/docs/document-api/reference/create/table.mdx b/apps/docs/document-api/reference/create/table.mdx index 91f3833b55..5335e4d36c 100644 --- a/apps/docs/document-api/reference/create/table.mdx +++ b/apps/docs/document-api/reference/create/table.mdx @@ -67,6 +67,10 @@ Returns a CreateTableResult with the new table block ID and address. | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +On success, `result.table` is the created table address. Reuse `result.table.nodeId` for follow-up table operations in the same session. + + ### Example response ```json @@ -75,7 +79,7 @@ Returns a CreateTableResult with the new table block ID and address. "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/apply-border-preset.mdx b/apps/docs/document-api/reference/tables/apply-border-preset.mdx index fa7093598c..0328d6fb74 100644 --- a/apps/docs/document-api/reference/tables/apply-border-preset.mdx +++ b/apps/docs/document-api/reference/tables/apply-border-preset.mdx @@ -38,12 +38,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "preset": "box", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/clear-border.mdx b/apps/docs/document-api/reference/tables/clear-border.mdx index a8d6afb6ba..d674bfd508 100644 --- a/apps/docs/document-api/reference/tables/clear-border.mdx +++ b/apps/docs/document-api/reference/tables/clear-border.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "edge": "top", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/clear-cell-spacing.mdx b/apps/docs/document-api/reference/tables/clear-cell-spacing.mdx index 0179ad49c1..8da209650c 100644 --- a/apps/docs/document-api/reference/tables/clear-cell-spacing.mdx +++ b/apps/docs/document-api/reference/tables/clear-cell-spacing.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/clear-contents.mdx b/apps/docs/document-api/reference/tables/clear-contents.mdx index 919cc3343c..a64edc7b14 100644 --- a/apps/docs/document-api/reference/tables/clear-contents.mdx +++ b/apps/docs/document-api/reference/tables/clear-contents.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/clear-shading.mdx b/apps/docs/document-api/reference/tables/clear-shading.mdx index 90aac8e32f..2e62b9e1e8 100644 --- a/apps/docs/document-api/reference/tables/clear-shading.mdx +++ b/apps/docs/document-api/reference/tables/clear-shading.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/clear-style.mdx b/apps/docs/document-api/reference/tables/clear-style.mdx index a1e923643d..8c4f822a84 100644 --- a/apps/docs/document-api/reference/tables/clear-style.mdx +++ b/apps/docs/document-api/reference/tables/clear-style.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/convert-from-text.mdx b/apps/docs/document-api/reference/tables/convert-from-text.mdx index 9fd6756a1f..4ed8044cea 100644 --- a/apps/docs/document-api/reference/tables/convert-from-text.mdx +++ b/apps/docs/document-api/reference/tables/convert-from-text.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "delimiter": "tab", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/convert-to-text.mdx b/apps/docs/document-api/reference/tables/convert-to-text.mdx index e658aa58ea..97a77b3057 100644 --- a/apps/docs/document-api/reference/tables/convert-to-text.mdx +++ b/apps/docs/document-api/reference/tables/convert-to-text.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "delimiter": "tab", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/delete-cell.mdx b/apps/docs/document-api/reference/tables/delete-cell.mdx index 760f679525..65fd7a204c 100644 --- a/apps/docs/document-api/reference/tables/delete-cell.mdx +++ b/apps/docs/document-api/reference/tables/delete-cell.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "mode": "shiftLeft", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/delete-column.mdx b/apps/docs/document-api/reference/tables/delete-column.mdx index 1cff7f06a8..39dde97859 100644 --- a/apps/docs/document-api/reference/tables/delete-column.mdx +++ b/apps/docs/document-api/reference/tables/delete-column.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "columnIndex": 1, - "tableNodeId": "example", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/delete-row.mdx b/apps/docs/document-api/reference/tables/delete-row.mdx index 795449da50..9351b2c16e 100644 --- a/apps/docs/document-api/reference/tables/delete-row.mdx +++ b/apps/docs/document-api/reference/tables/delete-row.mdx @@ -46,16 +46,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" - }, - "target": { - "kind": "block", - "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -83,6 +77,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -91,7 +89,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/delete.mdx b/apps/docs/document-api/reference/tables/delete.mdx index b8429d2e6b..99f9e1c464 100644 --- a/apps/docs/document-api/reference/tables/delete.mdx +++ b/apps/docs/document-api/reference/tables/delete.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/distribute-columns.mdx b/apps/docs/document-api/reference/tables/distribute-columns.mdx index 4a77999425..232e07ab02 100644 --- a/apps/docs/document-api/reference/tables/distribute-columns.mdx +++ b/apps/docs/document-api/reference/tables/distribute-columns.mdx @@ -42,11 +42,10 @@ _No fields._ "end": 10, "start": 0 }, - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -74,6 +73,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -82,7 +85,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/distribute-rows.mdx b/apps/docs/document-api/reference/tables/distribute-rows.mdx index b5bca6f90e..1967d55b63 100644 --- a/apps/docs/document-api/reference/tables/distribute-rows.mdx +++ b/apps/docs/document-api/reference/tables/distribute-rows.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/get-cells.mdx b/apps/docs/document-api/reference/tables/get-cells.mdx index 92afdc13fc..f519679270 100644 --- a/apps/docs/document-api/reference/tables/get-cells.mdx +++ b/apps/docs/document-api/reference/tables/get-cells.mdx @@ -38,12 +38,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "rowIndex": 1, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` diff --git a/apps/docs/document-api/reference/tables/get-properties.mdx b/apps/docs/document-api/reference/tables/get-properties.mdx index ff99559300..6d275ae55d 100644 --- a/apps/docs/document-api/reference/tables/get-properties.mdx +++ b/apps/docs/document-api/reference/tables/get-properties.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` diff --git a/apps/docs/document-api/reference/tables/get.mdx b/apps/docs/document-api/reference/tables/get.mdx index ed8c3226ce..749f792792 100644 --- a/apps/docs/document-api/reference/tables/get.mdx +++ b/apps/docs/document-api/reference/tables/get.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` diff --git a/apps/docs/document-api/reference/tables/index.mdx b/apps/docs/document-api/reference/tables/index.mdx index 16aa3a02b7..3509affd8a 100644 --- a/apps/docs/document-api/reference/tables/index.mdx +++ b/apps/docs/document-api/reference/tables/index.mdx @@ -12,6 +12,10 @@ description: Tables operation reference from the canonical Document API contract Table structure, layout, styling, and cell operations. + +For non-destructive table-targeted mutations, reuse `result.table.nodeId` from the previous success result instead of re-running `find()`. Cell-targeted border/shading calls may still return a `tableCell` address. + + | Operation | Member path | Mutates | Idempotency | Tracked | Dry run | | --- | --- | --- | --- | --- | --- | | tables.convertFromText | `tables.convertFromText` | Yes | `non-idempotent` | No | Yes | diff --git a/apps/docs/document-api/reference/tables/insert-cell.mdx b/apps/docs/document-api/reference/tables/insert-cell.mdx index 8bf9ecedbb..668611cd0c 100644 --- a/apps/docs/document-api/reference/tables/insert-cell.mdx +++ b/apps/docs/document-api/reference/tables/insert-cell.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "mode": "shiftRight", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/insert-column.mdx b/apps/docs/document-api/reference/tables/insert-column.mdx index 45db92077f..9b481debb9 100644 --- a/apps/docs/document-api/reference/tables/insert-column.mdx +++ b/apps/docs/document-api/reference/tables/insert-column.mdx @@ -41,11 +41,10 @@ _No fields._ "columnIndex": 1, "count": 1, "position": "left", - "tableNodeId": "example", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -73,6 +72,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -81,7 +84,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/insert-row.mdx b/apps/docs/document-api/reference/tables/insert-row.mdx index 52f76a3525..4c291aee4b 100644 --- a/apps/docs/document-api/reference/tables/insert-row.mdx +++ b/apps/docs/document-api/reference/tables/insert-row.mdx @@ -46,17 +46,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "position": "above", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" - }, - "target": { - "kind": "block", - "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -84,6 +78,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -92,7 +90,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/merge-cells.mdx b/apps/docs/document-api/reference/tables/merge-cells.mdx index 93ef2217e3..b6369fd26c 100644 --- a/apps/docs/document-api/reference/tables/merge-cells.mdx +++ b/apps/docs/document-api/reference/tables/merge-cells.mdx @@ -46,11 +46,10 @@ _No fields._ "columnIndex": 1, "rowIndex": 1 }, - "tableNodeId": "example", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -78,6 +77,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -86,7 +89,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/move.mdx b/apps/docs/document-api/reference/tables/move.mdx index 4722dee631..c8fa3d7748 100644 --- a/apps/docs/document-api/reference/tables/move.mdx +++ b/apps/docs/document-api/reference/tables/move.mdx @@ -41,11 +41,10 @@ _No fields._ "destination": { "kind": "documentStart" }, - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -73,6 +72,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -81,7 +84,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-alt-text.mdx b/apps/docs/document-api/reference/tables/set-alt-text.mdx index f9ba336ba1..0abc0ec0b0 100644 --- a/apps/docs/document-api/reference/tables/set-alt-text.mdx +++ b/apps/docs/document-api/reference/tables/set-alt-text.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "title": "example" } @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-border.mdx b/apps/docs/document-api/reference/tables/set-border.mdx index 495d108249..820f900851 100644 --- a/apps/docs/document-api/reference/tables/set-border.mdx +++ b/apps/docs/document-api/reference/tables/set-border.mdx @@ -42,11 +42,10 @@ _No fields._ "edge": "top", "lineStyle": "example", "lineWeightPt": 12.5, - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -74,6 +73,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -82,7 +85,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-cell-padding.mdx b/apps/docs/document-api/reference/tables/set-cell-padding.mdx index a12a164336..0a384bf78a 100644 --- a/apps/docs/document-api/reference/tables/set-cell-padding.mdx +++ b/apps/docs/document-api/reference/tables/set-cell-padding.mdx @@ -40,12 +40,11 @@ _No fields._ { "bottomPt": 12.5, "leftPt": 12.5, - "nodeId": "node-def456", "rightPt": 12.5, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "topPt": 12.5 } @@ -74,6 +73,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -82,7 +85,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-cell-properties.mdx b/apps/docs/document-api/reference/tables/set-cell-properties.mdx index cc98204b3e..b95e2d2828 100644 --- a/apps/docs/document-api/reference/tables/set-cell-properties.mdx +++ b/apps/docs/document-api/reference/tables/set-cell-properties.mdx @@ -38,12 +38,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "preferredWidthPt": 12.5, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-cell-spacing.mdx b/apps/docs/document-api/reference/tables/set-cell-spacing.mdx index 1fc3bd30ca..c9abb7b23b 100644 --- a/apps/docs/document-api/reference/tables/set-cell-spacing.mdx +++ b/apps/docs/document-api/reference/tables/set-cell-spacing.mdx @@ -38,12 +38,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "spacingPt": 12.5, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-column-width.mdx b/apps/docs/document-api/reference/tables/set-column-width.mdx index 17af31c505..04616c192e 100644 --- a/apps/docs/document-api/reference/tables/set-column-width.mdx +++ b/apps/docs/document-api/reference/tables/set-column-width.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "columnIndex": 1, - "tableNodeId": "example", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "widthPt": 12.5 } @@ -72,6 +71,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -80,7 +83,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-layout.mdx b/apps/docs/document-api/reference/tables/set-layout.mdx index 5ec464c5ea..eb7f70421a 100644 --- a/apps/docs/document-api/reference/tables/set-layout.mdx +++ b/apps/docs/document-api/reference/tables/set-layout.mdx @@ -38,12 +38,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "preferredWidth": 12.5, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-row-height.mdx b/apps/docs/document-api/reference/tables/set-row-height.mdx index 8bbe9e537c..4995924c76 100644 --- a/apps/docs/document-api/reference/tables/set-row-height.mdx +++ b/apps/docs/document-api/reference/tables/set-row-height.mdx @@ -47,17 +47,11 @@ _No fields._ ```json { "heightPt": 12.5, - "nodeId": "node-def456", "rule": "atLeast", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" - }, - "target": { - "kind": "block", - "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -85,6 +79,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -93,7 +91,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-row-options.mdx b/apps/docs/document-api/reference/tables/set-row-options.mdx index a41a7084c4..e237099434 100644 --- a/apps/docs/document-api/reference/tables/set-row-options.mdx +++ b/apps/docs/document-api/reference/tables/set-row-options.mdx @@ -46,16 +46,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "tableTarget": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" - }, - "target": { - "kind": "block", - "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -83,6 +77,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -91,7 +89,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-shading.mdx b/apps/docs/document-api/reference/tables/set-shading.mdx index 56870e039b..26acc3df68 100644 --- a/apps/docs/document-api/reference/tables/set-shading.mdx +++ b/apps/docs/document-api/reference/tables/set-shading.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "color": "example", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-style-option.mdx b/apps/docs/document-api/reference/tables/set-style-option.mdx index abcaa196eb..6ee304e646 100644 --- a/apps/docs/document-api/reference/tables/set-style-option.mdx +++ b/apps/docs/document-api/reference/tables/set-style-option.mdx @@ -40,11 +40,10 @@ _No fields._ { "enabled": true, "flag": "headerRow", - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -72,6 +71,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -80,7 +83,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-style.mdx b/apps/docs/document-api/reference/tables/set-style.mdx index dfb83c1fb7..6ca5ac4efb 100644 --- a/apps/docs/document-api/reference/tables/set-style.mdx +++ b/apps/docs/document-api/reference/tables/set-style.mdx @@ -38,12 +38,11 @@ _No fields._ ```json { - "nodeId": "node-def456", "styleId": "style-001", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/set-table-padding.mdx b/apps/docs/document-api/reference/tables/set-table-padding.mdx index b5f0e3ce3a..291b3a1098 100644 --- a/apps/docs/document-api/reference/tables/set-table-padding.mdx +++ b/apps/docs/document-api/reference/tables/set-table-padding.mdx @@ -40,12 +40,11 @@ _No fields._ { "bottomPt": 12.5, "leftPt": 12.5, - "nodeId": "node-def456", "rightPt": 12.5, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "topPt": 12.5 } @@ -74,6 +73,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -82,7 +85,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/sort.mdx b/apps/docs/document-api/reference/tables/sort.mdx index b44c57e435..d35831f74b 100644 --- a/apps/docs/document-api/reference/tables/sort.mdx +++ b/apps/docs/document-api/reference/tables/sort.mdx @@ -45,11 +45,10 @@ _No fields._ "type": "text" } ], - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -77,6 +76,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -85,7 +88,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/split-cell.mdx b/apps/docs/document-api/reference/tables/split-cell.mdx index 8a94d4ee7a..5d0374d72c 100644 --- a/apps/docs/document-api/reference/tables/split-cell.mdx +++ b/apps/docs/document-api/reference/tables/split-cell.mdx @@ -39,12 +39,11 @@ _No fields._ ```json { "columns": 1, - "nodeId": "node-def456", "rows": 1, "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -72,6 +71,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -80,7 +83,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/split.mdx b/apps/docs/document-api/reference/tables/split.mdx index d5a4f527dc..4109072316 100644 --- a/apps/docs/document-api/reference/tables/split.mdx +++ b/apps/docs/document-api/reference/tables/split.mdx @@ -39,11 +39,10 @@ _No fields._ ```json { "atRowIndex": 1, - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -71,6 +70,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -79,7 +82,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/apps/docs/document-api/reference/tables/unmerge-cells.mdx b/apps/docs/document-api/reference/tables/unmerge-cells.mdx index d451f388ea..58aac0130d 100644 --- a/apps/docs/document-api/reference/tables/unmerge-cells.mdx +++ b/apps/docs/document-api/reference/tables/unmerge-cells.mdx @@ -38,11 +38,10 @@ _No fields._ ```json { - "nodeId": "node-def456", "target": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" } } ``` @@ -70,6 +69,10 @@ _No fields._ | `failure.message` | string | yes | | | `success` | `false` | yes | Constant: `false` | + +When present, `result.table` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass `result.table.nodeId` to the next table operation instead of re-running `find()`. Destructive operations may omit `table`, and cell-targeted border/shading calls may still return a `tableCell` address. + + ### Example response ```json @@ -78,7 +81,7 @@ _No fields._ "table": { "kind": "block", "nodeId": "node-def456", - "nodeType": "paragraph" + "nodeType": "table" }, "trackedChangeRefs": [ { diff --git a/packages/document-api/scripts/lib/reference-docs-artifacts.ts b/packages/document-api/scripts/lib/reference-docs-artifacts.ts index 02ed12894d..477ac40320 100644 --- a/packages/document-api/scripts/lib/reference-docs-artifacts.ts +++ b/packages/document-api/scripts/lib/reference-docs-artifacts.ts @@ -682,6 +682,101 @@ function isObjectRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } +function makeExampleBlockAddress(nodeType: string): Record { + return { + kind: 'block', + nodeId: 'node-def456', + nodeType, + }; +} + +function isTableReferenceOperation(operationId: ContractOperationSnapshot['operationId']): boolean { + return operationId === 'create.table' || operationId.startsWith('tables.'); +} + +function normalizeTableOperationInputExample( + operationId: ContractOperationSnapshot['operationId'], + input: unknown, +): unknown { + if (!isTableReferenceOperation(operationId) || operationId === 'create.table' || !isObjectRecord(input)) { + return input; + } + + const clone = structuredClone(input) as Record; + + if (isObjectRecord(clone.tableTarget)) { + clone.tableTarget = makeExampleBlockAddress('table'); + delete clone.tableNodeId; + delete clone.target; + delete clone.nodeId; + return clone; + } + + if (isObjectRecord(clone.target)) { + clone.target = makeExampleBlockAddress('table'); + delete clone.nodeId; + } + + return clone; +} + +function normalizeTableOperationOutputExample( + operationId: ContractOperationSnapshot['operationId'], + output: unknown, +): unknown { + if (!isTableReferenceOperation(operationId) || !isObjectRecord(output)) { + return output; + } + + const clone = structuredClone(output) as Record; + if (isObjectRecord(clone.table)) { + clone.table = makeExampleBlockAddress('table'); + } + return clone; +} + +function schemaHasTopLevelField(schema: JsonSchema, $defs: Defs, fieldName: string, depth = 0): boolean { + if (depth > 8) return false; + + const { resolved } = resolveRef(schema, $defs); + const flat = flattenAllOf(resolved, $defs); + const properties = flat.properties as Record | undefined; + if (properties && Object.prototype.hasOwnProperty.call(properties, fieldName)) { + return true; + } + + for (const keyword of ['oneOf', 'anyOf'] as const) { + const variants = flat[keyword]; + if (!Array.isArray(variants)) continue; + for (const variant of variants as JsonSchema[]) { + if (schemaHasTopLevelField(variant, $defs, fieldName, depth + 1)) { + return true; + } + } + } + + return false; +} + +function renderTableResultNote(operation: ContractOperationSnapshot, $defs: Defs): string { + if ( + !isTableReferenceOperation(operation.operationId) || + !schemaHasTopLevelField(operation.schemas.output, $defs, 'table') + ) { + return ''; + } + + if (operation.operationId === 'create.table') { + return ` +On success, \`result.table\` is the created table address. Reuse \`result.table.nodeId\` for follow-up table operations in the same session. +`; + } + + return ` +When present, \`result.table\` is the follow-up address to reuse after this call. For non-destructive table-targeted mutations, pass \`result.table.nodeId\` to the next table operation instead of re-running \`find()\`. Destructive operations may omit \`table\`, and cell-targeted border/shading calls may still return a \`tableCell\` address. +`; +} + function buildCapabilitiesOutputExample(snapshot: ReturnType): unknown { const operation = snapshot.operations.find((entry) => entry.operationId === 'capabilities.get'); if (!operation) return {}; @@ -789,9 +884,12 @@ function getOperationExamples( }, }; + const input = inputOverrides[operation.operationId] ?? generateExample(operation.schemas.input, snapshot.$defs); + const output = outputOverrides[operation.operationId] ?? generateExample(operation.schemas.output, snapshot.$defs); + return { - input: inputOverrides[operation.operationId] ?? generateExample(operation.schemas.input, snapshot.$defs), - output: outputOverrides[operation.operationId] ?? generateExample(operation.schemas.output, snapshot.$defs), + input: normalizeTableOperationInputExample(operation.operationId, input), + output: normalizeTableOperationOutputExample(operation.operationId, output), }; } @@ -858,6 +956,7 @@ function renderOperationPage( const inputFields = renderFieldSections(operation.schemas.input, $defs); const outputFields = renderFieldSections(operation.schemas.output, $defs); + const tableResultNote = renderTableResultNote(operation, $defs); const { input: inputExample, output: outputExample } = getOperationExamples(operation, snapshot); const stepOpsSection = renderStepOpsSection(operation); @@ -912,7 +1011,7 @@ ${stableStringify(inputExample)} ## Output fields -${outputFields} +${outputFields}${tableResultNote ? `\n\n${tableResultNote}` : ''} ### Example response @@ -970,7 +1069,15 @@ ${GENERATED_MARKER} [Back to full reference](${toRelativeDocHref(group.pagePath, REFERENCE_INDEX_PATH)}) -${group.definition.description} +${group.definition.description}${ + group.definition.key === 'tables' + ? ` + + +For non-destructive table-targeted mutations, reuse \`result.table.nodeId\` from the previous success result instead of re-running \`find()\`. Cell-targeted border/shading calls may still return a \`tableCell\` address. +` + : '' + } | Operation | Member path | Mutates | Idempotency | Tracked | Dry run | | --- | --- | --- | --- | --- | --- |