@beyondwork/docx-react-component 1.0.42 → 1.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +17 -0
  2. package/package.json +5 -4
  3. package/src/api/editor-state-types.ts +110 -0
  4. package/src/api/public-types.ts +333 -4
  5. package/src/core/commands/formatting-commands.ts +7 -1
  6. package/src/core/commands/index.ts +60 -10
  7. package/src/core/commands/text-commands.ts +59 -0
  8. package/src/core/search/search-text.ts +15 -2
  9. package/src/core/selection/review-anchors.ts +131 -21
  10. package/src/index.ts +29 -1
  11. package/src/io/chart-preview-resolver.ts +281 -0
  12. package/src/io/docx-session.ts +692 -2
  13. package/src/io/export/build-app-properties-xml.ts +1 -1
  14. package/src/io/export/serialize-comments.ts +38 -9
  15. package/src/io/export/twip.ts +1 -1
  16. package/src/io/load-scheduler.ts +230 -0
  17. package/src/io/normalize/normalize-text.ts +116 -0
  18. package/src/io/ooxml/parse-comments.ts +0 -33
  19. package/src/io/ooxml/parse-complex-content.ts +14 -0
  20. package/src/io/ooxml/parse-main-document.ts +4 -0
  21. package/src/io/ooxml/workflow-payload-validator.ts +97 -1
  22. package/src/io/ooxml/workflow-payload.ts +172 -1
  23. package/src/preservation/opaque-region.ts +5 -0
  24. package/src/review/store/comment-remapping.ts +2 -2
  25. package/src/runtime/collab-session.ts +1 -1
  26. package/src/runtime/document-runtime.ts +661 -42
  27. package/src/runtime/edit-dispatch/dispatch-text-command.ts +98 -0
  28. package/src/runtime/edit-dispatch/index.ts +2 -0
  29. package/src/runtime/edit-dispatch/list-aware-dispatch.ts +125 -0
  30. package/src/runtime/editor-state-channel.ts +544 -0
  31. package/src/runtime/editor-state-integration.ts +217 -0
  32. package/src/runtime/editor-surface/capabilities.ts +411 -0
  33. package/src/runtime/layout/index.ts +2 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +4 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +63 -2
  36. package/src/runtime/layout/layout-engine-version.ts +41 -0
  37. package/src/runtime/layout/paginated-layout-engine.ts +211 -14
  38. package/src/runtime/layout/public-facet.ts +430 -1
  39. package/src/runtime/perf-counters.ts +28 -0
  40. package/src/runtime/prerender/cache-envelope.ts +29 -0
  41. package/src/runtime/prerender/cache-key.ts +66 -0
  42. package/src/runtime/prerender/font-fingerprint.ts +17 -0
  43. package/src/runtime/prerender/graph-canonicalize.ts +121 -0
  44. package/src/runtime/prerender/indexeddb-cache.ts +184 -0
  45. package/src/runtime/prerender/prerender-document.ts +145 -0
  46. package/src/runtime/render/block-fragment-projection.ts +2 -0
  47. package/src/runtime/render/render-frame-types.ts +17 -0
  48. package/src/runtime/render/render-kernel.ts +172 -29
  49. package/src/runtime/selection/post-edit-validator.ts +77 -0
  50. package/src/runtime/surface-projection.ts +45 -7
  51. package/src/runtime/workflow-markup.ts +71 -16
  52. package/src/ui/WordReviewEditor.tsx +142 -237
  53. package/src/ui/editor-command-bag.ts +14 -0
  54. package/src/ui/editor-runtime-boundary.ts +115 -12
  55. package/src/ui/editor-shell-view.tsx +10 -0
  56. package/src/ui/editor-surface-controller.tsx +5 -0
  57. package/src/ui/headless/selection-helpers.ts +10 -0
  58. package/src/ui/runtime-shortcut-dispatch.ts +28 -68
  59. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
  60. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
  61. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +48 -0
  62. package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
  63. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
  64. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +76 -165
  65. package/src/ui-tailwind/editor-surface/pm-schema.ts +170 -4
  66. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +58 -7
  67. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
  68. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
  69. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +8 -255
  70. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +47 -0
  71. package/src/ui-tailwind/index.ts +5 -1
  72. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
  73. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
  74. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
  75. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
  76. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
  77. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
  78. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +157 -0
  79. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
  80. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
  81. package/src/ui-tailwind/theme/editor-theme.css +47 -14
  82. package/src/ui-tailwind/tw-review-workspace.tsx +303 -123
@@ -31,6 +31,7 @@ import {
31
31
  insertHardBreak,
32
32
  insertTab,
33
33
  insertText,
34
+ outdentParagraphAtSelection,
34
35
  splitParagraph,
35
36
  } from "./text-commands.ts";
36
37
  import type { RevisionRecord as CanonicalRevisionRecord } from "../../model/canonical-document.ts";
@@ -128,6 +129,10 @@ export type EditorCommand =
128
129
  type: "text.insert-tab";
129
130
  origin?: CommandOrigin;
130
131
  }
132
+ | {
133
+ type: "text.outdent-tab";
134
+ origin?: CommandOrigin;
135
+ }
131
136
  | {
132
137
  type: "text.insert-hard-break";
133
138
  origin?: CommandOrigin;
@@ -367,7 +372,7 @@ export interface TransactionEffects {
367
372
  commentAdded?: { commentId: string; anchor: EditorAnchorProjection };
368
373
  commentResolved?: { commentId: string };
369
374
  commentReopened?: { commentId: string };
370
- commentReplyAdded?: { commentId: string };
375
+ commentReplyAdded?: { commentId: string; entryId: string };
371
376
  commentBodyEdited?: { commentId: string };
372
377
  changeAccepted?: { changeId: string };
373
378
  changeRejected?: { changeId: string };
@@ -513,6 +518,26 @@ export function executeEditorCommand(
513
518
  insertTab(document, selection, context),
514
519
  );
515
520
  }
521
+ case "text.outdent-tab": {
522
+ // No suggesting-mode branch: outdent is a paragraph-format change, not a
523
+ // text-content change, so it bypasses the suggesting/track-changes pipeline.
524
+ // (If a tracked-changes test fails, copy the suggesting branch from
525
+ // text.insert-tab.)
526
+ //
527
+ // We use `buildDocumentReplaceTransaction` (not `applyTextCommand`) so the
528
+ // no-op cases — already at zero indent or list paragraph — skip the
529
+ // transaction entirely and do not bump revisionToken. This matches the
530
+ // pattern used by the formatting-indent path.
531
+ const result = outdentParagraphAtSelection(state.document, state.selection, {
532
+ timestamp: context.timestamp,
533
+ });
534
+ return buildDocumentReplaceTransaction(state, context, {
535
+ changed: result.changed,
536
+ document: result.document,
537
+ selection: result.selection,
538
+ mapping: result.mapping,
539
+ });
540
+ }
516
541
  case "text.insert-hard-break": {
517
542
  const suggestingResult = context.documentMode === "suggesting"
518
543
  ? applySuggestingInsertUnit(state, "hard_break", context)
@@ -785,7 +810,7 @@ export function executeEditorCommand(
785
810
  {
786
811
  historyBoundary: "push",
787
812
  markDirty: true,
788
- effects: { commentReplyAdded: { commentId: command.commentId } },
813
+ effects: { commentReplyAdded: { commentId: command.commentId, entryId } },
789
814
  },
790
815
  );
791
816
  }
@@ -1791,22 +1816,46 @@ function combineMappingSteps(
1791
1816
  // Suggesting mode: creates revision records instead of (or alongside) text mutations
1792
1817
  // ---------------------------------------------------------------------------
1793
1818
 
1794
- let suggestingRevisionCounter = 0;
1795
-
1819
+ /**
1820
+ * Builds a suggesting-mode revision id that is deterministic across
1821
+ * clients. The id is a pure function of `(existing, timestamp, authorId)`
1822
+ * so that origin and replica — both of whom see the same `existing`
1823
+ * revisions record after a Yjs-ordered replay and receive the same
1824
+ * `timestamp` + `authorId` on the broadcast command event — produce
1825
+ * the same id for the same authored revision.
1826
+ *
1827
+ * Previously this used a module-level counter; that counter was shared
1828
+ * across every `DocumentRuntime` in a single process and drifted between
1829
+ * origin and replica whenever either side had other suggesting-mode work
1830
+ * bump the counter before replay. Cross-client `change.accept(changeId)`
1831
+ * / `change.reject(changeId)` then missed because the target id only
1832
+ * existed on one side.
1833
+ */
1796
1834
  function createSuggestingRevisionId(
1797
1835
  existing: Record<string, unknown>,
1798
1836
  timestamp: string,
1837
+ authorId: string,
1799
1838
  ): string {
1800
- suggestingRevisionCounter += 1;
1801
1839
  const ts = timestamp.replace(/[^0-9]/gu, "");
1802
- let id = `change-${ts}-s${suggestingRevisionCounter}`;
1840
+ const authorMarker = sanitizeAuthorMarker(authorId);
1841
+ const prefix = `change-${authorMarker}-${ts}-s`;
1842
+ let n = 1;
1843
+ let id = `${prefix}${n}`;
1803
1844
  while (existing[id]) {
1804
- suggestingRevisionCounter += 1;
1805
- id = `change-${ts}-s${suggestingRevisionCounter}`;
1845
+ n += 1;
1846
+ id = `${prefix}${n}`;
1806
1847
  }
1807
1848
  return id;
1808
1849
  }
1809
1850
 
1851
+ function sanitizeAuthorMarker(authorId: string): string {
1852
+ // OOXML `w:id` accepts alphanumerics plus `._-`; keep within that set so
1853
+ // the id survives round-trips unchanged (see `sanitizeRevisionId` on the
1854
+ // serializer side).
1855
+ const cleaned = authorId.replace(/[^A-Za-z0-9._-]/g, "-");
1856
+ return cleaned.length > 0 ? cleaned : "u";
1857
+ }
1858
+
1810
1859
  function createAuthoredRevision(
1811
1860
  existing: Record<string, unknown>,
1812
1861
  kind: "insertion" | "deletion",
@@ -1816,7 +1865,7 @@ function createAuthoredRevision(
1816
1865
  timestamp: string,
1817
1866
  metadata: Partial<NonNullable<CanonicalRevisionRecord["metadata"]>> = {},
1818
1867
  ): CanonicalRevisionRecord {
1819
- const changeId = createSuggestingRevisionId(existing, timestamp);
1868
+ const changeId = createSuggestingRevisionId(existing, timestamp, authorId);
1820
1869
  return {
1821
1870
  changeId,
1822
1871
  kind,
@@ -2066,6 +2115,7 @@ function applySuggestingInsert(
2066
2115
  const replacementSuggestionId = createSuggestingRevisionId(
2067
2116
  reviewState.document.review.revisions,
2068
2117
  context.timestamp,
2118
+ authorId,
2069
2119
  );
2070
2120
 
2071
2121
  // Step 3: Create deletion revision for the selected range (text stays in place).
@@ -2324,7 +2374,7 @@ function applySuggestingInsertUnit(
2324
2374
  result.mapping,
2325
2375
  );
2326
2376
  const replacementSuggestionId = from !== to
2327
- ? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp)
2377
+ ? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp, authorId)
2328
2378
  : undefined;
2329
2379
 
2330
2380
  // If non-collapsed, mark selected range as deletion (positions are pre-mapping, content preserved)
@@ -23,6 +23,8 @@ import {
23
23
  resolveParagraphScope,
24
24
  type StructuralMutationResult,
25
25
  } from "./structural-helpers.ts";
26
+ import { applyIndentation } from "./formatting-commands.ts";
27
+ import { createEmptyMapping, type TransactionMapping } from "../selection/mapping.ts";
26
28
  import { createEditorSurfaceSnapshot } from "../../runtime/surface-projection.ts";
27
29
 
28
30
  export interface TextCommandContext {
@@ -197,6 +199,63 @@ export function insertTab(
197
199
  );
198
200
  }
199
201
 
202
+ /**
203
+ * Reduce the active paragraph's left indent by one half-inch step (720 twips).
204
+ * Mirrors Word's Shift+Tab behavior on a non-list paragraph.
205
+ *
206
+ * Defensive list-check: if the paragraph carries `numbering`, this is a no-op —
207
+ * list-level changes are owned by the list-aware dispatcher in
208
+ * `src/runtime/edit-dispatch/list-aware-dispatch.ts`, which intercepts before
209
+ * we are called. The runtime command itself never mutates list level.
210
+ *
211
+ * No text positions change, so `mapping` is empty and the selection is preserved.
212
+ */
213
+ export function outdentParagraphAtSelection(
214
+ document: CanonicalDocumentEnvelope,
215
+ selection: SelectionSnapshot,
216
+ context: TextCommandContext,
217
+ ): {
218
+ changed: boolean;
219
+ document: CanonicalDocumentEnvelope;
220
+ selection: SelectionSnapshot;
221
+ mapping: TransactionMapping;
222
+ } {
223
+ const noop = {
224
+ changed: false,
225
+ document,
226
+ selection,
227
+ mapping: createEmptyMapping(),
228
+ };
229
+
230
+ const scope = resolveParagraphScope(document, selection);
231
+ if (!scope) {
232
+ return noop;
233
+ }
234
+
235
+ // Defensive: list paragraphs are owned by the list-aware dispatcher.
236
+ if (scope.paragraph.numbering) {
237
+ return noop;
238
+ }
239
+
240
+ // `resolveParagraphScope` already returns a cloned paragraph, so it is
241
+ // safe to mutate in place.
242
+ const changed = applyIndentation(scope.paragraph, -1);
243
+ if (!changed) {
244
+ return noop;
245
+ }
246
+
247
+ const nextDocument = replaceParagraphScope(document, scope, [scope.paragraph]);
248
+ return {
249
+ changed: true,
250
+ document: {
251
+ ...nextDocument,
252
+ updatedAt: context.timestamp,
253
+ },
254
+ selection,
255
+ mapping: createEmptyMapping(),
256
+ };
257
+ }
258
+
200
259
  export function insertHardBreak(
201
260
  document: CanonicalDocumentEnvelope,
202
261
  selection: SelectionSnapshot,
@@ -26,7 +26,7 @@ export interface SecondaryStorySearchResult extends SurfaceSearchResult {
26
26
  storyTarget: EditorStoryTarget;
27
27
  }
28
28
 
29
- interface ProjectedSurfaceText {
29
+ export interface ProjectedSurfaceText {
30
30
  text: string;
31
31
  offsetMap: Array<number | null>;
32
32
  }
@@ -112,7 +112,20 @@ export function searchSurfaceBlocks(
112
112
  query: string,
113
113
  options: SearchTextOptions = {},
114
114
  ): SurfaceSearchResult[] {
115
- const projection = projectSurfaceText(blocks);
115
+ return searchProjectedSurfaceText(projectSurfaceText(blocks), query, options);
116
+ }
117
+
118
+ /**
119
+ * Search a pre-projected surface text. Hoist `projectSurfaceText(blocks)` out
120
+ * of a per-query loop to avoid rebuilding the projection N times for N queries
121
+ * against the same surface — L7 Phase 1.5 discovered this cost dominates
122
+ * `collectFieldMarkup` on the CCEP large-tables fixture.
123
+ */
124
+ export function searchProjectedSurfaceText(
125
+ projection: ProjectedSurfaceText,
126
+ query: string,
127
+ options: SearchTextOptions = {},
128
+ ): SurfaceSearchResult[] {
116
129
  return findSearchMatches(projection.text, query, options)
117
130
  .map((match) => {
118
131
  const range = resolveProjectedRuntimeRange(
@@ -116,12 +116,61 @@ export function canCreateDocxCommentAnchor(
116
116
  return false;
117
117
  }
118
118
 
119
- return rangeStaysWithinSingleParagraph(content, normalized);
119
+ return rangeStaysWithinCommentableStory(content, normalized);
120
+ }
121
+
122
+ export function rangeStaysWithinCommentableStory(
123
+ content: unknown,
124
+ range: DocRange,
125
+ ): boolean {
126
+ const normalized = normalizeRange(range);
127
+ if (normalized.from === normalized.to) {
128
+ return false;
129
+ }
130
+
131
+ const surfaceBlocks = readSurfaceBlocks(content);
132
+ if (surfaceBlocks) {
133
+ const fromOwner = findContainingParagraphForEndpoint(surfaceBlocks, normalized.from, "start");
134
+ const toOwner = findContainingParagraphForEndpoint(surfaceBlocks, normalized.to, "end");
135
+ if (!fromOwner || !toOwner) {
136
+ return false;
137
+ }
138
+ if (fromOwner.tableId !== toOwner.tableId) {
139
+ return false;
140
+ }
141
+ if (fromOwner.tableCellId !== toOwner.tableCellId) {
142
+ return false;
143
+ }
144
+ return !rangeCrossesOpaqueOrTableBoundary(
145
+ surfaceBlocks,
146
+ normalized,
147
+ fromOwner,
148
+ );
149
+ }
150
+
151
+ const story = parseTextStory(content);
152
+ const upperBound = Math.min(normalized.to, story.units.length);
153
+ for (let index = Math.max(0, normalized.from); index < upperBound; index += 1) {
154
+ const unit = story.units[index];
155
+ if (!unit) continue;
156
+ if (unit.kind === "opaque_block") {
157
+ return false;
158
+ }
159
+ }
160
+ return true;
161
+ }
162
+
163
+ interface FlattenedSurfaceBlock {
164
+ kind: string;
165
+ from: number;
166
+ to: number;
167
+ tableId: string | null;
168
+ tableCellId: string | null;
120
169
  }
121
170
 
122
171
  function readSurfaceBlocks(
123
172
  content: unknown,
124
- ): Array<{ kind: string; from: number; to: number }> | undefined {
173
+ ): FlattenedSurfaceBlock[] | undefined {
125
174
  if (!content || typeof content !== "object" || !("blocks" in content)) {
126
175
  return undefined;
127
176
  }
@@ -131,17 +180,19 @@ function readSurfaceBlocks(
131
180
  return undefined;
132
181
  }
133
182
 
134
- const normalized = flattenSurfaceBlocks(blocks);
183
+ const normalized = flattenSurfaceBlocks(blocks, null, null);
135
184
 
136
185
  return normalized.length > 0 ? normalized : undefined;
137
186
  }
138
187
 
139
188
  function flattenSurfaceBlocks(
140
189
  blocks: unknown[],
141
- ): Array<{ kind: string; from: number; to: number }> {
142
- const flattened: Array<{ kind: string; from: number; to: number }> = [];
190
+ tableId: string | null,
191
+ tableCellId: string | null,
192
+ ): FlattenedSurfaceBlock[] {
193
+ const flattened: FlattenedSurfaceBlock[] = [];
143
194
 
144
- for (const block of blocks) {
195
+ for (const [blockIndex, block] of blocks.entries()) {
145
196
  if (
146
197
  !block ||
147
198
  typeof block !== "object" ||
@@ -152,32 +203,91 @@ function flattenSurfaceBlocks(
152
203
  continue;
153
204
  }
154
205
 
155
- flattened.push({
156
- kind: (block as { kind: string }).kind,
157
- from: (block as { from: number }).from,
158
- to: (block as { to: number }).to,
159
- });
206
+ const kind = (block as { kind: string }).kind;
207
+ const from = (block as { from: number }).from;
208
+ const to = (block as { to: number }).to;
160
209
 
161
- if (
162
- (block as { kind: string }).kind === "table" &&
163
- Array.isArray((block as { rows?: unknown }).rows)
164
- ) {
165
- for (const row of (block as { rows: Array<{ cells?: unknown[] }> }).rows) {
166
- for (const cell of row.cells ?? []) {
210
+ flattened.push({ kind, from, to, tableId, tableCellId });
211
+
212
+ if (kind === "table" && Array.isArray((block as { rows?: unknown }).rows)) {
213
+ const nextTableId =
214
+ (block as { blockId?: string }).blockId ?? `table-${from}-${to}-${blockIndex}`;
215
+ const rows = (block as { rows: Array<{ cells?: unknown[] }> }).rows;
216
+ for (const [rowIdx, row] of rows.entries()) {
217
+ for (const [cellIdx, cell] of (row.cells ?? []).entries()) {
167
218
  if (cell && typeof cell === "object" && Array.isArray((cell as { content?: unknown[] }).content)) {
168
- flattened.push(...flattenSurfaceBlocks((cell as { content: unknown[] }).content));
219
+ const cellFingerprint = `${nextTableId}-r${rowIdx}-c${cellIdx}`;
220
+ flattened.push(
221
+ ...flattenSurfaceBlocks(
222
+ (cell as { content: unknown[] }).content,
223
+ nextTableId,
224
+ cellFingerprint,
225
+ ),
226
+ );
169
227
  }
170
228
  }
171
229
  }
172
230
  }
173
231
 
174
232
  if (
175
- (block as { kind: string }).kind === "sdt_block" &&
176
- Array.isArray((block as { children?: unknown[] }).children)
233
+ kind === "sdt_block" &&
234
+ Array.isArray((block as { children?: unknown }).children)
177
235
  ) {
178
- flattened.push(...flattenSurfaceBlocks((block as { children: unknown[] }).children));
236
+ flattened.push(
237
+ ...flattenSurfaceBlocks(
238
+ (block as { children: unknown[] }).children,
239
+ tableId,
240
+ tableCellId,
241
+ ),
242
+ );
179
243
  }
180
244
  }
181
245
 
182
246
  return flattened;
183
247
  }
248
+
249
+ function findContainingParagraphForEndpoint(
250
+ blocks: readonly FlattenedSurfaceBlock[],
251
+ offset: number,
252
+ kind: "start" | "end",
253
+ ): FlattenedSurfaceBlock | null {
254
+ const matches = blocks.filter(
255
+ (block) =>
256
+ block.kind === "paragraph" && offset >= block.from && offset <= block.to,
257
+ );
258
+ if (matches.length === 0) return null;
259
+ if (matches.length === 1) return matches[0]!;
260
+ // When an offset sits exactly on a paragraph boundary it matches the
261
+ // trailing paragraph as well as the leading one. For a start endpoint we
262
+ // prefer the later paragraph (the range extends forward from it); for an
263
+ // end endpoint we prefer the earlier paragraph (the range ends there).
264
+ if (kind === "start") {
265
+ return matches.reduce((a, b) => (b.from >= a.from ? b : a));
266
+ }
267
+ return matches.reduce((a, b) => (b.to <= a.to ? b : a));
268
+ }
269
+
270
+ function rangeCrossesOpaqueOrTableBoundary(
271
+ blocks: readonly FlattenedSurfaceBlock[],
272
+ range: DocRange,
273
+ origin: FlattenedSurfaceBlock,
274
+ ): boolean {
275
+ for (const block of blocks) {
276
+ const overlapFrom = Math.max(block.from, range.from);
277
+ const overlapTo = Math.min(block.to, range.to);
278
+ if (overlapTo <= overlapFrom) continue;
279
+
280
+ if (block.kind === "opaque_block") {
281
+ return true;
282
+ }
283
+
284
+ if (
285
+ block.kind === "paragraph" &&
286
+ (block.tableId !== origin.tableId ||
287
+ block.tableCellId !== origin.tableCellId)
288
+ ) {
289
+ return true;
290
+ }
291
+ }
292
+ return false;
293
+ }
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@ export { ISSUE_METADATA_ID, REVIEW_ACTION_METADATA_ID } from "./api/public-types
13
13
  // P17 — metadata persistence error class.
14
14
  export { MetadataResolverMissingError } from "./api/public-types.ts";
15
15
 
16
- // Collab substrate (P1 – P8f + P14). See docs/plans/collab-master-plan.md
16
+ // Collab substrate (P1 – P8f + P14). See docs/plans/lane-4-collab-clm-vallor.md
17
17
  // for the shipped-slice table. Surfaces are stable for host integration;
18
18
  // the chrome preset + markdown renderer land in P9 / P10.
19
19
  export { createCollabSession } from "./runtime/collab-session.ts";
@@ -121,6 +121,7 @@ export type {
121
121
  LoadSourcePolicy,
122
122
  EditorSessionState,
123
123
  EditorHostAdapter,
124
+ ChartPreviewResolveParams,
124
125
  WordReviewEditorProps,
125
126
  WordReviewEditorChromePreset,
126
127
  WordReviewEditorChromeOptions,
@@ -185,6 +186,8 @@ export type {
185
186
  SnapshotRefreshChangeKind,
186
187
  SnapshotRefreshHints,
187
188
  AddCommentParams,
189
+ AddCommentResult,
190
+ AddCommentReplyResult,
188
191
  ExportDocxOptions,
189
192
  ExportResult,
190
193
  AutosaveConfig,
@@ -267,4 +270,29 @@ export type {
267
270
  ScopeMetadataPersistence,
268
271
  ScopeMetadataResolver,
269
272
  ScopeMetadataStorageRef,
273
+ // Schema 1.2 — editor-state persistence types
274
+ EditorStateNamespace,
275
+ EditorStateLocation,
276
+ EditorStateResolveErrorMode,
277
+ EditorStatePolicyEntry,
278
+ EditorStatePolicy,
279
+ EditorStateStorageRef,
280
+ EditorStateBlob,
281
+ EditorStateResolver,
282
+ EditorStatePersister,
283
+ EditorStatePolicyMigration,
284
+ EditorStatePartLoadFailure,
285
+ EditorStatePartPersistFailure,
270
286
  } from "./api/public-types.ts";
287
+
288
+ // L7 Phase 2.5 — prerender cache public API. Platforms / ingest workers
289
+ // call `prerenderDocument(buffer)` on template upload to populate the
290
+ // cache; end-user opens read the cache and skip the layout pass. See
291
+ // docs/plans/lane-2-render-performance.md §Phase 2.5.
292
+ export { prerenderDocument } from "./runtime/prerender/prerender-document.ts";
293
+ export type {
294
+ PrerenderOptions,
295
+ PrerenderResult,
296
+ PrerenderCounters,
297
+ } from "./runtime/prerender/prerender-document.ts";
298
+ export type { CacheEnvelope } from "./runtime/prerender/cache-envelope.ts";