@beyondwork/docx-react-component 1.0.102 → 1.0.104

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 (57) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +63 -1
  3. package/src/api/v3/_runtime-handle.ts +2 -0
  4. package/src/api/v3/ai/outline.ts +2 -7
  5. package/src/api/v3/runtime/geometry.ts +79 -0
  6. package/src/core/commands/formatting-commands.ts +8 -7
  7. package/src/core/commands/paragraph-layout-commands.ts +11 -10
  8. package/src/core/commands/section-layout-commands.ts +7 -6
  9. package/src/core/commands/style-commands.ts +3 -2
  10. package/src/io/normalize/normalize-text.ts +6 -5
  11. package/src/io/ooxml/parse-anchor.ts +15 -15
  12. package/src/io/ooxml/parse-drawing.ts +103 -5
  13. package/src/io/ooxml/parse-fields.ts +43 -21
  14. package/src/io/ooxml/parse-font-table.ts +2 -1
  15. package/src/io/ooxml/parse-footnotes.ts +3 -2
  16. package/src/io/ooxml/parse-headers-footers.ts +7 -6
  17. package/src/io/ooxml/parse-main-document.ts +41 -40
  18. package/src/io/ooxml/parse-numbering.ts +3 -2
  19. package/src/io/ooxml/parse-object.ts +6 -6
  20. package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
  21. package/src/io/ooxml/parse-picture.ts +16 -16
  22. package/src/io/ooxml/parse-run-formatting.ts +11 -10
  23. package/src/io/ooxml/parse-settings.ts +2 -1
  24. package/src/io/ooxml/parse-shapes.ts +148 -17
  25. package/src/io/ooxml/parse-styles.ts +16 -16
  26. package/src/io/ooxml/parse-theme.ts +5 -4
  27. package/src/model/canonical-document.ts +869 -836
  28. package/src/model/canonical-layout-inputs.ts +979 -0
  29. package/src/model/layout/index.ts +6 -0
  30. package/src/model/layout/page-graph-types.ts +61 -0
  31. package/src/model/layout/runtime-page-graph-types.ts +10 -0
  32. package/src/runtime/collab/runtime-collab-sync.ts +3 -3
  33. package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
  34. package/src/runtime/document-runtime.ts +30 -14
  35. package/src/runtime/event-refresh-hints.ts +3 -0
  36. package/src/runtime/formatting/document-lookup.ts +3 -2
  37. package/src/runtime/formatting/formatting-context.ts +176 -34
  38. package/src/runtime/formatting/index.ts +20 -0
  39. package/src/runtime/formatting/layout-inputs.ts +320 -0
  40. package/src/runtime/formatting/numbering/geometry.ts +13 -12
  41. package/src/runtime/formatting/style-cascade.ts +2 -1
  42. package/src/runtime/formatting/table-style-resolver.ts +8 -7
  43. package/src/runtime/geometry/caret-geometry.ts +82 -10
  44. package/src/runtime/geometry/geometry-facet.ts +36 -0
  45. package/src/runtime/geometry/geometry-index.ts +891 -0
  46. package/src/runtime/geometry/geometry-types.ts +221 -1
  47. package/src/runtime/geometry/index.ts +26 -0
  48. package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
  49. package/src/runtime/geometry/replacement-envelope.ts +41 -2
  50. package/src/runtime/layout/layout-engine-version.ts +16 -1
  51. package/src/runtime/layout/page-graph.ts +191 -1
  52. package/src/runtime/prerender/graph-canonicalize.ts +30 -0
  53. package/src/runtime/surface-projection.ts +74 -39
  54. package/src/runtime/workflow/coordinator.ts +57 -11
  55. package/src/session/import/normalize.ts +2 -1
  56. package/src/session/import/source-package-evidence.ts +612 -1
  57. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  RuntimeBlockFragment,
3
3
  RuntimePageAnchor,
4
+ RuntimePageFrame,
4
5
  RuntimePageGraph,
5
6
  RuntimePageNode,
6
7
  RuntimePageRegion,
@@ -54,6 +55,9 @@ export function canonicalizeGraph(graph: RuntimePageGraph): RuntimePageGraph {
54
55
  ...page,
55
56
  pageId: rewriteId(page.pageId),
56
57
  regions: rewriteRegions(page.regions, rewriteId),
58
+ ...(page.frame === undefined
59
+ ? {}
60
+ : { frame: rewriteFrame(page.frame, rewriteId) }),
57
61
  lineBoxes: page.lineBoxes.map((line) => ({
58
62
  ...line,
59
63
  fragmentId: rewriteId(line.fragmentId),
@@ -86,6 +90,32 @@ export function canonicalizeGraph(graph: RuntimePageGraph): RuntimePageGraph {
86
90
  };
87
91
  }
88
92
 
93
+ function rewriteFrame(
94
+ frame: RuntimePageFrame,
95
+ rewriteId: (id: string) => string,
96
+ ): RuntimePageFrame {
97
+ return {
98
+ ...frame,
99
+ pageId: rewriteId(frame.pageId),
100
+ regions: {
101
+ ...frame.regions,
102
+ body: rewriteRegion(frame.regions.body, rewriteId),
103
+ ...(frame.regions.header
104
+ ? { header: rewriteRegion(frame.regions.header, rewriteId) }
105
+ : {}),
106
+ ...(frame.regions.footer
107
+ ? { footer: rewriteRegion(frame.regions.footer, rewriteId) }
108
+ : {}),
109
+ ...(frame.regions.columns
110
+ ? { columns: frame.regions.columns.map((region) => rewriteRegion(region, rewriteId)) }
111
+ : {}),
112
+ ...(frame.regions.footnotes
113
+ ? { footnotes: frame.regions.footnotes.map((region) => rewriteRegion(region, rewriteId)) }
114
+ : {}),
115
+ },
116
+ };
117
+ }
118
+
89
119
  function rewriteRegions(
90
120
  regions: RuntimePageRegions,
91
121
  rewriteId: (id: string) => string,
@@ -6,6 +6,7 @@ import type {
6
6
  SurfaceDrawingAnchor,
7
7
  SurfaceInlineSegment,
8
8
  SurfacePictureEffects,
9
+ SurfacePreserveOnlyObjectSizing,
9
10
  SurfaceTableCellSnapshot,
10
11
  SurfaceTableRowSnapshot,
11
12
  SurfaceTextMark,
@@ -38,9 +39,11 @@ import type {
38
39
  TextMark,
39
40
  DrawingFrameNode,
40
41
  PictureContent,
42
+ PreserveOnlyObjectSizing,
41
43
  ShapeContent,
42
44
  VmlShapeNode,
43
45
  WordArtNode,
46
+ Mutable,
44
47
  } from "../model/canonical-document.ts";
45
48
  import {
46
49
  describeOpaqueFragment,
@@ -575,7 +578,7 @@ function createSurfaceBlock(
575
578
 
576
579
  function createTableBlock(
577
580
  tableIndex: number,
578
- table: TableNode,
581
+ table: Mutable<TableNode>,
579
582
  document: CanonicalDocumentEnvelope,
580
583
  cursor: number,
581
584
  counters: {
@@ -769,7 +772,7 @@ function createTableBlock(
769
772
  };
770
773
  }
771
774
 
772
- function computeTableRowSpans(table: TableNode): Map<string, number> {
775
+ function computeTableRowSpans(table: Mutable<TableNode>): Map<string, number> {
773
776
  const positionedRows = table.rows.map((row) => {
774
777
  let startColumn = 0;
775
778
 
@@ -933,7 +936,7 @@ function toSurfaceFrameProperties(
933
936
  }
934
937
 
935
938
  function resolveSurfaceParagraphFormatting(
936
- formatting: CanonicalParagraphFormatting,
939
+ formatting: Mutable<CanonicalParagraphFormatting>,
937
940
  themeResolver: ThemeColorResolver | undefined,
938
941
  ): CanonicalParagraphFormatting {
939
942
  if (!formatting.shading) {
@@ -1065,7 +1068,7 @@ function resolveCellBorderStyles(
1065
1068
 
1066
1069
  function createSdtBlock(
1067
1070
  sdtIndex: number,
1068
- block: SdtNode,
1071
+ block: Mutable<SdtNode>,
1069
1072
  document: CanonicalDocumentEnvelope,
1070
1073
  cursor: number,
1071
1074
  counters: {
@@ -1123,7 +1126,7 @@ function createSdtBlock(
1123
1126
 
1124
1127
  function createParagraphBlock(
1125
1128
  paragraphIndex: number,
1126
- paragraph: ParagraphNode,
1129
+ paragraph: Mutable<ParagraphNode>,
1127
1130
  document: CanonicalDocumentEnvelope,
1128
1131
  start: number,
1129
1132
  formattingContext: FormattingContext,
@@ -1548,6 +1551,9 @@ function appendInlineSegments(
1548
1551
  ...(c.line ? { line: c.line } : {}),
1549
1552
  ...(c.isTextBox ? { isTextBox: true } : {}),
1550
1553
  ...(c.textBoxBody ? { textBoxBody: c.textBoxBody } : {}),
1554
+ ...(c.preserveOnlyObject
1555
+ ? { preserveOnlyObject: surfacePreserveOnlyObject(c.preserveOnlyObject) }
1556
+ : {}),
1551
1557
  ...(txbxText ? { txbxText } : {}),
1552
1558
  ...(txbxTextSegment?.marks && txbxTextSegment.marks.length > 0
1553
1559
  ? { txbxMarks: txbxTextSegment.marks }
@@ -1567,6 +1573,7 @@ function appendInlineSegments(
1567
1573
  {
1568
1574
  previewMediaId: c.previewMediaId,
1569
1575
  parsedChartId,
1576
+ anchor: surfaceAnchorFromGeometry(node.anchor),
1570
1577
  },
1571
1578
  );
1572
1579
  }
@@ -1577,7 +1584,10 @@ function appendInlineSegments(
1577
1584
  start,
1578
1585
  "SmartArt diagram",
1579
1586
  `DrawingFrame smartart_preview (${node.anchor.wrapMode}).`,
1580
- { previewMediaId: c.previewMediaId },
1587
+ {
1588
+ previewMediaId: c.previewMediaId,
1589
+ anchor: surfaceAnchorFromGeometry(node.anchor),
1590
+ },
1581
1591
  );
1582
1592
  }
1583
1593
  return appendComplexPreviewSegment(
@@ -1586,6 +1596,7 @@ function appendInlineSegments(
1586
1596
  start,
1587
1597
  "Drawing frame",
1588
1598
  `DrawingFrame ${c.type} (${node.anchor.wrapMode}).`,
1599
+ { anchor: surfaceAnchorFromGeometry(node.anchor) },
1589
1600
  );
1590
1601
  }
1591
1602
  case "symbol":
@@ -1757,13 +1768,7 @@ function appendInlineSegments(
1757
1768
  const fieldLabel =
1758
1769
  node.fieldFamily === "TOC"
1759
1770
  ? "Table of Contents"
1760
- : node.fieldFamily === "PAGE"
1761
- ? "Current page number"
1762
- : node.fieldFamily === "NUMPAGES"
1763
- ? "Total pages"
1764
- : node.fieldFamily === "SECTIONPAGES"
1765
- ? "Section pages"
1766
- : `${node.fieldFamily ?? "Field"}: ${node.fieldTarget ?? node.instruction.trim()}`;
1771
+ : `${node.fieldFamily ?? "Field"}: ${node.fieldTarget ?? node.instruction.trim()}`;
1767
1772
  paragraph.segments.push({
1768
1773
  segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
1769
1774
  kind: "field_ref",
@@ -1804,7 +1809,7 @@ function appendInlineSegments(
1804
1809
  }
1805
1810
 
1806
1811
  function registerParsedChartPreview(
1807
- node: ChartPreviewNode,
1812
+ node: Mutable<ChartPreviewNode>,
1808
1813
  document: CanonicalDocumentEnvelope,
1809
1814
  ): string | undefined {
1810
1815
  if (!node.parsedData) return undefined;
@@ -1946,7 +1951,7 @@ function surfaceAnchorFromGeometry(
1946
1951
  * fast-path image rendering.
1947
1952
  */
1948
1953
  function surfacePictureEffectsFromContent(
1949
- content: PictureContent,
1954
+ content: Mutable<PictureContent>,
1950
1955
  themeResolver?: ThemeColorResolver,
1951
1956
  ): SurfacePictureEffects | undefined {
1952
1957
  const outerShadow = resolveSurfacePictureShadow(content.outerShadow, themeResolver);
@@ -2114,12 +2119,19 @@ function extractTxbxFirstTextSegment(
2114
2119
 
2115
2120
  function appendComplexPreviewSegment(
2116
2121
  paragraph: ParagraphAccumulator,
2117
- node: { rawXml: string },
2122
+ node: { rawXml: string; preserveOnlyObject?: PreserveOnlyObjectSizing },
2118
2123
  start: number,
2119
2124
  label: string,
2120
2125
  detail: string,
2121
- extras: { previewMediaId?: string; parsedChartId?: string } = {},
2126
+ extras: {
2127
+ previewMediaId?: string;
2128
+ parsedChartId?: string;
2129
+ anchor?: SurfaceDrawingAnchor;
2130
+ } = {},
2122
2131
  ): { nextCursor: number; lockedFragmentIds: string[] } {
2132
+ const preserveOnlyObject = node.preserveOnlyObject
2133
+ ? surfacePreserveOnlyObject(node.preserveOnlyObject)
2134
+ : undefined;
2123
2135
  paragraph.segments.push({
2124
2136
  segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
2125
2137
  kind: "opaque_inline",
@@ -2131,11 +2143,34 @@ function appendComplexPreviewSegment(
2131
2143
  detail,
2132
2144
  ...(extras.previewMediaId ? { previewMediaId: extras.previewMediaId } : {}),
2133
2145
  ...(extras.parsedChartId ? { parsedChartId: extras.parsedChartId } : {}),
2146
+ ...(preserveOnlyObject ? { preserveOnlyObject } : {}),
2147
+ ...(extras.anchor ? { anchor: extras.anchor } : {}),
2134
2148
  state: "locked-preserve-only",
2135
2149
  });
2136
2150
  return { nextCursor: start + 1, lockedFragmentIds: [] };
2137
2151
  }
2138
2152
 
2153
+ function surfacePreserveOnlyObject(
2154
+ object: PreserveOnlyObjectSizing,
2155
+ ): SurfacePreserveOnlyObjectSizing {
2156
+ return {
2157
+ ...(object.sourceId ? { sourceId: object.sourceId } : {}),
2158
+ display: object.display,
2159
+ ...(object.extentEmu
2160
+ ? {
2161
+ extentEmu: {
2162
+ widthEmu: object.extentEmu.widthEmu,
2163
+ heightEmu: object.extentEmu.heightEmu,
2164
+ },
2165
+ }
2166
+ : {}),
2167
+ fallbackHint: object.fallbackHint,
2168
+ ...(object.relationshipIds && object.relationshipIds.length > 0
2169
+ ? { relationshipIds: [...object.relationshipIds] }
2170
+ : {}),
2171
+ };
2172
+ }
2173
+
2139
2174
  function appendTextBoxSegment(
2140
2175
  paragraph: ParagraphAccumulator,
2141
2176
  start: number,
@@ -2159,7 +2194,7 @@ function appendTextBoxSegment(
2159
2194
  return { nextCursor: start + 1, lockedFragmentIds: [] };
2160
2195
  }
2161
2196
 
2162
- function shouldRenderSecondaryStoryVmlTextBox(node: VmlShapeNode): boolean {
2197
+ function shouldRenderSecondaryStoryVmlTextBox(node: Mutable<VmlShapeNode>): boolean {
2163
2198
  return Boolean(node.text) && (!node.shapeType || /_x0000_t202$/iu.test(node.shapeType));
2164
2199
  }
2165
2200
 
@@ -2174,7 +2209,7 @@ function isMicrosoftSensitivityLabelShape(
2174
2209
  /"Placement"\s*:\s*"Footer"/iu.test(node.rawXml);
2175
2210
  }
2176
2211
 
2177
- function createChartDetail(node: ChartPreviewNode): string {
2212
+ function createChartDetail(node: Mutable<ChartPreviewNode>): string {
2178
2213
  const parts = ["Embedded chart."];
2179
2214
  if (node.previewMediaId) {
2180
2215
  parts.push(`Preview available via fallback image (${node.previewMediaId}).`);
@@ -2185,7 +2220,7 @@ function createChartDetail(node: ChartPreviewNode): string {
2185
2220
  return parts.join(" ");
2186
2221
  }
2187
2222
 
2188
- function createSmartArtDetail(node: SmartArtPreviewNode): string {
2223
+ function createSmartArtDetail(node: Mutable<SmartArtPreviewNode>): string {
2189
2224
  const parts = ["SmartArt diagram."];
2190
2225
  if (node.previewMediaId) {
2191
2226
  parts.push(`Preview available via fallback image (${node.previewMediaId}).`);
@@ -2196,7 +2231,7 @@ function createSmartArtDetail(node: SmartArtPreviewNode): string {
2196
2231
  return parts.join(" ");
2197
2232
  }
2198
2233
 
2199
- function createShapeDetail(node: ShapeNode): string {
2234
+ function createShapeDetail(node: Mutable<ShapeNode>): string {
2200
2235
  if (node.isTextBox) {
2201
2236
  const parts = ["Text box."];
2202
2237
  if (node.text) parts.push(`Content: "${node.text}".`);
@@ -2210,7 +2245,7 @@ function createShapeDetail(node: ShapeNode): string {
2210
2245
  return parts.join(" ");
2211
2246
  }
2212
2247
 
2213
- function createWordArtDetail(node: WordArtNode): string {
2248
+ function createWordArtDetail(node: Mutable<WordArtNode>): string {
2214
2249
  const parts = ["WordArt decorative text."];
2215
2250
  if (node.text) parts.push(`Text: "${node.text}".`);
2216
2251
  if (node.geometry) parts.push(`Effect: ${node.geometry}.`);
@@ -2218,7 +2253,7 @@ function createWordArtDetail(node: WordArtNode): string {
2218
2253
  return parts.join(" ");
2219
2254
  }
2220
2255
 
2221
- function createVmlDetail(node: VmlShapeNode): string {
2256
+ function createVmlDetail(node: Mutable<VmlShapeNode>): string {
2222
2257
  const parts = ["Legacy VML drawing."];
2223
2258
  if (node.shapeType) parts.push(`Type: ${node.shapeType}.`);
2224
2259
  if (node.text) parts.push(`Text content: "${node.text}".`);
@@ -2587,22 +2622,22 @@ function toSurfaceTabStop(
2587
2622
  }
2588
2623
 
2589
2624
  function buildDirectParagraphFormattingFromNode(
2590
- paragraph: ParagraphNode,
2625
+ paragraph: Mutable<ParagraphNode>,
2591
2626
  ): CanonicalParagraphFormatting | undefined {
2592
- const direct: CanonicalParagraphFormatting = {};
2593
- if (paragraph.spacing) direct.spacing = paragraph.spacing;
2594
- if (paragraph.indentation) direct.indentation = paragraph.indentation;
2595
- if (paragraph.alignment) direct.alignment = paragraph.alignment;
2596
- if (paragraph.borders) direct.borders = paragraph.borders;
2597
- if (paragraph.shading) direct.shading = paragraph.shading;
2598
- if (paragraph.tabStops && paragraph.tabStops.length > 0) direct.tabStops = [...paragraph.tabStops];
2599
- if (paragraph.contextualSpacing !== undefined) direct.contextualSpacing = paragraph.contextualSpacing;
2600
- if (paragraph.keepNext !== undefined) direct.keepNext = paragraph.keepNext;
2601
- if (paragraph.keepLines !== undefined) direct.keepLines = paragraph.keepLines;
2602
- if (paragraph.widowControl !== undefined) direct.widowControl = paragraph.widowControl;
2603
- if (paragraph.pageBreakBefore !== undefined) direct.pageBreakBefore = paragraph.pageBreakBefore;
2604
- if (paragraph.outlineLevel !== undefined) direct.outlineLevel = paragraph.outlineLevel;
2605
- if (paragraph.bidi !== undefined) direct.bidi = paragraph.bidi;
2627
+ const direct: Mutable<CanonicalParagraphFormatting> = {};
2628
+ if (paragraph.spacing) (direct as Mutable<typeof direct>).spacing = paragraph.spacing;
2629
+ if (paragraph.indentation) (direct as Mutable<typeof direct>).indentation = paragraph.indentation;
2630
+ if (paragraph.alignment) (direct as Mutable<typeof direct>).alignment = paragraph.alignment;
2631
+ if (paragraph.borders) (direct as Mutable<typeof direct>).borders = paragraph.borders;
2632
+ if (paragraph.shading) (direct as Mutable<typeof direct>).shading = paragraph.shading;
2633
+ if (paragraph.tabStops && paragraph.tabStops.length > 0) (direct as Mutable<typeof direct>).tabStops = [...paragraph.tabStops];
2634
+ if (paragraph.contextualSpacing !== undefined) (direct as Mutable<typeof direct>).contextualSpacing = paragraph.contextualSpacing;
2635
+ if (paragraph.keepNext !== undefined) (direct as Mutable<typeof direct>).keepNext = paragraph.keepNext;
2636
+ if (paragraph.keepLines !== undefined) (direct as Mutable<typeof direct>).keepLines = paragraph.keepLines;
2637
+ if (paragraph.widowControl !== undefined) (direct as Mutable<typeof direct>).widowControl = paragraph.widowControl;
2638
+ if (paragraph.pageBreakBefore !== undefined) (direct as Mutable<typeof direct>).pageBreakBefore = paragraph.pageBreakBefore;
2639
+ if (paragraph.outlineLevel !== undefined) (direct as Mutable<typeof direct>).outlineLevel = paragraph.outlineLevel;
2640
+ if (paragraph.bidi !== undefined) (direct as Mutable<typeof direct>).bidi = paragraph.bidi;
2606
2641
  if (paragraph.suppressLineNumbers !== undefined) direct.suppressLineNumbers = paragraph.suppressLineNumbers;
2607
2642
  return Object.keys(direct).length > 0 ? direct : undefined;
2608
2643
  }
@@ -2947,7 +2982,7 @@ function createFloatingImageDetail(
2947
2982
  return parts.join(" ");
2948
2983
  }
2949
2984
 
2950
- function hasMediaItem(media: MediaCatalog, mediaId: string): boolean {
2985
+ function hasMediaItem(media: Mutable<MediaCatalog>, mediaId: string): boolean {
2951
2986
  return mediaId in media.items;
2952
2987
  }
2953
2988
 
@@ -62,6 +62,7 @@ import type {
62
62
  WorkflowMetadataDefinition,
63
63
  WorkflowMetadataEntry,
64
64
  WorkflowMetadataSnapshot,
65
+ WorkflowEventOrigin,
65
66
  WorkflowOverlay,
66
67
  WorkflowScope,
67
68
  WorkflowScopeGuardPolicy,
@@ -100,10 +101,8 @@ import {
100
101
  } from "./visibility-policy.ts";
101
102
  import { type WorkflowMarkupModePolicy } from "./markup-mode-policy.ts";
102
103
 
103
- /** Shape of origin metadata attached to commands. The runtime has a
104
- * richer `CommandOrigin` type; we only pass it through, so we keep an
105
- * open shape here to avoid coupling. */
106
- type CoordinatorCommandOrigin = { readonly source: string; readonly at?: string };
104
+ /** Shape of origin metadata attached to workflow commands/events. */
105
+ type CoordinatorCommandOrigin = WorkflowEventOrigin;
107
106
 
108
107
  /** Document-mutation commands the coordinator emits through `deps.dispatch`. */
109
108
  type CoordinatorDispatchedCommand =
@@ -157,10 +156,17 @@ export interface WorkflowEmittableEvent {
157
156
  readonly type:
158
157
  | "workflow_overlay_changed"
159
158
  | "workflow_active_work_item_changed"
160
- | "workflow_metadata_changed";
159
+ | "workflow_metadata_changed"
160
+ | "workflow_shared_state_changed"
161
+ | "workflow_visibility_policy_changed"
162
+ | "workflow_markup_mode_policy_changed";
161
163
  readonly documentId: string;
162
164
  readonly snapshot?: WorkflowScopeSnapshot | WorkflowMetadataSnapshot | null;
163
165
  readonly activeWorkItemId?: string | null;
166
+ readonly state?: SharedWorkflowState | null;
167
+ readonly kind?: OverlayKind;
168
+ readonly policy?: OverlayVisibilityPolicy | WorkflowMarkupModePolicy | null;
169
+ readonly origin?: CoordinatorCommandOrigin;
164
170
  }
165
171
 
166
172
  export interface PerfCounters {
@@ -276,7 +282,10 @@ export interface WorkflowCoordinator {
276
282
  * seam) so UI surfaces re-fire when policy changes mid-session. */
277
283
  subscribeMarkupModePolicy(listener: () => void): () => void;
278
284
  /* --- collab shared state --- */
279
- setSharedWorkflowState(state: SharedWorkflowState | null): void;
285
+ setSharedWorkflowState(
286
+ state: SharedWorkflowState | null,
287
+ origin?: CoordinatorCommandOrigin,
288
+ ): void;
280
289
  /* --- snapshots (cached) --- */
281
290
  getInteractionGuardSnapshot(): InteractionGuardSnapshot;
282
291
  getWorkflowScopeSnapshot(): WorkflowScopeSnapshot | null;
@@ -1133,12 +1142,21 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1133
1142
  return overlayStore.getMetadataSnapshot();
1134
1143
  }
1135
1144
 
1136
- function setSharedWorkflowState(state: SharedWorkflowState | null): void {
1145
+ function setSharedWorkflowState(
1146
+ state: SharedWorkflowState | null,
1147
+ origin: CoordinatorCommandOrigin = { source: "api", at: clock() },
1148
+ ): void {
1137
1149
  const prior = overlayStore.getSharedWorkflowState();
1138
1150
  if (state === prior) return;
1139
1151
  overlayStore.replaceSharedWorkflowState(state);
1140
1152
  cachedInteractionGuardSnapshot = undefined;
1141
1153
  cachedWorkflowScopeSnapshot = undefined;
1154
+ deps.emitEvent({
1155
+ type: "workflow_shared_state_changed",
1156
+ documentId: deps.getState().documentId,
1157
+ state,
1158
+ origin,
1159
+ });
1142
1160
  }
1143
1161
 
1144
1162
  function setScopeChromeVisibility(state: ScopeChromeVisibilityState): void {
@@ -1161,13 +1179,13 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1161
1179
 
1162
1180
  function setVisibilityPolicy(policy: OverlayVisibilityPolicy): boolean {
1163
1181
  const changed = overlayStore.replaceVisibilityPolicy(policy.kind, policy);
1164
- if (changed) emitVisibilityPolicyChanged();
1182
+ if (changed) emitVisibilityPolicyChanged(policy.kind, policy);
1165
1183
  return changed;
1166
1184
  }
1167
1185
 
1168
1186
  function clearVisibilityPolicy(kind: OverlayKind): boolean {
1169
1187
  const changed = overlayStore.replaceVisibilityPolicy(kind, null);
1170
- if (changed) emitVisibilityPolicyChanged();
1188
+ if (changed) emitVisibilityPolicyChanged(kind, null);
1171
1189
  return changed;
1172
1190
  }
1173
1191
 
@@ -1175,7 +1193,10 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1175
1193
  policies: readonly OverlayVisibilityPolicy[],
1176
1194
  ): boolean {
1177
1195
  const changed = overlayStore.replaceVisibilityPolicies(policies);
1178
- if (changed) emitVisibilityPolicyChanged();
1196
+ if (changed) emitVisibilityPolicyChanged(undefined, undefined, {
1197
+ source: "runtime",
1198
+ at: clock(),
1199
+ });
1179
1200
  return changed;
1180
1201
  }
1181
1202
 
@@ -1195,6 +1216,12 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1195
1216
  const changed = overlayStore.replaceMarkupModePolicy(policy);
1196
1217
  if (changed) {
1197
1218
  overlayStore.notifyMarkupModePolicyChanged();
1219
+ deps.emitEvent({
1220
+ type: "workflow_markup_mode_policy_changed",
1221
+ documentId: deps.getState().documentId,
1222
+ policy,
1223
+ origin: { source: "api", at: clock() },
1224
+ });
1198
1225
  if (deps.telemetryBus.isEnabled("scope")) {
1199
1226
  deps.telemetryBus.emit({
1200
1227
  channel: "scope",
@@ -1211,11 +1238,22 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1211
1238
  return overlayStore.subscribeMarkupModePolicy(listener);
1212
1239
  }
1213
1240
 
1214
- function emitVisibilityPolicyChanged(): void {
1241
+ function emitVisibilityPolicyChanged(
1242
+ kind?: OverlayKind,
1243
+ policy?: OverlayVisibilityPolicy | null,
1244
+ origin: CoordinatorCommandOrigin = { source: "api", at: clock() },
1245
+ ): void {
1215
1246
  // Notify direct subscribers first — L10 X3's ui.overlays.subscribeVisibility
1216
1247
  // chains onto this signal so UI surfaces re-fire when an authoring tool
1217
1248
  // mutates policy mid-session.
1218
1249
  overlayStore.notifyVisibilityPolicyChanged();
1250
+ deps.emitEvent({
1251
+ type: "workflow_visibility_policy_changed",
1252
+ documentId: deps.getState().documentId,
1253
+ ...(kind !== undefined ? { kind } : {}),
1254
+ ...(policy !== undefined ? { policy } : {}),
1255
+ origin,
1256
+ });
1219
1257
  if (!deps.telemetryBus.isEnabled("scope")) return;
1220
1258
  deps.telemetryBus.emit({
1221
1259
  channel: "scope",
@@ -1301,12 +1339,14 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1301
1339
  type: "workflow_overlay_changed",
1302
1340
  documentId,
1303
1341
  snapshot,
1342
+ origin: command.origin,
1304
1343
  });
1305
1344
  if (command.overlay.activeWorkItemId !== undefined) {
1306
1345
  deps.emitEvent({
1307
1346
  type: "workflow_active_work_item_changed",
1308
1347
  documentId,
1309
1348
  activeWorkItemId: command.overlay.activeWorkItemId ?? null,
1349
+ origin: command.origin,
1310
1350
  });
1311
1351
  }
1312
1352
  return warningDelta;
@@ -1323,6 +1363,7 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1323
1363
  type: "workflow_active_work_item_changed",
1324
1364
  documentId,
1325
1365
  activeWorkItemId: null,
1366
+ origin: command.origin,
1326
1367
  });
1327
1368
  deps.emitEvent({
1328
1369
  type: "workflow_overlay_changed",
@@ -1334,6 +1375,7 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1334
1375
  candidates: [],
1335
1376
  blockedReasons: [],
1336
1377
  },
1378
+ origin: command.origin,
1337
1379
  });
1338
1380
  return warningDelta;
1339
1381
  }
@@ -1343,6 +1385,7 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1343
1385
  type: "workflow_metadata_changed",
1344
1386
  documentId,
1345
1387
  snapshot: overlayStore.getMetadataSnapshot(),
1388
+ origin: command.origin,
1346
1389
  });
1347
1390
  return null;
1348
1391
  }
@@ -1352,6 +1395,7 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1352
1395
  type: "workflow_metadata_changed",
1353
1396
  documentId,
1354
1397
  snapshot: overlayStore.getMetadataSnapshot(),
1398
+ origin: command.origin,
1355
1399
  });
1356
1400
  return null;
1357
1401
  }
@@ -1361,6 +1405,7 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1361
1405
  type: "workflow_metadata_changed",
1362
1406
  documentId,
1363
1407
  snapshot: overlayStore.getMetadataSnapshot(),
1408
+ origin: command.origin,
1364
1409
  });
1365
1410
  return null;
1366
1411
  }
@@ -1370,6 +1415,7 @@ export function createWorkflowCoordinator(deps: CoordinatorDeps): WorkflowCoordi
1370
1415
  type: "workflow_metadata_changed",
1371
1416
  documentId,
1372
1417
  snapshot: overlayStore.getMetadataSnapshot(),
1418
+ origin: command.origin,
1373
1419
  });
1374
1420
  return null;
1375
1421
  }
@@ -22,6 +22,7 @@ import type {
22
22
  CanonicalDocument,
23
23
  FootnoteCollection,
24
24
  OpaqueFragmentRecord,
25
+ Mutable,
25
26
  } from "../../model/canonical-document.ts";
26
27
 
27
28
  /** Warning shape carried on `CanonicalDocument.diagnostics.warnings`. */
@@ -130,7 +131,7 @@ export function normalizeFootnoteCollectionOpaqueBlocks(
130
131
  if (!collection) return;
131
132
  const notes = kind === "footnote" ? collection.footnotes : collection.endnotes;
132
133
  for (const definition of Object.values(notes)) {
133
- definition.blocks = normalizeSubPartOpaqueBlocks(
134
+ (definition as Mutable<typeof definition>).blocks = normalizeSubPartOpaqueBlocks(
134
135
  definition.blocks,
135
136
  opaqueFragments,
136
137
  warnings,