docxodus 9.9.0 → 11.0.0

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 (124) hide show
  1. package/README.md +182 -4
  2. package/dist/canonical.d.ts +7 -0
  3. package/dist/canonical.d.ts.map +1 -0
  4. package/dist/canonical.js +56 -0
  5. package/dist/canonical.js.map +1 -0
  6. package/dist/docxodus.worker.d.ts +2 -1
  7. package/dist/docxodus.worker.d.ts.map +1 -1
  8. package/dist/docxodus.worker.js +250 -41
  9. package/dist/docxodus.worker.js.map +1 -1
  10. package/dist/editor.bundle.js +1980 -423
  11. package/dist/editor.d.ts +22 -2
  12. package/dist/editor.d.ts.map +1 -1
  13. package/dist/editor.js +94 -9
  14. package/dist/editor.js.map +1 -1
  15. package/dist/embed.bundle.js +2968 -597
  16. package/dist/embed.d.ts +1 -1
  17. package/dist/embed.iife.js +2969 -598
  18. package/dist/embed.js +1 -1
  19. package/dist/export-assets.json +277 -0
  20. package/dist/export-browser.bundle.js +7989 -0
  21. package/dist/export-browser.d.ts +266 -0
  22. package/dist/export-browser.d.ts.map +1 -0
  23. package/dist/export-browser.js +2915 -0
  24. package/dist/export-browser.js.map +1 -0
  25. package/dist/export-resource-limits-v1.json +57 -0
  26. package/dist/font-contract.d.ts +150 -0
  27. package/dist/font-contract.d.ts.map +1 -0
  28. package/dist/font-contract.js +59 -0
  29. package/dist/font-contract.js.map +1 -0
  30. package/dist/font-runtime.d.ts +33 -0
  31. package/dist/font-runtime.d.ts.map +1 -0
  32. package/dist/font-runtime.js +1100 -0
  33. package/dist/font-runtime.js.map +1 -0
  34. package/dist/index.d.ts +83 -65
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +194 -149
  37. package/dist/index.js.map +1 -1
  38. package/dist/page-geometry.d.ts +8 -1
  39. package/dist/page-geometry.d.ts.map +1 -1
  40. package/dist/page-geometry.js +37 -11
  41. package/dist/page-geometry.js.map +1 -1
  42. package/dist/pagination.bundle.js +1827 -410
  43. package/dist/pagination.d.ts +249 -15
  44. package/dist/pagination.d.ts.map +1 -1
  45. package/dist/pagination.js +1942 -453
  46. package/dist/pagination.js.map +1 -1
  47. package/dist/react.d.ts +13 -6
  48. package/dist/react.d.ts.map +1 -1
  49. package/dist/react.js +38 -5
  50. package/dist/react.js.map +1 -1
  51. package/dist/render-report-v2.schema.json +1581 -0
  52. package/dist/ribbon-chrome.d.ts +2 -2
  53. package/dist/ribbon-chrome.d.ts.map +1 -1
  54. package/dist/ribbon-chrome.js +72 -4
  55. package/dist/ribbon-chrome.js.map +1 -1
  56. package/dist/ribbon.js +119 -0
  57. package/dist/ribbon.js.map +1 -1
  58. package/dist/session.bundle.js +747 -29
  59. package/dist/session.d.ts +159 -19
  60. package/dist/session.d.ts.map +1 -1
  61. package/dist/session.js +593 -25
  62. package/dist/session.js.map +1 -1
  63. package/dist/types.d.ts +1287 -238
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +9 -75
  66. package/dist/types.js.map +1 -1
  67. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
  68. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
  69. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
  70. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
  71. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  72. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  73. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  74. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  75. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  76. package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
  77. package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
  78. package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
  79. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  80. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  81. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
  82. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
  83. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  84. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  85. package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
  86. package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
  87. package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
  88. package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
  89. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  90. package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
  91. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  92. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  93. package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
  94. package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
  95. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  96. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  97. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  98. package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
  99. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  100. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
  101. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  102. package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
  103. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  104. package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
  105. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
  106. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
  107. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  108. package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
  109. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  110. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  111. package/dist/wasm/_framework/dotnet.boot.js +34 -40
  112. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  113. package/dist/wasm/_framework/dotnet.native.js +39 -3
  114. package/dist/wasm/_framework/dotnet.native.js.br +0 -0
  115. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  116. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  117. package/dist/worker-proxy.bundle.js +198 -32
  118. package/dist/worker-proxy.d.ts +37 -4
  119. package/dist/worker-proxy.d.ts.map +1 -1
  120. package/dist/worker-proxy.js +187 -35
  121. package/dist/worker-proxy.js.map +1 -1
  122. package/package.json +22 -6
  123. package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
  124. package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
package/dist/session.js CHANGED
@@ -1,6 +1,46 @@
1
1
  // Copyright (c) Microsoft. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
- import { ContextBoundary, DiffFormat, PlaceholderKinds, ProjectionDepth } from "./types.js";
3
+ import { ContextBoundary, DiffFormat, PlaceholderKinds, ProjectionDepth, ProjectionScopes } from "./types.js";
4
+ function mutationBatchChangeSet(before, after, key) {
5
+ const beforeGroups = new Map();
6
+ const afterGroups = new Map();
7
+ const group = (items, target) => {
8
+ items.forEach((item, index) => {
9
+ const identity = key(item);
10
+ const indices = target.get(identity) ?? [];
11
+ indices.push(index);
12
+ target.set(identity, indices);
13
+ });
14
+ };
15
+ group(before, beforeGroups);
16
+ group(after, afterGroups);
17
+ const beforeMatched = before.map(() => false);
18
+ const afterMatched = after.map(() => false);
19
+ const modified = after.map(() => false);
20
+ for (const [identity, afterIndices] of afterGroups) {
21
+ const beforeIndices = beforeGroups.get(identity) ?? [];
22
+ for (const afterIndex of afterIndices) {
23
+ const beforeIndex = beforeIndices.find(index => !beforeMatched[index] && JSON.stringify(before[index]) === JSON.stringify(after[afterIndex]));
24
+ if (beforeIndex === undefined)
25
+ continue;
26
+ beforeMatched[beforeIndex] = true;
27
+ afterMatched[afterIndex] = true;
28
+ }
29
+ const remainingBefore = beforeIndices.filter(index => !beforeMatched[index]);
30
+ const remainingAfter = afterIndices.filter(index => !afterMatched[index]);
31
+ const modifiedCount = Math.min(remainingBefore.length, remainingAfter.length);
32
+ for (let index = 0; index < modifiedCount; index++) {
33
+ beforeMatched[remainingBefore[index]] = true;
34
+ afterMatched[remainingAfter[index]] = true;
35
+ modified[remainingAfter[index]] = true;
36
+ }
37
+ }
38
+ return {
39
+ added: after.filter((_, index) => !afterMatched[index]),
40
+ removed: before.filter((_, index) => !beforeMatched[index]),
41
+ modified: after.filter((_, index) => modified[index]),
42
+ };
43
+ }
4
44
  /**
5
45
  * Stateful in-memory DOCX editing session keyed by markdown-projection anchor ids.
6
46
  * Mirror of the .NET `DocxSession` surface. See
@@ -26,6 +66,312 @@ export class DocxSession {
26
66
  project() {
27
67
  return JSON.parse(this.wasm.Project(this.handle));
28
68
  }
69
+ /** Monotonic document version (0 at open; +1 per committed mutation/undo/redo). */
70
+ getVersion() {
71
+ return JSON.parse(this.wasm.GetVersion(this.handle)).version;
72
+ }
73
+ /**
74
+ * Verification manifest for the current logical checkpoint. Unsaved edits are included and
75
+ * the read does not mutate package bytes, caches, history, or the document version.
76
+ */
77
+ getPackageManifest() {
78
+ return JSON.parse(this.wasm.GetPackageManifest(this.handle));
79
+ }
80
+ /** Register a browser-materialized PageMap without changing the document version. */
81
+ registerPageMap(pageMap, expectedRendererFingerprint) {
82
+ return JSON.parse(this.wasm.RegisterPageMap(this.handle, JSON.stringify(pageMap), expectedRendererFingerprint ?? ""));
83
+ }
84
+ getPageMapStatus(request) {
85
+ return JSON.parse(this.wasm.GetPageMapStatus(this.handle, request ? JSON.stringify(request) : ""));
86
+ }
87
+ getPageCitation(anchorId, request) {
88
+ return JSON.parse(this.wasm.GetPageCitation(this.handle, anchorId, JSON.stringify(request)));
89
+ }
90
+ /** Evaluate optimistic guards without mutating or advancing the version. */
91
+ checkPreconditions(preconditions) {
92
+ return JSON.parse(this.wasm.CheckPreconditions(this.handle, JSON.stringify(preconditions)));
93
+ }
94
+ /**
95
+ * Guard any synchronous mutation. WASM calls are synchronous and single-threaded, so the
96
+ * check and callback form one uninterrupted client-side operation. Prefer a method's native
97
+ * `preconditions` option where it has one (notably replaceTextRange's match-count guard).
98
+ */
99
+ runWithPreconditions(preconditions, mutation) {
100
+ const checked = this.checkPreconditions(preconditions);
101
+ return checked.success ? mutation() : checked;
102
+ }
103
+ /**
104
+ * Execute synchronous mutations atomically by default. Atomic success is one undo/version
105
+ * unit; any failed or thrown step restores the exact package and history checkpoint.
106
+ */
107
+ executeBatch(steps, mode = "atomic") {
108
+ if (mode !== "atomic" && mode !== "best_effort") {
109
+ throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
110
+ }
111
+ const baseVersion = this.getVersion();
112
+ const observationWarnings = [];
113
+ const inspect = (label, read, fallback) => {
114
+ try {
115
+ return read();
116
+ }
117
+ catch (error) {
118
+ observationWarnings.push(`${label} unavailable: ${error instanceof Error ? error.message : String(error)}`);
119
+ return fallback;
120
+ }
121
+ };
122
+ const beforeRevisions = inspect("Revision delta inspection", () => this.listRevisions(), []);
123
+ const beforeComments = inspect("Comment delta inspection", () => this.listComments(), []);
124
+ const beforeAnnotations = inspect("Annotation delta inspection", () => this.listAnnotations(), []);
125
+ const complete = (result) => {
126
+ try {
127
+ const revisionChanges = mutationBatchChangeSet(beforeRevisions, inspect("Revision delta inspection", () => this.listRevisions(), beforeRevisions), revision => revision.id);
128
+ const commentChanges = mutationBatchChangeSet(beforeComments, inspect("Comment delta inspection", () => this.listComments(), beforeComments), comment => comment.anchorId);
129
+ const annotationChanges = mutationBatchChangeSet(beforeAnnotations, inspect("Annotation delta inspection", () => this.listAnnotations(), beforeAnnotations), annotation => annotation.id ?? "");
130
+ const resultVersion = inspect("Result version inspection", () => this.getVersion(), baseVersion);
131
+ const warnings = [...observationWarnings];
132
+ if ([...revisionChanges.added, ...revisionChanges.modified]
133
+ .some(revision => revision.date !== undefined && revision.date !== null)) {
134
+ warnings.push("Tracked-revision date attributes may use the execution clock; compare revision ids, authors, types, text, and anchors across separate executions.");
135
+ }
136
+ if ([...commentChanges.added, ...commentChanges.modified]
137
+ .some(comment => comment.date !== undefined && comment.date !== null)) {
138
+ warnings.push("Comment date attributes may be generated from the execution clock; supply dates explicitly when byte-identical replay is required.");
139
+ }
140
+ // Same predicate as the .NET receipt (`annotation.Created.HasValue`): the warning is
141
+ // about an execution CLOCK, so an annotation added with no created timestamp is
142
+ // deterministic and must not raise it on one surface and not the other.
143
+ if (annotationChanges.added
144
+ .some(annotation => annotation.created !== undefined && annotation.created !== null)) {
145
+ warnings.push("Auto-generated annotation ids or creation timestamps are execution metadata; supply id and created explicitly when byte-identical replay is required.");
146
+ }
147
+ if (result.steps.some(step => step.results.some(edit => edit.created.length > 0))) {
148
+ warnings.push("Created anchors and related OOXML ids may be generated independently on replay; preview/apply equivalence is semantic and packageHash or anchor ids may differ.");
149
+ }
150
+ if (mode === "best_effort" && !result.success) {
151
+ warnings.push("Best-effort execution retains every successful step despite later failures.");
152
+ }
153
+ // null, never "": an absent hash must not compare equal to another absent hash, or a
154
+ // naive `preview.packageHash === applied.packageHash` replay assertion passes vacuously.
155
+ let packageHash = null;
156
+ if (!this.wasm.GetPackageContentHash) {
157
+ warnings.push("This WASM bundle predates package equivalence hashes; packageHash is unavailable.");
158
+ }
159
+ else {
160
+ try {
161
+ packageHash = this.wasm.GetPackageContentHash(this.handle);
162
+ }
163
+ catch (error) {
164
+ warnings.push(`Package equivalence hash unavailable: ${error instanceof Error ? error.message : String(error)}`);
165
+ }
166
+ }
167
+ return {
168
+ ...result,
169
+ preview: false,
170
+ baseVersion,
171
+ resultVersion,
172
+ packageHash,
173
+ revisionChanges,
174
+ commentChanges,
175
+ annotationChanges,
176
+ warnings,
177
+ html: null,
178
+ };
179
+ }
180
+ catch (error) {
181
+ return {
182
+ ...result,
183
+ preview: false,
184
+ baseVersion,
185
+ resultVersion: inspect("Result version inspection", () => this.getVersion(), baseVersion),
186
+ packageHash: null,
187
+ revisionChanges: { added: [], removed: [], modified: [] },
188
+ commentChanges: { added: [], removed: [], modified: [] },
189
+ annotationChanges: { added: [], removed: [], modified: [] },
190
+ warnings: [
191
+ ...observationWarnings,
192
+ `Batch receipt enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`,
193
+ ],
194
+ html: null,
195
+ };
196
+ }
197
+ };
198
+ const internalFailure = (value) => ({
199
+ success: false,
200
+ error: { code: "internal_error", message: value instanceof Error ? value.message : String(value) },
201
+ created: [], removed: [], modified: [],
202
+ });
203
+ const run = (step) => {
204
+ try {
205
+ const value = step.mutation();
206
+ const results = Array.isArray(value) ? value : [value];
207
+ if (results.some(result => result === null || typeof result !== "object" || typeof result.success !== "boolean")) {
208
+ return [internalFailure("batch mutation returned invalid edit results")];
209
+ }
210
+ // An empty list is a successful no-op step, matching the core contract.
211
+ return results;
212
+ }
213
+ catch (error) {
214
+ return [internalFailure(error)];
215
+ }
216
+ };
217
+ const failureOf = (step, rolledBack) => ({
218
+ index: step.index,
219
+ tool: step.tool,
220
+ action: step.action,
221
+ error: step.results.find(result => !result.success)?.error
222
+ ?? { code: "internal_error", message: "batch step failed without an error" },
223
+ rolledBack,
224
+ });
225
+ const preflightOne = (step) => {
226
+ try {
227
+ return step.preflight?.();
228
+ }
229
+ catch (error) {
230
+ return internalFailure(error).error;
231
+ }
232
+ };
233
+ if (mode === "atomic") {
234
+ const preflight = steps.map(preflightOne);
235
+ const failedPreflight = preflight.findIndex(error => error !== undefined);
236
+ if (failedPreflight >= 0) {
237
+ const source = steps[failedPreflight];
238
+ const failed = {
239
+ index: failedPreflight, tool: source.tool, action: source.action,
240
+ success: false, rolledBack: true,
241
+ results: [{ success: false, error: preflight[failedPreflight], created: [], removed: [], modified: [] }],
242
+ };
243
+ return complete({ mode, status: "failed", success: false, rolledBack: true,
244
+ steps: [failed], failure: failureOf(failed, true) });
245
+ }
246
+ const transaction = this.wasm.BeginTransaction(this.handle);
247
+ const completed = [];
248
+ try {
249
+ for (let index = 0; index < steps.length; index++) {
250
+ const source = steps[index];
251
+ const results = run(source);
252
+ const step = {
253
+ index, tool: source.tool, action: source.action,
254
+ success: results.every(result => result.success), rolledBack: false, results,
255
+ };
256
+ completed.push(step);
257
+ if (!step.success) {
258
+ this.wasm.RollbackTransaction(transaction);
259
+ const rolledBack = completed.map(value => ({ ...value, rolledBack: true }));
260
+ const failed = rolledBack[rolledBack.length - 1];
261
+ return complete({ mode, status: "failed", success: false, rolledBack: true,
262
+ steps: rolledBack, failure: failureOf(failed, true) });
263
+ }
264
+ }
265
+ this.wasm.CommitTransaction(transaction);
266
+ return complete({ mode, status: "ok", success: true, rolledBack: false, steps: completed });
267
+ }
268
+ catch (error) {
269
+ try {
270
+ this.wasm.RollbackTransaction(transaction);
271
+ }
272
+ catch { /* preserve the original */ }
273
+ throw error;
274
+ }
275
+ }
276
+ // Preserve sequential best-effort semantics: a later preflight can observe state created by
277
+ // an earlier successful step, so run it immediately before that step rather than up front.
278
+ const completed = steps.map((source, index) => {
279
+ const preflight = preflightOne(source);
280
+ const results = preflight
281
+ ? [{ success: false, error: preflight, created: [], removed: [], modified: [] }]
282
+ : run(source);
283
+ return {
284
+ index, tool: source.tool, action: source.action,
285
+ success: results.every(result => result.success), rolledBack: false, results,
286
+ };
287
+ });
288
+ const failed = completed.find(step => !step.success);
289
+ return complete({
290
+ mode,
291
+ status: failed ? (completed.some(step => step.success) ? "partial" : "failed") : "ok",
292
+ success: failed === undefined,
293
+ rolledBack: false,
294
+ steps: completed,
295
+ failure: failed ? failureOf(failed, false) : undefined,
296
+ });
297
+ }
298
+ /**
299
+ * Execute the same callback batch algorithm against a complete isolated package clone.
300
+ * Callbacks receive the shadow session explicitly; mutate that argument. The live session's
301
+ * package, caches, version, configuration, and undo/redo history are never execution targets.
302
+ */
303
+ previewBatch(steps, mode = "atomic", options) {
304
+ if (mode !== "atomic" && mode !== "best_effort") {
305
+ throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
306
+ }
307
+ const htmlMode = options?.html ?? "none";
308
+ if (htmlMode !== "none" && htmlMode !== "scoped" && htmlMode !== "full") {
309
+ throw new RangeError(`unknown preview HTML mode: ${String(htmlMode)}`);
310
+ }
311
+ if (!this.wasm.OpenPreviewSession) {
312
+ throw new Error("This WASM bundle does not support isolated mutation previews.");
313
+ }
314
+ const shadow = new DocxSession(this.wasm.OpenPreviewSession(this.handle), this.wasm);
315
+ try {
316
+ const result = shadow.executeBatch(steps.map(step => ({
317
+ tool: step.tool,
318
+ action: step.action,
319
+ mutation: () => step.mutation(shadow),
320
+ preflight: step.preflight ? () => step.preflight(shadow) : undefined,
321
+ })), mode);
322
+ const warnings = [...result.warnings];
323
+ let html = null;
324
+ // A rendered document always starts with '<'; a leading '{' is the bridge's error object.
325
+ const unwrapRendered = (rendered) => {
326
+ if (rendered.trimStart().startsWith("{")) {
327
+ const envelope = JSON.parse(rendered);
328
+ if (envelope.error) {
329
+ warnings.push(`Preview HTML could not be generated: ${envelope.error}`);
330
+ return null;
331
+ }
332
+ }
333
+ return rendered;
334
+ };
335
+ // Preview HTML MUST come from the façade's preview profile (DocxSessionOps.RenderPreview*),
336
+ // not the editor's authoring profile: the editor render hides comments, annotations and
337
+ // headers/footers, so routing a preview through it would show this surface a materially
338
+ // different document than the stdio/Python/MCP surfaces show for the identical batch.
339
+ const legacyProfileWarning = "This WASM bundle predates the shared preview HTML profile; preview HTML omits comments, " +
340
+ "annotations and headers/footers and may differ from other surfaces.";
341
+ try {
342
+ if (htmlMode === "scoped") {
343
+ if (!options?.htmlAnchorId) {
344
+ warnings.push("Scoped HTML was requested without htmlAnchorId; no HTML was generated.");
345
+ }
346
+ else if (this.wasm.RenderPreviewBlockHtml) {
347
+ html = unwrapRendered(this.wasm.RenderPreviewBlockHtml(shadow.handle, options.htmlAnchorId));
348
+ }
349
+ else {
350
+ warnings.push(legacyProfileWarning);
351
+ html = shadow.renderBlock(options.htmlAnchorId);
352
+ }
353
+ }
354
+ else if (htmlMode === "full") {
355
+ if (this.wasm.RenderPreviewHtml) {
356
+ html = unwrapRendered(this.wasm.RenderPreviewHtml(shadow.handle));
357
+ }
358
+ else {
359
+ warnings.push(legacyProfileWarning);
360
+ html = unwrapRendered(this.wasm.RenderHtmlForReview
361
+ ? this.wasm.RenderHtmlForReview(shadow.handle, "docx-", false, false, 1, true)
362
+ : this.wasm.RenderHtml(shadow.handle, "docx-", false, false, 1));
363
+ }
364
+ }
365
+ }
366
+ catch (error) {
367
+ warnings.push(`Preview HTML could not be generated: ${error instanceof Error ? error.message : String(error)}`);
368
+ }
369
+ return { ...result, preview: true, warnings, html };
370
+ }
371
+ finally {
372
+ shadow.close();
373
+ }
374
+ }
29
375
  /**
30
376
  * Project a slice of the document keyed off an anchor — useful for showing
31
377
  * one section to an LLM at a time without paying the cost of projecting the
@@ -41,8 +387,10 @@ export class DocxSession {
41
387
  *
42
388
  * @see docs/architecture/docx_mutation_api.md
43
389
  */
44
- projectAnchor(anchorId, depth = ProjectionDepth.SubtreeAndFollowingSiblings) {
45
- return JSON.parse(this.wasm.ProjectAnchor(this.handle, anchorId, depth));
390
+ projectAnchor(anchorId, depth = ProjectionDepth.SubtreeAndFollowingSiblings, citation) {
391
+ return JSON.parse(citation
392
+ ? this.wasm.ProjectAnchorWithCitations(this.handle, anchorId, depth, JSON.stringify(citation))
393
+ : this.wasm.ProjectAnchor(this.handle, anchorId, depth));
46
394
  }
47
395
  /**
48
396
  * Render a single block to faithful HTML from the live session — the editor's
@@ -61,11 +409,17 @@ export class DocxSession {
61
409
  return html;
62
410
  }
63
411
  // ─── Tier A: text CRUD ───────────────────────────────────────────────
64
- replaceText(anchorId, markdown) {
65
- return JSON.parse(this.wasm.ReplaceText(this.handle, anchorId, markdown));
66
- }
67
- deleteBlock(anchorId) {
68
- return JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
412
+ replaceText(anchorId, markdown, preconditions) {
413
+ const apply = () => JSON.parse(this.wasm.ReplaceText(this.handle, anchorId, markdown));
414
+ return preconditions
415
+ ? this.runWithPreconditions({ ...preconditions, anchorId: preconditions.anchorId ?? anchorId }, apply)
416
+ : apply();
417
+ }
418
+ deleteBlock(anchorId, preconditions) {
419
+ const apply = () => JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
420
+ return preconditions
421
+ ? this.runWithPreconditions({ ...preconditions, anchorId: preconditions.anchorId ?? anchorId }, apply)
422
+ : apply();
69
423
  }
70
424
  /** Reorder one top-level paragraph/heading/list/table block relative to another. */
71
425
  moveBlock(sourceAnchorId, targetAnchorId, position) {
@@ -116,17 +470,29 @@ export class DocxSession {
116
470
  }
117
471
  /**
118
472
  * Insert a `rows`×`cols` table before/after the block. `options` controls borders, row-major
119
- * cell markdown, and cell alignment. The returned `EditResult.created` lists the cell-paragraph
473
+ * cell markdown, and cell alignment. The returned `EditResult.created` lists canonical `tc`
120
474
  * anchors (row-major), so each cell can then be addressed to fill/format.
121
475
  */
122
476
  insertTable(anchorId, position, rows, cols, options) {
123
477
  const optionsJson = options ? JSON.stringify(options) : "";
124
478
  return JSON.parse(this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson));
125
479
  }
480
+ /** Resolve a canonical `tbl` anchor to explicit table/row/column/cell identities. */
481
+ getTableMetadata(tableAnchorId) {
482
+ return JSON.parse(this.wasm.GetTableMetadata(this.handle, tableAnchorId));
483
+ }
484
+ /** Resolve a canonical `tc` anchor to its zero-based table-grid coordinate and spans. */
485
+ resolveTableCellAnchor(cellAnchorId) {
486
+ return JSON.parse(this.wasm.ResolveTableCellAnchor(this.handle, cellAnchorId));
487
+ }
488
+ /** Resolve a zero-based table-grid coordinate to the physical `tc` covering it. */
489
+ resolveTableCellCoordinate(tableAnchorId, rowIndex, columnIndex) {
490
+ return JSON.parse(this.wasm.ResolveTableCellCoordinate(this.handle, tableAnchorId, rowIndex, columnIndex));
491
+ }
126
492
  /**
127
- * Table row/column editing, addressed by a cell-paragraph anchor (e.g. one returned from
128
- * {@link insertTable}'s `created`). Insert clones the reference row/column's widths and starts
129
- * empty (`created` lists the new cell-paragraph anchors); delete of the last row/column removes
493
+ * Table row/column editing, addressed by the canonical `tc` anchor returned from
494
+ * {@link insertTable}'s `created` or table metadata. Insert clones the reference row/column's
495
+ * widths and starts empty (`created` lists new `tc` anchors); delete of the last row/column removes
130
496
  * the whole table. All four are grid-aware: inserting across a merge extends it, deleting
131
497
  * through one narrows it, and deleting a vertical merge's lead row promotes the next row to
132
498
  * carry it — the grid is never left ragged.
@@ -164,7 +530,7 @@ export class DocxSession {
164
530
  return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
165
531
  }
166
532
  /**
167
- * Table styling, addressed by a cell-paragraph anchor — the post-insert counterpart of
533
+ * Table styling, addressed by a canonical `tc` anchor — the post-insert counterpart of
168
534
  * {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
169
535
  * every row's cell width (one positive twip value per column) and pins the table to fixed
170
536
  * layout, exactly as inserting with explicit `columnWidths` would.
@@ -196,6 +562,10 @@ export class DocxSession {
196
562
  setRepeatHeaderRow(cellAnchorId, repeat) {
197
563
  return JSON.parse(this.wasm.SetRepeatHeaderRow(this.handle, cellAnchorId, repeat));
198
564
  }
565
+ /** Apply row layout options to the row containing the canonical cell anchor. */
566
+ setTableRowOptions(cellAnchorId, options) {
567
+ return JSON.parse(this.wasm.SetTableRowOptions(this.handle, cellAnchorId, options.repeatHeader ?? null, options.allowBreakAcrossPages ?? null, options.heightTwips ?? null, options.heightRule ?? "atLeast"));
568
+ }
199
569
  // ─── Headers / footers / page numbers ────────────────────────────────
200
570
  /**
201
571
  * Set the running header story for the section that owns `anchorId` (any body block in that
@@ -242,6 +612,37 @@ export class DocxSession {
242
612
  setPageNumbering(anchorId, op) {
243
613
  return JSON.parse(this.wasm.SetPageNumbering(this.handle, anchorId, JSON.stringify(op)));
244
614
  }
615
+ /**
616
+ * Insert a **table of contents** before or after `anchorId` (issue #607). The field is written
617
+ * dirty and the document asks for a field update on open, so Word paginates and fills the table
618
+ * itself — nothing here ships a cached result that is stale the moment anything above it moves.
619
+ *
620
+ * The table is wrapped in the `w:sdt` content control Word puts around one, which is what gives
621
+ * it the *Update Table* control in Word's UI. Word's `TOCHeading` and `TOC1` styles are
622
+ * find-or-created; a document that already defines them keeps its own.
623
+ *
624
+ * Body anchors only, and refused under tracked-change recording: a generated table is regenerated
625
+ * wholesale on every field update, so there is no reversible way to redline it.
626
+ */
627
+ insertTableOfContents(anchorId, pos = "before", options) {
628
+ return JSON.parse(this.wasm.InsertTableOfContents(this.handle, anchorId, pos, options ? JSON.stringify(options) : ""));
629
+ }
630
+ /**
631
+ * Insert a **table of figures** — the captions carrying `options.captionLabel` and their page
632
+ * numbers. Same field mechanics as {@link insertTableOfContents}; Word writes this one as a bare
633
+ * paragraph rather than inside a content control, so this does too.
634
+ */
635
+ insertTableOfFigures(anchorId, pos = "before", options) {
636
+ return JSON.parse(this.wasm.InsertTableOfFigures(this.handle, anchorId, pos, options ? JSON.stringify(options) : ""));
637
+ }
638
+ /**
639
+ * Insert a **table of authorities** — the cases, statutes or other authorities marked in the
640
+ * document, grouped by `options.category`. The table lists entries the document has MARKED with
641
+ * `TA` fields; a document with no marked citations produces a table that is correct and empty.
642
+ */
643
+ insertTableOfAuthorities(anchorId, pos = "before", options) {
644
+ return JSON.parse(this.wasm.InsertTableOfAuthorities(this.handle, anchorId, pos, options ? JSON.stringify(options) : ""));
645
+ }
245
646
  /**
246
647
  * Remove the section's page-numbering start/format: it reverts to continuing the previous
247
648
  * section's numbering in Word's default `1, 2, 3`. Chapter-numbering attributes
@@ -284,6 +685,17 @@ export class DocxSession {
284
685
  insertFootnote(anchorId, characterOffset, markdown) {
285
686
  return JSON.parse(this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown));
286
687
  }
688
+ /**
689
+ * Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
690
+ * bookmark — at a character offset (issue #545). The field carries a cached result run
691
+ * (the bookmarked text, or the target's auto-number under `referenceNumber`), so
692
+ * renderers that do not recompute fields show a faithful snapshot and Word updates it
693
+ * like a hand-authored cross-reference. A missing or incoherent bookmark fails with
694
+ * `missing_bookmark_target`.
695
+ */
696
+ insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
697
+ return JSON.parse(this.wasm.InsertCrossReference(this.handle, anchorId, characterOffset, bookmarkName, options ? JSON.stringify(options) : ""));
698
+ }
287
699
  /** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
288
700
  * `w:endnoteReference`, and the created definition anchor has kind `en`. */
289
701
  insertEndnote(anchorId, characterOffset, markdown) {
@@ -343,6 +755,87 @@ export class DocxSession {
343
755
  listComments() {
344
756
  return JSON.parse(this.wasm.ListComments(this.handle));
345
757
  }
758
+ listHyperlinks(scopes = ProjectionScopes.All) {
759
+ return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
760
+ }
761
+ addHyperlink(anchorId, span, kind, target) {
762
+ return JSON.parse(this.wasm.AddHyperlink(this.handle, anchorId, span.start, span.length, kind, target));
763
+ }
764
+ updateHyperlink(hyperlinkId, kind, target) {
765
+ return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
766
+ }
767
+ removeHyperlink(hyperlinkId) {
768
+ return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
769
+ }
770
+ /** Versioned operational facts for native image inspection/mutation in this runtime. */
771
+ getImageCapabilities() {
772
+ return JSON.parse(this.wasm.GetImageCapabilities());
773
+ }
774
+ listImages(scopes = ProjectionScopes.All) {
775
+ return JSON.parse(this.wasm.ListImages(this.handle, scopes));
776
+ }
777
+ insertImage(anchorId, characterOffset, bytes, options = {}) {
778
+ return JSON.parse(this.wasm.InsertImage(this.handle, anchorId, characterOffset, imageBytesToBase64(bytes), JSON.stringify(options)));
779
+ }
780
+ replaceImage(imageId, bytes) {
781
+ return JSON.parse(this.wasm.ReplaceImage(this.handle, imageId, imageBytesToBase64(bytes)));
782
+ }
783
+ setImageDimensions(imageId, dimensions) {
784
+ return JSON.parse(this.wasm.SetImageDimensions(this.handle, imageId, JSON.stringify(dimensions)));
785
+ }
786
+ setImageMetadata(imageId, altText, title) {
787
+ return JSON.parse(this.wasm.SetImageMetadata(this.handle, imageId, altText, title));
788
+ }
789
+ setImageFloatingLayout(imageId, layout) {
790
+ return JSON.parse(this.wasm.SetImageFloatingLayout(this.handle, imageId, JSON.stringify(layout)));
791
+ }
792
+ removeImage(imageId) {
793
+ return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
794
+ }
795
+ /** Native Word structured-document tags, in outer-before-inner story order. */
796
+ listContentControls(scopes = ProjectionScopes.All) {
797
+ return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
798
+ }
799
+ fillContentControlText(anchorId, text, options = {}) {
800
+ return JSON.parse(this.wasm.FillContentControlText(this.handle, anchorId, text, JSON.stringify(options)));
801
+ }
802
+ fillContentControlRichText(anchorId, markdown, options = {}) {
803
+ return JSON.parse(this.wasm.FillContentControlRichText(this.handle, anchorId, markdown, JSON.stringify(options)));
804
+ }
805
+ setContentControlChecked(anchorId, isChecked, options = {}) {
806
+ return JSON.parse(this.wasm.SetContentControlChecked(this.handle, anchorId, isChecked, JSON.stringify(options)));
807
+ }
808
+ setContentControlDate(anchorId, value, displayText, options = {}) {
809
+ const timestamp = value instanceof Date ? value.toISOString() : value;
810
+ return JSON.parse(this.wasm.SetContentControlDate(this.handle, anchorId, timestamp, displayText ?? null, JSON.stringify(options)));
811
+ }
812
+ selectContentControlItem(anchorId, value, options = {}) {
813
+ return JSON.parse(this.wasm.SelectContentControlItem(this.handle, anchorId, value, JSON.stringify(options)));
814
+ }
815
+ fillContentControlPicture(anchorId, bytes, options = {}) {
816
+ return JSON.parse(this.wasm.FillContentControlPicture(this.handle, anchorId, imageBytesToBase64(bytes), JSON.stringify(options)));
817
+ }
818
+ addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
819
+ return JSON.parse(this.wasm.AddRepeatingSectionItem(this.handle, sectionAnchorId, afterItemAnchorId ?? "", JSON.stringify(options)));
820
+ }
821
+ removeRepeatingSectionItem(itemAnchorId) {
822
+ return JSON.parse(this.wasm.RemoveRepeatingSectionItem(this.handle, itemAnchorId));
823
+ }
824
+ listBookmarks(scopes = ProjectionScopes.All) {
825
+ return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
826
+ }
827
+ addBookmark(name, range) {
828
+ return JSON.parse(this.wasm.AddBookmark(this.handle, name, range.startAnchorId, range.startOffset, range.endAnchorId, range.endOffset));
829
+ }
830
+ renameBookmark(name, newName) {
831
+ return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
832
+ }
833
+ moveBookmark(name, range) {
834
+ return JSON.parse(this.wasm.MoveBookmark(this.handle, name, range.startAnchorId, range.startOffset, range.endAnchorId, range.endOffset));
835
+ }
836
+ removeBookmark(name) {
837
+ return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
838
+ }
346
839
  // ─── Tracked revisions (issue #318) ──────────────────────────────────
347
840
  /** Markup-native tracked-revision listing, in document order across body, headers,
348
841
  * footers, footnotes, and endnotes. Ids are stable while the underlying markup
@@ -363,6 +856,22 @@ export class DocxSession {
363
856
  rejectRevision(revisionId) {
364
857
  return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
365
858
  }
859
+ /**
860
+ * Accept every live revision as one undoable session mutation.
861
+ *
862
+ * Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
863
+ * operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
864
+ * mutated. There is no force mode — call {@link listRevisions} and read each entry's
865
+ * `diagnostic` to see what blocks it.
866
+ */
867
+ acceptAllRevisions() {
868
+ return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
869
+ }
870
+ /** Reject every live revision as one undoable session mutation. Fails closed exactly like
871
+ * {@link acceptAllRevisions}. */
872
+ rejectAllRevisions() {
873
+ return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
874
+ }
366
875
  // ─── Tier C: formatting ──────────────────────────────────────────────
367
876
  applyFormat(anchorId, span, op) {
368
877
  const spanJson = span ? JSON.stringify(span) : "";
@@ -462,6 +971,10 @@ export class DocxSession {
462
971
  * replaces it with `replace`, preserving the surrounding run formatting that
463
972
  * the match didn't touch. Returns one `EditResult` per attempted match.
464
973
  *
974
+ * A `find` that matches nothing fails with a single `text_not_found` result
975
+ * naming the anchor and the needle — never an empty array. Pass
976
+ * `expectedMatchCount: 0` to assert absence as a successful no-op instead.
977
+ *
465
978
  * Run-formatting contract: the replacement text inherits the formatting of
466
979
  * the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
467
980
  * but lose the slice of text the match consumed.
@@ -635,8 +1148,11 @@ export class DocxSession {
635
1148
  *
636
1149
  * @see docs/architecture/docx_mutation_api.md#findplaceholders
637
1150
  */
638
- findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
639
- return JSON.parse(this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary));
1151
+ findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
1152
+ const json = citation
1153
+ ? this.wasm.FindPlaceholdersWithCitations(this.handle, kinds, scope, contextChars, boundary, JSON.stringify(citation))
1154
+ : this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
1155
+ return JSON.parse(json);
640
1156
  }
641
1157
  /**
642
1158
  * Returns a snapshot of edit-state introspection signals — placeholder counts,
@@ -659,6 +1175,21 @@ export class DocxSession {
659
1175
  }
660
1176
  return raw;
661
1177
  }
1178
+ /**
1179
+ * Return stable semantic changes from the package opened for this session to
1180
+ * its current state. Requires `captureInitialProjection` (enabled by default).
1181
+ */
1182
+ getSemanticChanges() {
1183
+ return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
1184
+ }
1185
+ /**
1186
+ * Run the default deliverable gate on this session's normal clean-save checkpoint.
1187
+ * With initial projection capture enabled (the default), exact opening bytes are the
1188
+ * baseline used for dispositions and semantic/package deltas.
1189
+ */
1190
+ verifyDeliverable() {
1191
+ return JSON.parse(this.wasm.VerifyDeliverable(this.handle));
1192
+ }
662
1193
  // ─── Annotation-based anchor discovery (#132) ────────────────────────
663
1194
  /**
664
1195
  * Resolves an annotation's range to the block-level markdown anchors covering
@@ -674,8 +1205,11 @@ export class DocxSession {
674
1205
  *
675
1206
  * @see docs/architecture/docx_mutation_api.md#findbyannotation
676
1207
  */
677
- findByAnnotation(annotationId) {
678
- return JSON.parse(this.wasm.FindByAnnotation(this.handle, annotationId));
1208
+ findByAnnotation(annotationId, citation) {
1209
+ const json = citation
1210
+ ? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation))
1211
+ : this.wasm.FindByAnnotation(this.handle, annotationId);
1212
+ return JSON.parse(json);
679
1213
  }
680
1214
  /**
681
1215
  * Finds every annotation whose `labelId` matches and resolves each of their
@@ -684,8 +1218,11 @@ export class DocxSession {
684
1218
  * annotations on different paragraphs become three entries). Annotations
685
1219
  * whose bookmark resolves to no anchors are omitted from the result.
686
1220
  */
687
- findByLabel(labelId) {
688
- return JSON.parse(this.wasm.FindByLabel(this.handle, labelId));
1221
+ findByLabel(labelId, citation) {
1222
+ const json = citation
1223
+ ? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation))
1224
+ : this.wasm.FindByLabel(this.handle, labelId);
1225
+ return JSON.parse(json);
689
1226
  }
690
1227
  /**
691
1228
  * Resolves any bookmark in the main document part (Docxodus-managed or
@@ -693,8 +1230,11 @@ export class DocxSession {
693
1230
  * order. Empty when the bookmark name is unknown. Use this for raw bookmark
694
1231
  * names that didn't come from the annotation system.
695
1232
  */
696
- findByBookmark(bookmarkName) {
697
- return JSON.parse(this.wasm.FindByBookmark(this.handle, bookmarkName));
1233
+ findByBookmark(bookmarkName, citation) {
1234
+ const json = citation
1235
+ ? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation))
1236
+ : this.wasm.FindByBookmark(this.handle, bookmarkName);
1237
+ return JSON.parse(json);
698
1238
  }
699
1239
  // ─── Text/kind-based anchor discovery (#171) ─────────────────────────
700
1240
  /**
@@ -733,13 +1273,22 @@ export class DocxSession {
733
1273
  return JSON.parse(this.wasm.FindByRegex(this.handle, pattern, regexOptions, options ? JSON.stringify(options) : ""));
734
1274
  }
735
1275
  /**
736
- * Return every anchor of the given `kind` (`"p"`, `"h"`, `"li"`, `"tbl"`,
737
- * `"row"`, `"cell"`, …), in document order. Reads the projection's anchor
1276
+ * Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
1277
+ * `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
1278
+ * `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
1279
+ * in particular the row and cell tokens are `"tr"` and `"tc"`, never
1280
+ * `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
1281
+ * return an empty array. `"img"` and `"drw"` likewise return empty because the
1282
+ * projection never assigns those reserved kinds; address images through the
1283
+ * image surface instead. Reads the projection's anchor
738
1284
  * index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
739
1285
  * a single part; omit it to span all scopes.
740
1286
  */
741
- findByKind(kind, scope) {
742
- return JSON.parse(this.wasm.FindByKind(this.handle, kind, scope ?? ""));
1287
+ findByKind(kind, scope, citation) {
1288
+ const json = citation
1289
+ ? this.wasm.FindByKindWithCitations(this.handle, kind, scope ?? "", JSON.stringify(citation))
1290
+ : this.wasm.FindByKind(this.handle, kind, scope ?? "");
1291
+ return JSON.parse(json);
743
1292
  }
744
1293
  /**
745
1294
  * Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
@@ -793,6 +1342,18 @@ export class DocxSession {
793
1342
  const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
794
1343
  return JSON.parse(raw);
795
1344
  }
1345
+ /** Enumerate the document's explicit style catalog with resolved high-signal properties. */
1346
+ listStyles() {
1347
+ return JSON.parse(this.wasm.ListStyles(this.handle));
1348
+ }
1349
+ /** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
1350
+ getFormatting(anchorId) {
1351
+ return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
1352
+ }
1353
+ /** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
1354
+ listInlineSpans(anchorId) {
1355
+ return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
1356
+ }
796
1357
  /**
797
1358
  * Enumerates every annotation persisted in the document. Lets an agent prime
798
1359
  * itself with "here are the labeled regions you can target" before committing
@@ -851,6 +1412,13 @@ export class DocxSession {
851
1412
  this.close();
852
1413
  }
853
1414
  }
1415
+ function imageBytesToBase64(bytes) {
1416
+ let binary = "";
1417
+ for (let offset = 0; offset < bytes.length; offset += 0x8000) {
1418
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
1419
+ }
1420
+ return globalThis.btoa(binary);
1421
+ }
854
1422
  /**
855
1423
  * Opens a new {@link DocxSession} over the supplied DOCX bytes.
856
1424
  * The returned session holds its document in WASM memory until you call