@beyondwork/docx-react-component 1.0.106 → 1.0.109

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 (190) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +2 -1
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-time.ts +5 -0
  11. package/src/api/v3/ai/_pe2-evidence.ts +38 -0
  12. package/src/api/v3/ai/attach.ts +7 -2
  13. package/src/api/v3/ai/replacement.ts +101 -18
  14. package/src/api/v3/ai/resolve.ts +2 -2
  15. package/src/api/v3/ai/review.ts +177 -3
  16. package/src/api/v3/index.ts +1 -0
  17. package/src/api/v3/runtime/collab.ts +462 -0
  18. package/src/api/v3/runtime/document.ts +503 -20
  19. package/src/api/v3/runtime/geometry.ts +97 -0
  20. package/src/api/v3/runtime/layout.ts +744 -0
  21. package/src/api/v3/runtime/perf-probe.ts +14 -0
  22. package/src/api/v3/runtime/viewport.ts +9 -8
  23. package/src/api/v3/ui/_types.ts +149 -55
  24. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  25. package/src/api/v3/ui/debug.ts +115 -2
  26. package/src/api/v3/ui/index.ts +13 -0
  27. package/src/api/v3/ui/overlays.ts +0 -8
  28. package/src/api/v3/ui/surface.ts +56 -0
  29. package/src/api/v3/ui/viewport.ts +22 -9
  30. package/src/core/commands/image-commands.ts +1 -0
  31. package/src/core/commands/index.ts +6 -0
  32. package/src/core/schema/text-schema.ts +43 -5
  33. package/src/core/selection/mapping.ts +8 -1
  34. package/src/core/selection/review-anchors.ts +5 -1
  35. package/src/core/state/text-transaction.ts +8 -2
  36. package/src/io/export/serialize-revisions.ts +149 -1
  37. package/src/io/normalize/normalize-text.ts +6 -0
  38. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  39. package/src/io/ooxml/parse-fields.ts +24 -2
  40. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  41. package/src/io/ooxml/parse-main-document.ts +153 -9
  42. package/src/io/ooxml/parse-numbering.ts +20 -0
  43. package/src/io/ooxml/parse-revisions.ts +19 -8
  44. package/src/io/opc/package-reader.ts +98 -8
  45. package/src/model/anchor.ts +4 -3
  46. package/src/model/canonical-document.ts +220 -2
  47. package/src/model/canonical-hash.ts +221 -0
  48. package/src/model/canonical-layout-inputs.ts +245 -6
  49. package/src/model/layout/index.ts +1 -0
  50. package/src/model/layout/page-graph-types.ts +118 -1
  51. package/src/model/review/revision-types.ts +14 -3
  52. package/src/preservation/store.ts +20 -4
  53. package/src/review/README.md +1 -1
  54. package/src/review/store/revision-actions.ts +14 -2
  55. package/src/runtime/collab/event-types.ts +67 -1
  56. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  57. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  58. package/src/runtime/document-heading-outline.ts +147 -0
  59. package/src/runtime/document-navigation.ts +8 -243
  60. package/src/runtime/document-runtime.ts +240 -97
  61. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  62. package/src/runtime/formatting/layout-inputs.ts +38 -5
  63. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  64. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  65. package/src/runtime/geometry/caret-geometry.ts +5 -6
  66. package/src/runtime/geometry/geometry-facet.ts +60 -10
  67. package/src/runtime/geometry/geometry-index.ts +591 -20
  68. package/src/runtime/geometry/geometry-types.ts +59 -0
  69. package/src/runtime/geometry/hit-test.ts +11 -1
  70. package/src/runtime/geometry/overlay-rects.ts +5 -3
  71. package/src/runtime/geometry/project-anchors.ts +1 -1
  72. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  73. package/src/runtime/layout/index.ts +6 -0
  74. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  75. package/src/runtime/layout/layout-engine-version.ts +181 -16
  76. package/src/runtime/layout/layout-facet-types.ts +6 -0
  77. package/src/runtime/layout/page-graph.ts +21 -4
  78. package/src/runtime/layout/paginated-layout-engine.ts +139 -15
  79. package/src/runtime/layout/project-block-fragments.ts +265 -7
  80. package/src/runtime/layout/public-facet.ts +78 -24
  81. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  82. package/src/runtime/layout/table-row-split.ts +92 -35
  83. package/src/runtime/prerender/cache-envelope.ts +2 -2
  84. package/src/runtime/prerender/cache-key.ts +5 -4
  85. package/src/runtime/prerender/customxml-cache.ts +0 -1
  86. package/src/runtime/render/render-kernel.ts +1 -1
  87. package/src/runtime/revision-runtime.ts +112 -10
  88. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  89. package/src/runtime/scopes/action-validation.ts +22 -2
  90. package/src/runtime/scopes/capabilities.ts +316 -0
  91. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  92. package/src/runtime/scopes/compiler-service.ts +108 -4
  93. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  94. package/src/runtime/scopes/create-issue.ts +5 -5
  95. package/src/runtime/scopes/evidence.ts +91 -0
  96. package/src/runtime/scopes/formatting/apply.ts +2 -0
  97. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  98. package/src/runtime/scopes/index.ts +54 -0
  99. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  100. package/src/runtime/scopes/layout-evidence.ts +374 -0
  101. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  102. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  103. package/src/runtime/scopes/replacement/apply.ts +97 -34
  104. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  105. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  106. package/src/runtime/scopes/visualization.ts +28 -0
  107. package/src/runtime/surface-projection.ts +44 -5
  108. package/src/runtime/telemetry/perf-probe.ts +216 -0
  109. package/src/runtime/virtualized-rendering.ts +36 -1
  110. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  111. package/src/runtime/workflow/coordinator.ts +39 -11
  112. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  113. package/src/runtime/workflow/index.ts +3 -0
  114. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  115. package/src/runtime/workflow/overlay-lanes.ts +168 -10
  116. package/src/runtime/workflow/overlay-store.ts +2 -2
  117. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  118. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  119. package/src/session/_sync-legacy.ts +17 -27
  120. package/src/session/import/loader.ts +6 -4
  121. package/src/session/import/source-package-evidence.ts +186 -2
  122. package/src/session/index.ts +5 -6
  123. package/src/session/session.ts +30 -56
  124. package/src/session/types.ts +8 -13
  125. package/src/shell/session-bootstrap.ts +155 -81
  126. package/src/ui/WordReviewEditor.tsx +520 -12
  127. package/src/ui/editor-shell-view.tsx +14 -4
  128. package/src/ui/editor-surface-controller.tsx +5 -3
  129. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  130. package/src/ui/presence-overlay-lane.ts +0 -1
  131. package/src/ui/ui-controller-factory.ts +7 -0
  132. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  133. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  134. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  135. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  136. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  137. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  138. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  139. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  140. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  141. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  142. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  143. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  144. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  145. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  146. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  147. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  148. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  149. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  150. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  151. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  152. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  153. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  154. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  155. package/src/ui-tailwind/debug/README.md +4 -1
  156. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  157. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  158. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  159. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  160. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  161. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  162. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  163. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  164. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  165. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  166. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  167. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  168. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  169. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  170. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  171. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  172. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  173. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  174. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  175. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  176. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  177. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  178. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  179. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  180. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  181. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  182. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  183. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  184. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  185. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  186. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  187. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  188. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  189. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  190. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -0,0 +1,231 @@
1
+ import type {
2
+ CommentSidebarSnapshot,
3
+ TrackedChangesSnapshot,
4
+ WorkflowMarkupSnapshot,
5
+ } from "../../api/public-types.ts";
6
+
7
+ export type WorkflowWordFieldAvailability = "available" | "partial" | "missing";
8
+
9
+ export interface WorkflowWordFieldMatrixRowLike {
10
+ readonly group: string;
11
+ readonly field: string;
12
+ readonly primaryOwner: string;
13
+ readonly wordStatus: WorkflowWordFieldAvailability;
14
+ readonly wordEvidencePath: string;
15
+ readonly routingNote?: string;
16
+ }
17
+
18
+ export type WorkflowReviewWordFieldFact =
19
+ | "clean-vs-markup-page-counts"
20
+ | "revision-and-comment-counts"
21
+ | "bookmark-and-comment-anchors";
22
+
23
+ export type WorkflowReviewWordFieldRepresentation =
24
+ | "durable-workflow-review-posture"
25
+ | "explicit-anchor-evidence"
26
+ | "missing-matrix-row"
27
+ | "word-fact-missing";
28
+
29
+ export interface WorkflowReviewWordFieldCalibrationFact {
30
+ readonly fact: WorkflowReviewWordFieldFact;
31
+ readonly matrixGroup: string;
32
+ readonly matrixField: string;
33
+ readonly wordEvidencePath: string;
34
+ readonly wordStatus: WorkflowWordFieldAvailability | "row-missing";
35
+ readonly representation: WorkflowReviewWordFieldRepresentation;
36
+ readonly l06Surfaces: readonly string[];
37
+ readonly downstreamRoutes: readonly string[];
38
+ readonly localEvidence: Readonly<Record<string, number | string | boolean>>;
39
+ readonly note: string;
40
+ }
41
+
42
+ export interface WorkflowReviewWordFieldCalibration {
43
+ readonly schemaVersion: "l06-word-field-matrix-calibration/v0";
44
+ readonly facts: readonly WorkflowReviewWordFieldCalibrationFact[];
45
+ readonly summary: {
46
+ readonly representedFacts: number;
47
+ readonly explicitAnchorEvidenceFacts: number;
48
+ readonly missingMatrixRows: number;
49
+ readonly missingWordFacts: number;
50
+ readonly downstreamRouteCount: number;
51
+ };
52
+ }
53
+
54
+ export interface WorkflowReviewWordFieldCalibrationInput {
55
+ readonly rows: readonly WorkflowWordFieldMatrixRowLike[];
56
+ readonly workflowMarkup?: WorkflowMarkupSnapshot;
57
+ readonly comments?: CommentSidebarSnapshot;
58
+ readonly trackedChanges?: TrackedChangesSnapshot;
59
+ }
60
+
61
+ const EXPECTED_FACTS = [
62
+ {
63
+ fact: "clean-vs-markup-page-counts" as const,
64
+ group: "reviewMarkup",
65
+ field: "cleanVsMarkupPageCounts",
66
+ representation: "durable-workflow-review-posture" as const,
67
+ surfaces: [
68
+ "WorkflowMarkupModePolicy",
69
+ "WorkflowMarkupSnapshot.revisions",
70
+ "TrackedChangesSnapshot.revisions",
71
+ ],
72
+ downstreamRoutes: ["L04 clean-vs-markup page delta", "L11 review markup visibility"],
73
+ note:
74
+ "L06 owns the review posture and revision visibility decision; page-count deltas remain L04/L11 evidence consumers, not L06 geometry.",
75
+ },
76
+ {
77
+ fact: "revision-and-comment-counts" as const,
78
+ group: "reviewMarkup",
79
+ field: "revisionAndCommentCounts",
80
+ representation: "durable-workflow-review-posture" as const,
81
+ surfaces: [
82
+ "WorkflowMarkupSnapshot.revisions",
83
+ "WorkflowMarkupSnapshot.comments",
84
+ "TrackedChangesSnapshot.revisions",
85
+ "CommentSidebarSnapshot.threads",
86
+ ],
87
+ downstreamRoutes: ["L11 review/comment lane visibility"],
88
+ note:
89
+ "L06 review stores and markup snapshots carry the durable revision/comment counts before presentation paints them.",
90
+ },
91
+ {
92
+ fact: "bookmark-and-comment-anchors" as const,
93
+ group: "anchorsAndObjects",
94
+ field: "bookmarkAndCommentAnchors",
95
+ representation: "explicit-anchor-evidence" as const,
96
+ surfaces: [
97
+ "CommentSidebarSnapshot.threads[].anchor",
98
+ "WorkflowMarkupSnapshot.comments[].anchor",
99
+ "WorkflowMarkupSnapshot.revisions[].anchor",
100
+ ],
101
+ downstreamRoutes: [
102
+ "L04/L05 anchor geometry lookup",
103
+ "L11 comment/bookmark visual placement",
104
+ "L08 bookmark/scope semantic joins",
105
+ ],
106
+ note:
107
+ "Comment and revision anchors are durable L06 review evidence; bookmark placement stays an explicit routed fact rather than an L06 visual/geometry claim.",
108
+ },
109
+ ] as const;
110
+
111
+ export function calibrateWorkflowReviewWordFieldMatrix(
112
+ input: WorkflowReviewWordFieldCalibrationInput,
113
+ ): WorkflowReviewWordFieldCalibration {
114
+ const facts = EXPECTED_FACTS.map((spec) => {
115
+ const row = input.rows.find(
116
+ (candidate) =>
117
+ candidate.primaryOwner === "L06" &&
118
+ candidate.group === spec.group &&
119
+ candidate.field === spec.field,
120
+ );
121
+ return buildFact(spec, row, input);
122
+ });
123
+
124
+ return {
125
+ schemaVersion: "l06-word-field-matrix-calibration/v0",
126
+ facts,
127
+ summary: {
128
+ representedFacts: facts.filter(
129
+ (fact) => fact.representation === "durable-workflow-review-posture",
130
+ ).length,
131
+ explicitAnchorEvidenceFacts: facts.filter(
132
+ (fact) => fact.representation === "explicit-anchor-evidence",
133
+ ).length,
134
+ missingMatrixRows: facts.filter((fact) => fact.representation === "missing-matrix-row")
135
+ .length,
136
+ missingWordFacts: facts.filter((fact) => fact.representation === "word-fact-missing")
137
+ .length,
138
+ downstreamRouteCount: new Set(facts.flatMap((fact) => fact.downstreamRoutes)).size,
139
+ },
140
+ };
141
+ }
142
+
143
+ function buildFact(
144
+ spec: (typeof EXPECTED_FACTS)[number],
145
+ row: WorkflowWordFieldMatrixRowLike | undefined,
146
+ input: WorkflowReviewWordFieldCalibrationInput,
147
+ ): WorkflowReviewWordFieldCalibrationFact {
148
+ if (!row) {
149
+ return {
150
+ fact: spec.fact,
151
+ matrixGroup: spec.group,
152
+ matrixField: spec.field,
153
+ wordEvidencePath: "",
154
+ wordStatus: "row-missing",
155
+ representation: "missing-matrix-row",
156
+ l06Surfaces: spec.surfaces,
157
+ downstreamRoutes: spec.downstreamRoutes,
158
+ localEvidence: collectLocalEvidence(spec.fact, input),
159
+ note: `Missing L06-owned Word field matrix row for ${spec.group}.${spec.field}.`,
160
+ };
161
+ }
162
+
163
+ const representation =
164
+ row.wordStatus === "missing" ? "word-fact-missing" : spec.representation;
165
+
166
+ return {
167
+ fact: spec.fact,
168
+ matrixGroup: row.group,
169
+ matrixField: row.field,
170
+ wordEvidencePath: row.wordEvidencePath,
171
+ wordStatus: row.wordStatus,
172
+ representation,
173
+ l06Surfaces: spec.surfaces,
174
+ downstreamRoutes: spec.downstreamRoutes,
175
+ localEvidence: collectLocalEvidence(spec.fact, input),
176
+ note: row.wordStatus === "missing" ? row.routingNote ?? "Word fact is missing." : spec.note,
177
+ };
178
+ }
179
+
180
+ function collectLocalEvidence(
181
+ fact: WorkflowReviewWordFieldFact,
182
+ input: WorkflowReviewWordFieldCalibrationInput,
183
+ ): Readonly<Record<string, number | string | boolean>> {
184
+ const markupRevisionCount = input.workflowMarkup?.revisions.length ?? 0;
185
+ const markupCommentCount = input.workflowMarkup?.comments.length ?? 0;
186
+ const trackedRevisionCount = input.trackedChanges?.revisions.length ?? markupRevisionCount;
187
+ const sidebarCommentCount = input.comments?.threads.length ?? markupCommentCount;
188
+
189
+ switch (fact) {
190
+ case "clean-vs-markup-page-counts":
191
+ return {
192
+ hasMarkupPostureSurface: true,
193
+ markupRevisionCount,
194
+ trackedRevisionCount,
195
+ ownsPageGeometry: false,
196
+ };
197
+ case "revision-and-comment-counts":
198
+ return {
199
+ markupRevisionCount,
200
+ markupCommentCount,
201
+ trackedRevisionCount,
202
+ sidebarCommentCount,
203
+ reconciledCounts:
204
+ markupRevisionCount === trackedRevisionCount &&
205
+ markupCommentCount === sidebarCommentCount,
206
+ };
207
+ case "bookmark-and-comment-anchors":
208
+ return {
209
+ commentAnchorCount: countAnchors(input.workflowMarkup?.comments, input.comments?.threads),
210
+ revisionAnchorCount: countAnchors(input.workflowMarkup?.revisions, input.trackedChanges?.revisions),
211
+ detachedAnchorCount:
212
+ countDetachedAnchors(input.workflowMarkup?.comments, input.comments?.threads) +
213
+ countDetachedAnchors(input.workflowMarkup?.revisions, input.trackedChanges?.revisions),
214
+ ownsBookmarkGeometry: false,
215
+ };
216
+ }
217
+ }
218
+
219
+ function countAnchors(
220
+ primary: readonly { readonly anchor?: { readonly kind: string } }[] | undefined,
221
+ fallback: readonly { readonly anchor?: { readonly kind: string } }[] | undefined,
222
+ ): number {
223
+ return (primary ?? fallback ?? []).filter((item) => item.anchor != null).length;
224
+ }
225
+
226
+ function countDetachedAnchors(
227
+ primary: readonly { readonly anchor?: { readonly kind: string } }[] | undefined,
228
+ fallback: readonly { readonly anchor?: { readonly kind: string } }[] | undefined,
229
+ ): number {
230
+ return (primary ?? fallback ?? []).filter((item) => item.anchor?.kind === "detached").length;
231
+ }
@@ -1,38 +1,28 @@
1
1
  /**
2
- * Transitional sync entry — wraps `loadDocxEditorSession` from the
3
- * legacy `src/io/docx-session.ts` for the two sync call sites inside
4
- * `src/ui/editor-runtime-boundary.ts` that cannot (yet) await.
2
+ * Transitional sync entry — wraps the in-layer sync loader for the
3
+ * shell/UI call site that still needs a synchronous first-paint
4
+ * decision.
5
5
  *
6
6
  * Why this module exists.
7
7
  * =======================
8
8
  * `DocxSession.open()` is async per the architecture (the loader
9
- * yields between parse stages so the browser can paint). Two
10
- * `editor-runtime-boundary` paths need a sync load:
9
+ * yields between parse stages so the browser can paint). A small
10
+ * compatibility surface still needs a sync load:
11
11
  *
12
12
  * 1. `createRuntime`'s SSR / Node fallback — runs when no
13
13
  * `preloadedDocxSession` was produced by the async warm path but
14
14
  * `initialDocx` bytes are present. `createRuntime` is sync, so a
15
15
  * sync loader is the only entry that keeps the mount simple.
16
- * Slice 5e-9 is expected to eliminate this fallback altogether
17
- * by requiring the async warm path to run, OR by making
18
- * `createRuntime` async-capable.
19
- *
20
- * 2. `resolvePackageBackedExportSession` — resolves an embedded
21
- * source-package blob to decide the export barrier for
22
- * snapshot-only mounts. The result drives the compatibility
23
- * report that shows on first paint; deferring to first export
24
- * would regress the blocked-export UX. Slice 5e-9 may thread a
25
- * preloaded barrier session through the async warm path
26
- * instead.
16
+ * A future shell cleanup can eliminate this fallback by requiring
17
+ * the async warm path to run, or by making `createRuntime`
18
+ * async-capable.
27
19
  *
28
20
  * Isolation.
29
21
  * ==========
30
- * By routing both sync call sites through this one module, the
31
- * `src/ui/editor-runtime-boundary.ts` file no longer imports directly
32
- * from `src/io/docx-session.ts`. The transit is pinned as a
33
- * WARNING-level debt exception in
34
- * `scripts/ci-check-session-layer-purity.mjs`, so the legacy-import
35
- * surface shrinks to one narrow file that Slice 5e-9 deletes whole.
22
+ * By routing sync compatibility through this one module, callers do
23
+ * not import from the back-compat `src/io/docx-session.ts` shim. This
24
+ * module reaches only the in-session loader, so the purity checker has
25
+ * zero debt exceptions.
36
26
  *
37
27
  * Not exported from the session barrel.
38
28
  * =====================================
@@ -46,16 +36,16 @@
46
36
  import { loadDocxSessionSync } from "./import/loader.ts";
47
37
 
48
38
  /**
49
- * Sync docx load — returns the legacy-shaped handle the two
50
- * `editor-runtime-boundary` sync call sites currently consume.
39
+ * Sync docx load — returns the legacy-shaped handle the
40
+ * `editor-runtime-boundary` SSR/Node raw-DOCX fallback currently consumes.
51
41
  *
52
42
  * Options + result are typed via `Parameters<>` / `ReturnType<>` so
53
43
  * this module does not force its loader-types dependency to widen
54
44
  * publicly.
55
45
  *
56
- * Removed in Slice 5e-9 when the sync call sites either flip to
57
- * `await DocxSession.open(...)` or rely on a preloaded session
58
- * produced by the async warm path.
46
+ * Kept only for sync compatibility. New code should use
47
+ * `await new DocxSession().open(...)` or
48
+ * `await new DocxSession().reopenFromSnapshot(...)`.
59
49
  */
60
50
  export type SyncLegacyOpenOptions = Parameters<typeof loadDocxSessionSync>[0];
61
51
 
@@ -378,15 +378,17 @@ export async function loadDocxSessionAsync(
378
378
  const protectionSnapshot = buildProtectionSnapshot(documentProtection, []);
379
379
 
380
380
  // Chart previews (`previewMediaId` is host-dependent) aren't cached
381
- // in the envelope. C3b: when onChartPreviewsReady is provided, defer
382
- // resolution out of the critical path. Otherwise block (legacy behavior).
381
+ // in the envelope. Cold opens preserve the legacy blocking fallback
382
+ // when no callback is supplied. Warm laycache opens must not re-enter
383
+ // chart parsing on the critical path, so schedule async resolution even
384
+ // for hosts that did not provide a callback.
383
385
  let documentWithChartPreviews: CanonicalDocument;
384
- if (options.onChartPreviewsReady) {
386
+ if (options.onChartPreviewsReady || laycacheEnvelope) {
385
387
  scheduleChartPreviewResolution(
386
388
  canonicalDocument,
387
389
  sourcePackage,
388
390
  options.hostAdapter,
389
- options.onChartPreviewsReady,
391
+ options.onChartPreviewsReady ?? (() => {}),
390
392
  );
391
393
  documentWithChartPreviews = canonicalDocument as CanonicalDocument;
392
394
  } else {
@@ -9,7 +9,8 @@ import {
9
9
  } from "../../io/ooxml/part-manifest.ts";
10
10
  import { parseXml, parseXmlWithOffsets } from "../../io/ooxml/xml-parser.ts";
11
11
  import type { XmlElementNode } from "../../io/ooxml/xml-element.ts";
12
- import { localName } from "../../io/ooxml/xml-attr-helpers.ts";
12
+ import { streamWalkFieldGroups } from "../../io/ooxml/parse-fields.ts";
13
+ import { localName, readStringAttr, textContent } from "../../io/ooxml/xml-attr-helpers.ts";
13
14
  import {
14
15
  resolveMainDocumentPartPath,
15
16
  } from "./part-discovery.ts";
@@ -26,6 +27,7 @@ export interface SourcePackageElementCounts {
26
27
  fieldMarkers: number;
27
28
  fieldInstructions: number;
28
29
  simpleFields: number;
30
+ hyperlinks: number;
29
31
  bookmarks: number;
30
32
  footnotes: number;
31
33
  endnotes: number;
@@ -36,6 +38,7 @@ export interface SourcePackagePartEvidence {
36
38
  storyKind: SourcePackageStoryKind;
37
39
  contentType: string | null;
38
40
  byteLength: number;
41
+ sha256Hex: string;
39
42
  relationshipCount: number;
40
43
  counts: SourcePackageElementCounts;
41
44
  }
@@ -60,6 +63,20 @@ export interface SourcePackageLocationEvidence {
60
63
  endOffset?: number;
61
64
  }
62
65
 
66
+ export interface SourcePackageFieldSequenceEvidence
67
+ extends SourcePackageLocationEvidence {
68
+ fieldSequenceKind: "complex" | "simple" | "hyperlink";
69
+ instructionFamily: string;
70
+ instructionHash?: string;
71
+ normalizedInstructionHash?: string;
72
+ resultHash?: string;
73
+ instructionLength?: number;
74
+ resultLength?: number;
75
+ cachedDisplayAvailable: boolean;
76
+ relationshipId?: string;
77
+ anchor?: string;
78
+ }
79
+
63
80
  export interface SourcePackageStoryPartDescriptor {
64
81
  sourceId: string;
65
82
  partPath: string;
@@ -131,6 +148,7 @@ export interface SourcePackageLayoutInventoryEvidence {
131
148
  sections: SourcePackageSectionDescriptorEvidence[];
132
149
  tables: SourcePackageLocationEvidence[];
133
150
  fields: SourcePackageLocationEvidence[];
151
+ fieldSequences: SourcePackageFieldSequenceEvidence[];
134
152
  drawings: SourcePackageLocationEvidence[];
135
153
  vmlShapes: SourcePackageLocationEvidence[];
136
154
  contentControls: SourcePackageLocationEvidence[];
@@ -203,6 +221,7 @@ const ZERO_COUNTS: SourcePackageElementCounts = {
203
221
  fieldMarkers: 0,
204
222
  fieldInstructions: 0,
205
223
  simpleFields: 0,
224
+ hyperlinks: 0,
206
225
  bookmarks: 0,
207
226
  footnotes: 0,
208
227
  endnotes: 0,
@@ -305,6 +324,7 @@ function summarizePart(
305
324
  storyKind,
306
325
  contentType: part.contentType,
307
326
  byteLength: part.bytes.byteLength,
327
+ sha256Hex: sha256Hex(part.bytes),
308
328
  relationshipCount: part.relationships.length,
309
329
  counts: countPartElements(part, storyKind, parseErrors),
310
330
  };
@@ -384,6 +404,9 @@ function countElements(root: XmlElementNode): SourcePackageElementCounts {
384
404
  case "fldSimple":
385
405
  counts.simpleFields += 1;
386
406
  break;
407
+ case "hyperlink":
408
+ counts.hyperlinks += 1;
409
+ break;
387
410
  case "bookmarkStart":
388
411
  counts.bookmarks += 1;
389
412
  break;
@@ -421,6 +444,7 @@ function collectLayoutInventory(
421
444
  | "sections"
422
445
  | "tables"
423
446
  | "fields"
447
+ | "fieldSequences"
424
448
  | "drawings"
425
449
  | "vmlShapes"
426
450
  | "contentControls"
@@ -431,6 +455,7 @@ function collectLayoutInventory(
431
455
  sections: [],
432
456
  tables: [],
433
457
  fields: [],
458
+ fieldSequences: [],
434
459
  drawings: [],
435
460
  vmlShapes: [],
436
461
  contentControls: [],
@@ -457,7 +482,18 @@ function collectLayoutInventory(
457
482
 
458
483
  let root: XmlElementNode;
459
484
  try {
460
- root = parseXmlWithOffsets(new TextDecoder("utf-8").decode(part.bytes));
485
+ const sourceXml = new TextDecoder("utf-8").decode(part.bytes);
486
+ root = parseXmlWithOffsets(sourceXml);
487
+ collectFieldSequences(
488
+ root,
489
+ sourceXml,
490
+ {
491
+ partPath: part.path,
492
+ storyKind,
493
+ relationships: sourceRelationshipByPart.get(part.path) ?? [],
494
+ },
495
+ locations.fieldSequences,
496
+ );
461
497
  } catch (error) {
462
498
  parseErrors.push({
463
499
  partPath: part.path,
@@ -606,6 +642,7 @@ function collectPartLocations(
606
642
  case "fldSimple":
607
643
  case "fldChar":
608
644
  case "instrText":
645
+ case "hyperlink":
609
646
  locations.fields.push(locationFor(context, node, name, nextOrdinal(name)));
610
647
  break;
611
648
  case "drawing": {
@@ -652,6 +689,152 @@ function collectPartLocations(
652
689
  visit(root);
653
690
  }
654
691
 
692
+ function collectFieldSequences(
693
+ root: XmlElementNode,
694
+ sourceXml: string,
695
+ context: {
696
+ partPath: string;
697
+ storyKind: SourcePackageStoryKind;
698
+ relationships: SourcePackageRelationshipEvidence[];
699
+ },
700
+ fieldSequences: SourcePackageFieldSequenceEvidence[],
701
+ ): void {
702
+ let sequenceOrdinal = fieldSequences.length;
703
+ const nextSequenceOrdinal = (): number => {
704
+ sequenceOrdinal += 1;
705
+ return sequenceOrdinal;
706
+ };
707
+
708
+ for (const group of streamWalkFieldGroups(sourceXml)) {
709
+ const ordinal = nextSequenceOrdinal();
710
+ const instruction = group.instruction.trim();
711
+ const resultText = textFromXmlFragment(group.displayContent);
712
+ fieldSequences.push({
713
+ sourceId: sequenceSourceId(context.partPath, ordinal),
714
+ partPath: context.partPath,
715
+ storyKind: context.storyKind,
716
+ element: "fldCharSequence",
717
+ ordinal,
718
+ startOffset: group.start,
719
+ endOffset: group.end,
720
+ fieldSequenceKind: "complex",
721
+ instructionFamily: instructionFamily(instruction),
722
+ ...hashedInstruction(instruction),
723
+ ...hashedResult(resultText),
724
+ cachedDisplayAvailable: resultText.length > 0,
725
+ });
726
+ }
727
+
728
+ const visit = (node: XmlElementNode): void => {
729
+ const name = localName(node.name);
730
+ if (name === "fldSimple") {
731
+ const ordinal = nextSequenceOrdinal();
732
+ const instruction = (readStringAttr(node, "w:instr") ?? "").trim();
733
+ const resultText = textContent(node);
734
+ fieldSequences.push({
735
+ ...locationFor(context, node, "fieldSequence", ordinal),
736
+ element: "fldSimple",
737
+ fieldSequenceKind: "simple",
738
+ instructionFamily: instructionFamily(instruction),
739
+ ...hashedInstruction(instruction),
740
+ ...hashedResult(resultText),
741
+ cachedDisplayAvailable: resultText.length > 0,
742
+ });
743
+ } else if (name === "hyperlink") {
744
+ const ordinal = nextSequenceOrdinal();
745
+ const relationshipId = readStringAttr(node, "r:id");
746
+ const anchor = readStringAttr(node, "w:anchor");
747
+ const target = relationshipId
748
+ ? context.relationships.find(
749
+ (relationship) => relationship.relationshipId === relationshipId,
750
+ )?.target
751
+ : undefined;
752
+ const instruction = [
753
+ "HYPERLINK",
754
+ anchor ? "anchor" : undefined,
755
+ anchor,
756
+ relationshipId ? "relationship" : undefined,
757
+ relationshipId,
758
+ target ? "target" : undefined,
759
+ target,
760
+ ].filter((value): value is string => Boolean(value)).join(" ");
761
+ const resultText = textContent(node);
762
+ fieldSequences.push({
763
+ ...locationFor(context, node, "fieldSequence", ordinal),
764
+ element: "hyperlink",
765
+ fieldSequenceKind: "hyperlink",
766
+ instructionFamily: "HYPERLINK",
767
+ ...hashedInstruction(instruction),
768
+ ...hashedResult(resultText),
769
+ cachedDisplayAvailable: resultText.length > 0,
770
+ ...(relationshipId !== undefined ? { relationshipId } : {}),
771
+ ...(anchor !== undefined ? { anchor } : {}),
772
+ });
773
+ }
774
+
775
+ for (const child of node.children) {
776
+ if (child.type === "element") {
777
+ visit(child);
778
+ }
779
+ }
780
+ };
781
+
782
+ visit(root);
783
+ }
784
+
785
+ function sequenceSourceId(partPath: string, ordinal: number): string {
786
+ return `${partSourceId(partPath)}#fieldSequence:${ordinal}`;
787
+ }
788
+
789
+ function instructionFamily(instruction: string): string {
790
+ const normalized = normalizeInstruction(instruction);
791
+ const family = normalized.match(/^[A-Z0-9_]+/u)?.[0] ?? "";
792
+ return family || "UNKNOWN";
793
+ }
794
+
795
+ function hashedInstruction(
796
+ instruction: string,
797
+ ): Pick<
798
+ SourcePackageFieldSequenceEvidence,
799
+ "instructionHash" | "normalizedInstructionHash" | "instructionLength"
800
+ > {
801
+ if (instruction.length === 0) {
802
+ return { instructionLength: 0 };
803
+ }
804
+ const normalized = normalizeInstruction(instruction);
805
+ return {
806
+ instructionHash: hashText(instruction),
807
+ normalizedInstructionHash: hashText(normalized),
808
+ instructionLength: instruction.length,
809
+ };
810
+ }
811
+
812
+ function hashedResult(
813
+ resultText: string,
814
+ ): Pick<SourcePackageFieldSequenceEvidence, "resultHash" | "resultLength"> {
815
+ return {
816
+ resultHash: hashText(resultText),
817
+ resultLength: resultText.length,
818
+ };
819
+ }
820
+
821
+ function hashText(value: string): string {
822
+ return sha256Hex(new TextEncoder().encode(value));
823
+ }
824
+
825
+ function normalizeInstruction(instruction: string): string {
826
+ return instruction.trim().replace(/\s+/gu, " ").toUpperCase();
827
+ }
828
+
829
+ function textFromXmlFragment(xml: string): string {
830
+ if (xml.trim().length === 0) return "";
831
+ try {
832
+ return textContent(parseXml(`<root>${xml}</root>`));
833
+ } catch {
834
+ return xml.replace(/<[^>]*>/gu, "").replace(/\s+/gu, " ").trim();
835
+ }
836
+ }
837
+
655
838
  function locationFor(
656
839
  context: {
657
840
  partPath: string;
@@ -847,6 +1030,7 @@ function aggregateElementCounts(
847
1030
  aggregate.fieldMarkers += counts.fieldMarkers;
848
1031
  aggregate.fieldInstructions += counts.fieldInstructions;
849
1032
  aggregate.simpleFields += counts.simpleFields;
1033
+ aggregate.hyperlinks += counts.hyperlinks;
850
1034
  aggregate.bookmarks += counts.bookmarks;
851
1035
  aggregate.footnotes += counts.footnotes;
852
1036
  aggregate.endnotes += counts.endnotes;
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Public barrel for Layer 01 (package / session).
3
3
  *
4
- * Reserved shape — matches `docs/architecture/01-package-session.md`
4
+ * Public shape — matches `docs/architecture/01-package-session.md`
5
5
  * § Public entry points. Consumers should import from `src/session`
6
6
  * (or the `@beyondwork/docx-react-component/session` package subpath)
7
- * rather than reaching into `src/io/docx-session.ts`. The legacy entry
8
- * points remain exported from the root barrel for back-compat until
9
- * Slice 5 deletes them.
7
+ * rather than reaching into the back-compat `src/io/docx-session.ts`
8
+ * shim.
10
9
  */
11
10
 
12
11
  export {
@@ -17,7 +16,7 @@ export {
17
16
  export { isReopenBarrier } from "./types.ts";
18
17
  export type { EditorSessionState } from "./session-state.ts";
19
18
  export type {
20
- // Reserved Layer-01 contract types.
19
+ // Layer-01 contract types.
21
20
  OpenOptions,
22
21
  OpenResult,
23
22
  ExportOptions,
@@ -28,7 +27,7 @@ export type {
28
27
  EmbeddedDocumentManifest,
29
28
  // P8 Step 7 offload surface.
30
29
  ReopenBarrier,
31
- // Transitional re-exports from the legacy IO layer.
30
+ // Back-compat public types also exposed from the session package.
32
31
  ExportResult,
33
32
  EditorHostAdapter,
34
33
  ExportDocxOptions,