@beyondwork/docx-react-component 1.0.41 → 1.0.43

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 (118) hide show
  1. package/package.json +38 -37
  2. package/src/api/awareness-identity-types.ts +35 -0
  3. package/src/api/comment-negotiation-types.ts +130 -0
  4. package/src/api/comment-presentation-types.ts +106 -0
  5. package/src/api/editor-state-types.ts +110 -0
  6. package/src/api/external-custody-types.ts +74 -0
  7. package/src/api/participants-types.ts +18 -0
  8. package/src/api/public-types.ts +541 -5
  9. package/src/api/scope-metadata-resolver-types.ts +88 -0
  10. package/src/core/commands/formatting-commands.ts +1 -1
  11. package/src/core/commands/index.ts +601 -9
  12. package/src/core/search/search-text.ts +15 -2
  13. package/src/index.ts +131 -1
  14. package/src/io/docx-session.ts +672 -2
  15. package/src/io/export/escape-xml-attribute.ts +26 -0
  16. package/src/io/export/external-send.ts +188 -0
  17. package/src/io/export/serialize-comments.ts +13 -16
  18. package/src/io/export/serialize-footnotes.ts +17 -24
  19. package/src/io/export/serialize-headers-footers.ts +17 -24
  20. package/src/io/export/serialize-main-document.ts +59 -62
  21. package/src/io/export/serialize-numbering.ts +20 -27
  22. package/src/io/export/serialize-runtime-revisions.ts +2 -9
  23. package/src/io/export/serialize-tables.ts +8 -15
  24. package/src/io/export/table-properties-xml.ts +25 -32
  25. package/src/io/import/external-reimport.ts +40 -0
  26. package/src/io/load-scheduler.ts +230 -0
  27. package/src/io/normalize/normalize-text.ts +83 -0
  28. package/src/io/ooxml/bw-xml.ts +244 -0
  29. package/src/io/ooxml/canonicalize-payload.ts +301 -0
  30. package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
  31. package/src/io/ooxml/comment-presentation-payload.ts +311 -0
  32. package/src/io/ooxml/external-custody-payload.ts +102 -0
  33. package/src/io/ooxml/participants-payload.ts +97 -0
  34. package/src/io/ooxml/payload-signature.ts +112 -0
  35. package/src/io/ooxml/workflow-payload-validator.ts +367 -0
  36. package/src/io/ooxml/workflow-payload.ts +317 -7
  37. package/src/runtime/awareness-identity.ts +173 -0
  38. package/src/runtime/collab/event-types.ts +27 -0
  39. package/src/runtime/collab-session-bridge.ts +157 -0
  40. package/src/runtime/collab-session-facet.ts +193 -0
  41. package/src/runtime/collab-session.ts +273 -0
  42. package/src/runtime/comment-negotiation-sync.ts +91 -0
  43. package/src/runtime/comment-negotiation.ts +158 -0
  44. package/src/runtime/comment-presentation.ts +223 -0
  45. package/src/runtime/document-runtime.ts +639 -124
  46. package/src/runtime/editor-state-channel.ts +544 -0
  47. package/src/runtime/editor-state-integration.ts +217 -0
  48. package/src/runtime/external-send-runtime.ts +117 -0
  49. package/src/runtime/layout/docx-font-loader.ts +11 -30
  50. package/src/runtime/layout/index.ts +2 -0
  51. package/src/runtime/layout/inert-layout-facet.ts +4 -0
  52. package/src/runtime/layout/layout-engine-instance.ts +139 -14
  53. package/src/runtime/layout/page-graph.ts +79 -7
  54. package/src/runtime/layout/paginated-layout-engine.ts +441 -48
  55. package/src/runtime/layout/public-facet.ts +585 -14
  56. package/src/runtime/layout/table-row-split.ts +316 -0
  57. package/src/runtime/markdown-sanitizer.ts +132 -0
  58. package/src/runtime/participants.ts +134 -0
  59. package/src/runtime/perf-counters.ts +28 -0
  60. package/src/runtime/render/render-frame-types.ts +17 -0
  61. package/src/runtime/render/render-kernel.ts +172 -29
  62. package/src/runtime/resign-payload.ts +120 -0
  63. package/src/runtime/surface-projection.ts +10 -5
  64. package/src/runtime/tamper-gate.ts +157 -0
  65. package/src/runtime/workflow-markup.ts +80 -16
  66. package/src/runtime/workflow-rail-segments.ts +244 -5
  67. package/src/ui/WordReviewEditor.tsx +654 -45
  68. package/src/ui/editor-command-bag.ts +14 -0
  69. package/src/ui/editor-runtime-boundary.ts +111 -11
  70. package/src/ui/editor-shell-view.tsx +21 -0
  71. package/src/ui/editor-surface-controller.tsx +5 -0
  72. package/src/ui/headless/selection-helpers.ts +10 -0
  73. package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
  74. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
  75. package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
  76. package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
  77. package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
  78. package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
  79. package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
  80. package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
  81. package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
  82. package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
  83. package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
  84. package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
  85. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
  86. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
  87. package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
  88. package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
  89. package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
  90. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +106 -0
  91. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
  92. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
  93. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
  94. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
  95. package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
  96. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
  97. package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
  98. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
  99. package/src/ui-tailwind/editor-surface/pm-schema.ts +167 -17
  100. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +35 -7
  101. package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
  102. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
  103. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +10 -256
  104. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -0
  105. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
  106. package/src/ui-tailwind/index.ts +37 -1
  107. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
  108. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
  109. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
  110. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
  111. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
  112. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
  113. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
  114. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
  115. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
  116. package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
  117. package/src/ui-tailwind/theme/editor-theme.css +25 -0
  118. package/src/ui-tailwind/tw-review-workspace.tsx +455 -118
@@ -39,7 +39,11 @@ import {
39
39
  type ParsedInlineNode,
40
40
  type ParsedPermStartInlineNode,
41
41
  } from "./ooxml/parse-main-document.ts";
42
- import { normalizeParsedTextDocument } from "./normalize/normalize-text.ts";
42
+ import {
43
+ normalizeParsedTextDocument,
44
+ normalizeParsedTextDocumentAsync,
45
+ } from "./normalize/normalize-text.ts";
46
+ import { type LoadScheduler } from "./load-scheduler.ts";
43
47
  import {
44
48
  CONTENT_TYPES_PATH,
45
49
  PACKAGE_RELATIONSHIPS_PATH,
@@ -213,6 +217,17 @@ interface LoadDocxEditorSessionOptions {
213
217
  sourceLabel?: string;
214
218
  bytes: Uint8Array | ArrayBuffer;
215
219
  editorBuild?: string;
220
+ /**
221
+ * Fastload P2: optional instrumentation callback invoked once per
222
+ * completed load stage. Stage ordering: opc → body →
223
+ * styles-numbering-comments → skeleton-ready. `durationMs` is
224
+ * measured against `performance.now()` boundaries inside the loader.
225
+ *
226
+ * Pure instrumentation — has no effect on returned session shape.
227
+ * Safe to leave undefined; existing callers observe no behavior
228
+ * change.
229
+ */
230
+ onLoadStage?: (stage: import("../api/public-types.ts").LoadStage, durationMs: number) => void;
216
231
  }
217
232
 
218
233
  export interface LoadedDocxEditorSession {
@@ -225,6 +240,8 @@ export interface LoadedDocxEditorSession {
225
240
  sessionState: EditorSessionState | PersistedEditorSnapshot,
226
241
  options?: ExportDocxOptions,
227
242
  ) => Promise<ExportResult>;
243
+ /** Schema 1.2 — editorState block parsed from the 1.2 payload, if present. */
244
+ initialEditorStatePayload?: import("./ooxml/workflow-payload.ts").EditorStatePayload;
228
245
  }
229
246
 
230
247
  interface ImportedDocxState {
@@ -277,6 +294,37 @@ const BLOCKING_COMMENT_DIAGNOSTIC_CODES = new Set<CommentImportDiagnostic["code"
277
294
  "preserve_only_revision_overlap",
278
295
  ]);
279
296
 
297
+ interface StageEmitter {
298
+ emit(stage: import("../api/public-types.ts").LoadStage): void;
299
+ }
300
+
301
+ /**
302
+ * Fastload P2 (hoisted in P6): build a stage-event emitter. When no callback
303
+ * is supplied the emitter is a zero-cost no-op. Shared between the sync and
304
+ * async load orchestrators so both paths emit `load-stage` events at the
305
+ * same four boundaries: `opc` → `body` → `styles-numbering-comments` →
306
+ * `skeleton-ready`.
307
+ */
308
+ function createStageEmitter(
309
+ onStage: LoadDocxEditorSessionOptions["onLoadStage"],
310
+ ): StageEmitter {
311
+ if (!onStage) {
312
+ return {
313
+ emit() {
314
+ /* no-op */
315
+ },
316
+ };
317
+ }
318
+ let cursor = loadStageNow();
319
+ return {
320
+ emit(stage) {
321
+ const nextMark = loadStageNow();
322
+ onStage(stage, nextMark - cursor);
323
+ cursor = nextMark;
324
+ },
325
+ };
326
+ }
327
+
280
328
  export function loadDocxEditorSession(
281
329
  options: LoadDocxEditorSessionOptions,
282
330
  ): LoadedDocxEditorSession {
@@ -285,6 +333,9 @@ export function loadDocxEditorSession(
285
333
  ? options.editorBuild
286
334
  : "dev";
287
335
  const sourceBytes = toUint8Array(options.bytes);
336
+
337
+ const stages = createStageEmitter(options.onLoadStage);
338
+
288
339
  let sourcePackage: OpcPackage;
289
340
 
290
341
  try {
@@ -297,6 +348,7 @@ export function loadDocxEditorSession(
297
348
  }),
298
349
  );
299
350
  }
351
+ stages.emit("opc");
300
352
  const embeddedWorkflowPayload = parseWorkflowPayloadEnvelopeFromPackage(sourcePackage);
301
353
  const embeddedWorkflowMetadata = embeddedWorkflowPayload?.workflowMetadata;
302
354
  const embeddedWorkflowOverlay = embeddedWorkflowPayload?.workflowOverlay;
@@ -376,6 +428,7 @@ export function loadDocxEditorSession(
376
428
  parsedDocument,
377
429
  mainDocumentPath,
378
430
  );
431
+ stages.emit("body");
379
432
  const commentsPartPath = resolveCommentsPartPath(
380
433
  sourcePackage,
381
434
  mainDocumentPath,
@@ -438,6 +491,7 @@ export function loadDocxEditorSession(
438
491
  normalizedDocument.preservation.opaqueFragments,
439
492
  normalizedRevisions.revisions,
440
493
  );
494
+ stages.emit("styles-numbering-comments");
441
495
  const importedStoryRevisions: ReviewRevisionRecord[] = [];
442
496
  const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
443
497
  const subPartOpaqueState = createSubPartOpaqueImportState(
@@ -813,6 +867,7 @@ export function loadDocxEditorSession(
813
867
  },
814
868
  };
815
869
 
870
+ stages.emit("skeleton-ready");
816
871
  return {
817
872
  initialSessionState,
818
873
  initialSnapshot: snapshot,
@@ -820,6 +875,9 @@ export function loadDocxEditorSession(
820
875
  protectionSnapshot: importedProtectionSnapshot,
821
876
  exportDocx: async (nextSessionState, exportOptions) =>
822
877
  exportDocxEditorSession(importedState, nextSessionState, exportOptions),
878
+ ...(embeddedWorkflowPayload?.editorState
879
+ ? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
880
+ : {}),
823
881
  };
824
882
  } catch (error) {
825
883
  return createDiagnosticsSession(
@@ -829,6 +887,614 @@ export function loadDocxEditorSession(
829
887
  }
830
888
  }
831
889
 
890
+ interface LoadDocxEditorSessionAsyncOptions extends LoadDocxEditorSessionOptions {
891
+ /**
892
+ * Scheduler that the async loader awaits between parse stages. Callers
893
+ * in DOM environments should construct this with `createLoadScheduler()`
894
+ * (auto-detects scheduler.yield → MessageChannel → setTimeout backend).
895
+ * Callers in Node / SSR should pass
896
+ * `createLoadScheduler({ backendOverride: "sync" })` — sync backend
897
+ * yields resolve without task-boundary latency, so the async path
898
+ * behaves like the sync path from the test harness POV.
899
+ */
900
+ scheduler: LoadScheduler;
901
+ }
902
+
903
+ /**
904
+ * Fastload P6: async sibling of {@link loadDocxEditorSession} that yields to
905
+ * the browser between parse stages. Parse sequence is byte-equivalent to
906
+ * the sync path (asserted on every F*.docx fixture in
907
+ * `test/io/fastload-parity.test.ts`). Yields fire at:
908
+ * 1. after OPC read,
909
+ * 2. after `parseMainDocumentXml`,
910
+ * 3. between every header/footer sub-part parse,
911
+ * 4. after footnotes/endnotes parse,
912
+ * 5. after theme + settings + styles parse,
913
+ * 6. after `buildCompatibilityReport`,
914
+ * plus mid-walk yields every 256 blocks inside
915
+ * `normalizeParsedTextDocumentAsync` (stage 3 — body normalize).
916
+ *
917
+ * Sync `loadDocxEditorSession` remains the only entry point for Node tests
918
+ * and SSR. The DOM boundary in `editor-runtime-boundary.ts` calls this
919
+ * async path so the browser can paint the skeleton mid-parse.
920
+ */
921
+ export async function loadDocxEditorSessionAsync(
922
+ options: LoadDocxEditorSessionAsyncOptions,
923
+ ): Promise<LoadedDocxEditorSession> {
924
+ const { scheduler } = options;
925
+ const editorBuild =
926
+ typeof options.editorBuild === "string" && options.editorBuild.length > 0
927
+ ? options.editorBuild
928
+ : "dev";
929
+ const sourceBytes = toUint8Array(options.bytes);
930
+
931
+ const stages = createStageEmitter(options.onLoadStage);
932
+
933
+ let sourcePackage: OpcPackage;
934
+
935
+ try {
936
+ sourcePackage = readOpcPackage(sourceBytes);
937
+ } catch (error) {
938
+ return createDiagnosticsSession(
939
+ options,
940
+ createPackageImportDiagnostics({
941
+ issue: classifyCorruptPackageError(error),
942
+ }),
943
+ );
944
+ }
945
+ stages.emit("opc");
946
+ await scheduler.yield();
947
+ const embeddedWorkflowPayload = parseWorkflowPayloadEnvelopeFromPackage(sourcePackage);
948
+ const embeddedWorkflowMetadata = embeddedWorkflowPayload?.workflowMetadata;
949
+ const embeddedWorkflowOverlay = embeddedWorkflowPayload?.workflowOverlay;
950
+
951
+ const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
952
+ const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
953
+ sourcePackage,
954
+ mainDocumentPath,
955
+ );
956
+ if (brokenRelationshipIssues.length > 0) {
957
+ return createDiagnosticsSession(
958
+ options,
959
+ createPackageImportDiagnostics({
960
+ issue: {
961
+ ...brokenRelationshipIssues[0],
962
+ message: summarizeBrokenRelationshipIssues(brokenRelationshipIssues),
963
+ details: {
964
+ issueCount: brokenRelationshipIssues.length,
965
+ targets: brokenRelationshipIssues.map((issue) => issue.targetPartPath).filter(Boolean),
966
+ },
967
+ },
968
+ }),
969
+ );
970
+ }
971
+
972
+ if (!mainDocumentPath) {
973
+ return createDiagnosticsSession(
974
+ options,
975
+ createPackageImportDiagnostics({
976
+ issue: createMissingPartIssue(MAIN_DOCUMENT_PATH),
977
+ }),
978
+ );
979
+ }
980
+
981
+ const documentPart = sourcePackage.parts.get(mainDocumentPath);
982
+ if (!documentPart) {
983
+ return createDiagnosticsSession(
984
+ options,
985
+ createPackageImportDiagnostics({
986
+ issue: createMissingPartIssue(mainDocumentPath),
987
+ }),
988
+ );
989
+ }
990
+ if (documentPart.contentType !== MAIN_DOCUMENT_CONTENT_TYPE) {
991
+ return createDiagnosticsSession(
992
+ options,
993
+ createValidationImportDiagnostics({
994
+ message: `DOCX main document part ${mainDocumentPath} must use content type ${MAIN_DOCUMENT_CONTENT_TYPE}.`,
995
+ }),
996
+ );
997
+ }
998
+
999
+ try {
1000
+ const sourceDocumentXml = decodeUtf8(documentPart.bytes);
1001
+ const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
1002
+ const numberingPartPath = resolveDocumentRelatedPartPath(
1003
+ sourcePackage,
1004
+ mainDocumentPath,
1005
+ documentPart.relationships,
1006
+ NUMBERING_RELATIONSHIP_TYPE,
1007
+ NUMBERING_PART_PATH,
1008
+ );
1009
+ const parsedNumbering = numberingPartPath
1010
+ ? parseNumberingXml(
1011
+ decodeUtf8(sourcePackage.parts.get(numberingPartPath)?.bytes ?? new Uint8Array()),
1012
+ )
1013
+ : createEmptyNumberingCatalog();
1014
+ const mediaParts = collectInlineMediaParts(sourcePackage);
1015
+ const parsedDocument = parseMainDocumentXml(
1016
+ sourceDocumentXml,
1017
+ documentPart.relationships,
1018
+ mediaParts,
1019
+ mainDocumentPath,
1020
+ );
1021
+ await scheduler.yield();
1022
+ const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
1023
+ const normalizedDocument = await normalizeParsedTextDocumentAsync(
1024
+ parsedDocument,
1025
+ mainDocumentPath,
1026
+ scheduler,
1027
+ );
1028
+ stages.emit("body");
1029
+ await scheduler.yield();
1030
+ const commentsPartPath = resolveCommentsPartPath(
1031
+ sourcePackage,
1032
+ mainDocumentPath,
1033
+ documentPart.relationships,
1034
+ );
1035
+ const commentsExtendedPartPath = resolveDocumentRelatedPartPath(
1036
+ sourcePackage,
1037
+ mainDocumentPath,
1038
+ documentPart.relationships,
1039
+ COMMENTS_EXTENDED_RELATIONSHIP_TYPE,
1040
+ COMMENTS_EXTENDED_PART_PATH,
1041
+ );
1042
+ const commentsIdsPartPath = resolveDocumentRelatedPartPath(
1043
+ sourcePackage,
1044
+ mainDocumentPath,
1045
+ documentPart.relationships,
1046
+ COMMENTS_IDS_RELATIONSHIP_TYPE,
1047
+ COMMENTS_IDS_PART_PATH,
1048
+ );
1049
+ const peoplePartPath = resolveDocumentRelatedPartPath(
1050
+ sourcePackage,
1051
+ mainDocumentPath,
1052
+ documentPart.relationships,
1053
+ PEOPLE_RELATIONSHIP_TYPE,
1054
+ PEOPLE_PART_PATH,
1055
+ );
1056
+ const parsedComments = commentsPartPath
1057
+ ? parseCommentsFromOoxml(
1058
+ sourceDocumentXml,
1059
+ {
1060
+ commentsXml: decodeUtf8(sourcePackage.parts.get(commentsPartPath)?.bytes ?? new Uint8Array()),
1061
+ commentsExtendedXml: decodeUtf8(
1062
+ sourcePackage.parts.get(commentsExtendedPartPath ?? "")?.bytes ?? new Uint8Array(),
1063
+ ),
1064
+ commentsIdsXml: decodeUtf8(
1065
+ sourcePackage.parts.get(commentsIdsPartPath ?? "")?.bytes ?? new Uint8Array(),
1066
+ ),
1067
+ peopleXml: decodeUtf8(
1068
+ sourcePackage.parts.get(peoplePartPath ?? "")?.bytes ?? new Uint8Array(),
1069
+ ),
1070
+ },
1071
+ )
1072
+ : {
1073
+ threads: [] as CommentThread[],
1074
+ diagnostics: [] as CommentImportDiagnostic[],
1075
+ definitions: [] as ImportedCommentDefinition[],
1076
+ sourceRootTag: undefined,
1077
+ sourceExtendedRootTag: undefined,
1078
+ sourceIdsRootTag: undefined,
1079
+ sourcePeopleRootTag: undefined,
1080
+ peopleAuthors: [] as string[],
1081
+ };
1082
+ const normalizedRevisions = normalizeImportedRevisionRecords(
1083
+ importedRevisions,
1084
+ normalizedDocument.content,
1085
+ normalizedDocument.preservation.opaqueFragments,
1086
+ );
1087
+ const normalizedComments = normalizeImportedCommentThreads(
1088
+ parsedComments,
1089
+ normalizedDocument.preservation.opaqueFragments,
1090
+ normalizedRevisions.revisions,
1091
+ );
1092
+ stages.emit("styles-numbering-comments");
1093
+ const importedStoryRevisions: ReviewRevisionRecord[] = [];
1094
+ const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
1095
+ const subPartOpaqueState = createSubPartOpaqueImportState(
1096
+ normalizedDocument.preservation.opaqueFragments,
1097
+ normalizedDocument.diagnostics.warnings,
1098
+ );
1099
+ // ---- Parse sub-parts: headers, footers, footnotes, endnotes, theme ----
1100
+ const headerFooterRefs = parseHeaderFooterReferences(sourceDocumentXml);
1101
+ const parsedHeaders: HeaderDocument[] = [];
1102
+ const parsedFooters: FooterDocument[] = [];
1103
+ const sourceHeaderPaths: Array<{ partPath: string; relationshipId: string }> = [];
1104
+ const sourceFooterPaths: Array<{ partPath: string; relationshipId: string }> = [];
1105
+ const seenSubPartKeys = new Set<string>();
1106
+
1107
+ for (const ref of headerFooterRefs) {
1108
+ const dedupeKey = `${ref.kind}:${ref.variant}:${ref.relationshipId}`;
1109
+ if (seenSubPartKeys.has(dedupeKey)) {
1110
+ continue;
1111
+ }
1112
+ seenSubPartKeys.add(dedupeKey);
1113
+
1114
+ const relationship = documentPart.relationships.find(
1115
+ (r) => r.id === ref.relationshipId && r.targetMode === "internal",
1116
+ );
1117
+ if (!relationship) {
1118
+ continue;
1119
+ }
1120
+
1121
+ const partPath = resolveRelationshipTarget(mainDocumentPath, relationship);
1122
+ const partBytes = sourcePackage.parts.get(partPath)?.bytes;
1123
+ if (!partBytes) {
1124
+ continue;
1125
+ }
1126
+
1127
+ await scheduler.yield();
1128
+ const xml = decodeUtf8(partBytes);
1129
+ if (ref.kind === "header") {
1130
+ const parsedHeaderRevisions = parseRevisionsFromStoryXml(xml);
1131
+ const parsed = parseHeaderXml(xml);
1132
+ parsedHeaders.push({
1133
+ variant: ref.variant,
1134
+ partPath,
1135
+ relationshipId: ref.relationshipId,
1136
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1137
+ blocks: normalizeSubPartOpaqueBlocks(
1138
+ parsed.blocks,
1139
+ normalizedDocument.preservation.opaqueFragments,
1140
+ normalizedDocument.diagnostics.warnings,
1141
+ partPath,
1142
+ subPartOpaqueState,
1143
+ ),
1144
+ });
1145
+ importedStoryRevisions.push(
1146
+ ...parsedHeaderRevisions.revisions.map((revision): ReviewRevisionRecord => ({
1147
+ ...revision,
1148
+ metadata: {
1149
+ ...revision.metadata,
1150
+ storyTarget: {
1151
+ kind: "header" as const,
1152
+ relationshipId: ref.relationshipId,
1153
+ variant: ref.variant,
1154
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1155
+ },
1156
+ },
1157
+ })),
1158
+ );
1159
+ importedStoryRevisionDiagnostics.push(...parsedHeaderRevisions.diagnostics);
1160
+ sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
1161
+ } else {
1162
+ const parsedFooterRevisions = parseRevisionsFromStoryXml(xml);
1163
+ const parsed = parseFooterXml(xml);
1164
+ parsedFooters.push({
1165
+ variant: ref.variant,
1166
+ partPath,
1167
+ relationshipId: ref.relationshipId,
1168
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1169
+ blocks: normalizeSubPartOpaqueBlocks(
1170
+ parsed.blocks,
1171
+ normalizedDocument.preservation.opaqueFragments,
1172
+ normalizedDocument.diagnostics.warnings,
1173
+ partPath,
1174
+ subPartOpaqueState,
1175
+ ),
1176
+ });
1177
+ importedStoryRevisions.push(
1178
+ ...parsedFooterRevisions.revisions.map((revision): ReviewRevisionRecord => ({
1179
+ ...revision,
1180
+ metadata: {
1181
+ ...revision.metadata,
1182
+ storyTarget: {
1183
+ kind: "footer" as const,
1184
+ relationshipId: ref.relationshipId,
1185
+ variant: ref.variant,
1186
+ ...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
1187
+ },
1188
+ },
1189
+ })),
1190
+ );
1191
+ importedStoryRevisionDiagnostics.push(...parsedFooterRevisions.diagnostics);
1192
+ sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
1193
+ }
1194
+ }
1195
+
1196
+ const footnotesPartPath = resolveDocumentRelatedPartPath(
1197
+ sourcePackage,
1198
+ mainDocumentPath,
1199
+ documentPart.relationships,
1200
+ FOOTNOTES_RELATIONSHIP_TYPE,
1201
+ FOOTNOTES_PART_PATH,
1202
+ );
1203
+ const footnotesRelationshipId = documentPart.relationships.find(
1204
+ (r) => r.type === FOOTNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
1205
+ )?.id;
1206
+ const endnotesPartPath = resolveDocumentRelatedPartPath(
1207
+ sourcePackage,
1208
+ mainDocumentPath,
1209
+ documentPart.relationships,
1210
+ ENDNOTES_RELATIONSHIP_TYPE,
1211
+ ENDNOTES_PART_PATH,
1212
+ );
1213
+ const endnotesRelationshipId = documentPart.relationships.find(
1214
+ (r) => r.type === ENDNOTES_RELATIONSHIP_TYPE && r.targetMode === "internal",
1215
+ )?.id;
1216
+
1217
+ let footnoteCollection: FootnoteCollection | undefined;
1218
+ if (footnotesPartPath) {
1219
+ footnoteCollection = parseFootnotesXml(
1220
+ decodeUtf8(sourcePackage.parts.get(footnotesPartPath)?.bytes ?? new Uint8Array()),
1221
+ );
1222
+ normalizeFootnoteCollectionOpaqueBlocks(
1223
+ footnoteCollection,
1224
+ "footnote",
1225
+ normalizedDocument.preservation.opaqueFragments,
1226
+ normalizedDocument.diagnostics.warnings,
1227
+ footnotesPartPath,
1228
+ subPartOpaqueState,
1229
+ );
1230
+ }
1231
+ if (endnotesPartPath) {
1232
+ footnoteCollection = parseEndnotesXml(
1233
+ decodeUtf8(sourcePackage.parts.get(endnotesPartPath)?.bytes ?? new Uint8Array()),
1234
+ footnoteCollection,
1235
+ );
1236
+ normalizeFootnoteCollectionOpaqueBlocks(
1237
+ footnoteCollection,
1238
+ "endnote",
1239
+ normalizedDocument.preservation.opaqueFragments,
1240
+ normalizedDocument.diagnostics.warnings,
1241
+ endnotesPartPath,
1242
+ subPartOpaqueState,
1243
+ );
1244
+ }
1245
+ await scheduler.yield();
1246
+
1247
+ const themeRelationship = documentPart.relationships.find(
1248
+ (r) => r.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" &&
1249
+ r.targetMode === "internal",
1250
+ );
1251
+ const themePartPath = themeRelationship
1252
+ ? resolveRelationshipTarget(mainDocumentPath, themeRelationship)
1253
+ : undefined;
1254
+ const parsedTheme =
1255
+ themePartPath && sourcePackage.parts.has(themePartPath)
1256
+ ? parseThemeXml(
1257
+ decodeUtf8(sourcePackage.parts.get(themePartPath)?.bytes ?? new Uint8Array()),
1258
+ )
1259
+ : undefined;
1260
+ const resolvedTheme = parsedTheme ? resolveTheme(parsedTheme) : undefined;
1261
+ const settingsPartPath = resolveDocumentRelatedPartPath(
1262
+ sourcePackage,
1263
+ mainDocumentPath,
1264
+ documentPart.relationships,
1265
+ SETTINGS_RELATIONSHIP_TYPE,
1266
+ SETTINGS_PART_PATH,
1267
+ );
1268
+ const parsedSettings =
1269
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
1270
+ ? parseSettingsXml(
1271
+ decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
1272
+ )
1273
+ : undefined;
1274
+ const settingsXmlForProtection =
1275
+ settingsPartPath && sourcePackage.parts.has(settingsPartPath)
1276
+ ? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
1277
+ : "";
1278
+ const documentProtection = extractDocumentProtection(settingsXmlForProtection);
1279
+ const importedProtectionSnapshot = buildProtectionSnapshot(documentProtection, protectionRanges);
1280
+
1281
+ // ---- Parse styles.xml for canonical style catalog ----
1282
+ const stylesPartPath = resolveDocumentRelatedPartPath(
1283
+ sourcePackage,
1284
+ mainDocumentPath,
1285
+ documentPart.relationships,
1286
+ STYLES_RELATIONSHIP_TYPE,
1287
+ STYLES_PART_PATH,
1288
+ );
1289
+ const parsedStyles =
1290
+ stylesPartPath && sourcePackage.parts.has(stylesPartPath)
1291
+ ? parseStylesXml(
1292
+ decodeUtf8(sourcePackage.parts.get(stylesPartPath)?.bytes ?? new Uint8Array()),
1293
+ )
1294
+ : parseStylesXml("");
1295
+ await scheduler.yield();
1296
+
1297
+ const subParts: SubPartsCatalog | undefined =
1298
+ parsedHeaders.length > 0 ||
1299
+ parsedFooters.length > 0 ||
1300
+ footnoteCollection !== undefined ||
1301
+ parsedTheme !== undefined ||
1302
+ normalizedDocument.finalSectionProperties !== undefined ||
1303
+ resolvedTheme !== undefined ||
1304
+ parsedSettings !== undefined
1305
+ ? {
1306
+ headers: parsedHeaders,
1307
+ footers: parsedFooters,
1308
+ ...(footnoteCollection !== undefined ? { footnoteCollection } : {}),
1309
+ ...(parsedTheme !== undefined ? { theme: parsedTheme } : {}),
1310
+ ...(normalizedDocument.finalSectionProperties !== undefined
1311
+ ? { finalSectionProperties: normalizedDocument.finalSectionProperties }
1312
+ : {}),
1313
+ ...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
1314
+ ...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
1315
+ }
1316
+ : undefined;
1317
+
1318
+ const timestamp = new Date().toISOString();
1319
+ const translatedWorkflowState = translateClmCommentsToWorkflow({
1320
+ comments: normalizedComments.threads,
1321
+ workflowOverlay: embeddedWorkflowOverlay,
1322
+ workflowMetadata: embeddedWorkflowMetadata,
1323
+ timestamp,
1324
+ });
1325
+ const document = createImportedCanonicalDocument({
1326
+ documentId: options.documentId,
1327
+ timestamp,
1328
+ numbering: parsedNumbering,
1329
+ media: normalizedDocument.media,
1330
+ content: normalizedDocument.content,
1331
+ subParts,
1332
+ parsedStyles,
1333
+ preservation: {
1334
+ ...normalizedDocument.preservation,
1335
+ packageParts: {
1336
+ ...normalizedDocument.preservation.packageParts,
1337
+ ...collectPreservedPackageParts(sourcePackage, [
1338
+ mainDocumentPath,
1339
+ numberingPartPath,
1340
+ commentsPartPath,
1341
+ commentsExtendedPartPath,
1342
+ commentsIdsPartPath,
1343
+ peoplePartPath,
1344
+ ]),
1345
+ },
1346
+ },
1347
+ diagnostics: {
1348
+ warnings: [
1349
+ ...createBrokenRelationshipWarnings(sourcePackage, mainDocumentPath),
1350
+ ...normalizedDocument.diagnostics.warnings,
1351
+ ...normalizedRevisions.diagnostics.map((diagnostic, index) => ({
1352
+ diagnosticId: `diagnostic:revision-import-${index + 1}`,
1353
+ warningId: `warning:revision-import-${diagnostic.revisionId}`,
1354
+ source: "review" as const,
1355
+ message: diagnostic.message,
1356
+ })),
1357
+ ...importedStoryRevisionDiagnostics.map((diagnostic, index) => ({
1358
+ diagnosticId: `diagnostic:story-revision-import-${index + 1}`,
1359
+ warningId: `warning:story-revision-import-${diagnostic.revisionId}`,
1360
+ source: "review" as const,
1361
+ message: diagnostic.message,
1362
+ })),
1363
+ ...normalizedComments.diagnostics.map((diagnostic, index) => ({
1364
+ diagnosticId: `diagnostic:comment-import-${index + 1}`,
1365
+ warningId: `warning:comment-import-${diagnostic.commentId}`,
1366
+ source: "review" as const,
1367
+ message: diagnostic.message,
1368
+ })),
1369
+ ],
1370
+ errors: [],
1371
+ },
1372
+ review: {
1373
+ comments: toRuntimeCommentRecords(translatedWorkflowState.comments),
1374
+ revisions: toRuntimeRevisionRecords([
1375
+ ...normalizedRevisions.revisions,
1376
+ ...importedStoryRevisions,
1377
+ ]),
1378
+ },
1379
+ });
1380
+ const compatibility = buildCompatibilityReport({
1381
+ document,
1382
+ generatedAt: timestamp,
1383
+ });
1384
+ await scheduler.yield();
1385
+ const snapshot = createImportedSnapshot({
1386
+ documentId: options.documentId,
1387
+ editorBuild,
1388
+ timestamp,
1389
+ document,
1390
+ compatibility: toPublicCompatibilityReport(compatibility),
1391
+ protectionSnapshot: importedProtectionSnapshot,
1392
+ sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
1393
+ workflowOverlay: translatedWorkflowState.workflowOverlay,
1394
+ workflowMetadata: translatedWorkflowState.workflowMetadata,
1395
+ });
1396
+ const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
1397
+ if (snapshotIssues.length > 0) {
1398
+ const firstIssue = snapshotIssues[0];
1399
+ return createDiagnosticsSession(
1400
+ options,
1401
+ createValidationImportDiagnostics({
1402
+ message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
1403
+ source: "import",
1404
+ details: {
1405
+ issueCount: snapshotIssues.length,
1406
+ firstIssuePath: firstIssue?.path,
1407
+ },
1408
+ }),
1409
+ );
1410
+ }
1411
+ const initialSessionState = editorSessionStateFromPersistedSnapshot(snapshot);
1412
+ const importedState: ImportedDocxState = {
1413
+ sourceBytes: new Uint8Array(sourceBytes),
1414
+ sourcePackage,
1415
+ sourceDocumentXml,
1416
+ sourceDocumentPartPath: mainDocumentPath,
1417
+ sourceDocumentRelationships: documentPart.relationships,
1418
+ sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
1419
+ sourceNumberingPartPath: numberingPartPath,
1420
+ sourceNumberingRelationshipId: documentPart.relationships.find(
1421
+ (relationship) =>
1422
+ relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
1423
+ relationship.targetMode === "internal",
1424
+ )?.id,
1425
+ sourceCommentsPartPath: commentsPartPath,
1426
+ sourceCommentsRelationshipId: documentPart.relationships.find(
1427
+ (relationship) =>
1428
+ relationship.type === COMMENTS_RELATIONSHIP_TYPE &&
1429
+ relationship.targetMode === "internal",
1430
+ )?.id,
1431
+ sourceCommentsRootTag: normalizedComments.sourceRootTag,
1432
+ sourceCommentsExtendedPartPath: commentsExtendedPartPath,
1433
+ sourceCommentsExtendedRelationshipId: documentPart.relationships.find(
1434
+ (relationship) =>
1435
+ relationship.type === COMMENTS_EXTENDED_RELATIONSHIP_TYPE &&
1436
+ relationship.targetMode === "internal",
1437
+ )?.id,
1438
+ sourceCommentsExtendedRootTag: normalizedComments.sourceExtendedRootTag,
1439
+ sourceCommentsIdsPartPath: commentsIdsPartPath,
1440
+ sourceCommentsIdsRelationshipId: documentPart.relationships.find(
1441
+ (relationship) =>
1442
+ relationship.type === COMMENTS_IDS_RELATIONSHIP_TYPE &&
1443
+ relationship.targetMode === "internal",
1444
+ )?.id,
1445
+ sourceCommentsIdsRootTag: normalizedComments.sourceIdsRootTag,
1446
+ sourcePeoplePartPath: peoplePartPath,
1447
+ sourcePeopleRelationshipId: documentPart.relationships.find(
1448
+ (relationship) =>
1449
+ relationship.type === PEOPLE_RELATIONSHIP_TYPE &&
1450
+ relationship.targetMode === "internal",
1451
+ )?.id,
1452
+ sourcePeopleRootTag: normalizedComments.sourcePeopleRootTag,
1453
+ sourcePeopleAuthors: normalizedComments.peopleAuthors,
1454
+ protectionSnapshot: buildProtectionSnapshot(documentProtection, protectionRanges),
1455
+ preservedCommentDefinitions: normalizedComments.preservedDefinitions,
1456
+ blockingCommentDiagnostics: normalizedComments.diagnostics.filter((diagnostic) =>
1457
+ BLOCKING_COMMENT_DIAGNOSTIC_CODES.has(diagnostic.code),
1458
+ ),
1459
+ initialCanonicalSignature: serializeCanonicalDocumentForExport(document),
1460
+ sourceSubPartPaths: {
1461
+ headers: sourceHeaderPaths,
1462
+ footers: sourceFooterPaths,
1463
+ footnotesPartPath,
1464
+ footnotesRelationshipId,
1465
+ endnotesPartPath,
1466
+ endnotesRelationshipId,
1467
+ themePartPath,
1468
+ themeRelationshipId: themeRelationship?.id,
1469
+ },
1470
+ };
1471
+
1472
+ stages.emit("skeleton-ready");
1473
+ return {
1474
+ initialSessionState,
1475
+ initialSnapshot: snapshot,
1476
+ readOnly: false,
1477
+ protectionSnapshot: importedProtectionSnapshot,
1478
+ exportDocx: async (nextSessionState, exportOptions) =>
1479
+ exportDocxEditorSession(importedState, nextSessionState, exportOptions),
1480
+ ...(embeddedWorkflowPayload?.editorState
1481
+ ? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
1482
+ : {}),
1483
+ };
1484
+ } catch (error) {
1485
+ return createDiagnosticsSession(
1486
+ options,
1487
+ createImportDiagnosticsFromError(error),
1488
+ );
1489
+ }
1490
+ }
1491
+
1492
+ function loadStageNow(): number {
1493
+ return typeof performance !== "undefined" && typeof performance.now === "function"
1494
+ ? performance.now()
1495
+ : Date.now();
1496
+ }
1497
+
832
1498
  function exportDocxEditorSession(
833
1499
  state: ImportedDocxState,
834
1500
  sessionStateOrSnapshot: EditorSessionState | PersistedEditorSnapshot,
@@ -1286,7 +1952,9 @@ function exportDocxEditorSession(
1286
1952
  }
1287
1953
 
1288
1954
  ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
1289
- ensureWorkflowPayloadParts(exportSession, sessionState, currentDocument, state.sourcePackage);
1955
+ // Schema 1.2: pass through editorState payload collected by the runtime channel.
1956
+ const internalEditorState = (options as { _editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload } | undefined)?._editorState;
1957
+ ensureWorkflowPayloadParts(exportSession, sessionState, currentDocument, state.sourcePackage, internalEditorState);
1290
1958
 
1291
1959
  return {
1292
1960
  bytes: exportSession.serialize(),
@@ -3012,11 +3680,13 @@ function ensureWorkflowPayloadParts(
3012
3680
  sessionState: EditorSessionState,
3013
3681
  document: CanonicalDocumentEnvelope,
3014
3682
  sourcePackage: OpcPackage,
3683
+ editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload,
3015
3684
  ): void {
3016
3685
  const payloadParts = buildWorkflowPayloadParts({
3017
3686
  sourcePackage,
3018
3687
  workflowMetadata: sessionState.workflowMetadata,
3019
3688
  workflowOverlay: sessionState.workflowOverlay,
3689
+ editorState,
3020
3690
  documentId: sessionState.documentId,
3021
3691
  createdAt: document.createdAt,
3022
3692
  updatedAt: document.updatedAt,