@beyondwork/docx-react-component 1.0.104 → 1.0.106

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/api/public-types.ts +3 -0
  3. package/src/api/v3/_create.ts +9 -2
  4. package/src/api/v3/ai/_audit-reference.ts +28 -0
  5. package/src/api/v3/ai/_pe2-evidence.ts +419 -0
  6. package/src/api/v3/ai/attach.ts +22 -2
  7. package/src/api/v3/ai/bundle.ts +18 -6
  8. package/src/api/v3/ai/inspect.ts +12 -2
  9. package/src/api/v3/ai/replacement.ts +124 -0
  10. package/src/api/v3/index.ts +7 -0
  11. package/src/api/v3/ui/_types.ts +139 -0
  12. package/src/api/v3/ui/index.ts +9 -0
  13. package/src/api/v3/ui/overlays.ts +104 -0
  14. package/src/api/v3/ui/viewport.ts +97 -0
  15. package/src/model/layout/index.ts +3 -0
  16. package/src/model/layout/page-graph-types.ts +118 -0
  17. package/src/model/layout/runtime-page-graph-types.ts +13 -0
  18. package/src/runtime/document-runtime.ts +39 -18
  19. package/src/runtime/event-refresh-hints.ts +33 -6
  20. package/src/runtime/geometry/geometry-facet.ts +9 -1
  21. package/src/runtime/geometry/geometry-index.ts +461 -10
  22. package/src/runtime/geometry/geometry-types.ts +6 -0
  23. package/src/runtime/geometry/object-handles.ts +7 -4
  24. package/src/runtime/layout/layout-engine-instance.ts +2 -0
  25. package/src/runtime/layout/layout-engine-version.ts +36 -1
  26. package/src/runtime/layout/page-graph.ts +697 -10
  27. package/src/runtime/layout/paginated-layout-engine.ts +10 -0
  28. package/src/runtime/layout/project-block-fragments.ts +187 -8
  29. package/src/runtime/layout/public-facet.ts +236 -0
  30. package/src/runtime/prerender/graph-canonicalize.ts +14 -0
  31. package/src/runtime/workflow/index.ts +1 -0
  32. package/src/runtime/workflow/overlay-lanes.ts +228 -0
  33. package/src/ui/presence-overlay-lane.ts +131 -0
  34. package/src/ui/ui-controller-factory.ts +21 -0
@@ -24,6 +24,7 @@
24
24
  import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
25
25
  import type {
26
26
  ViewportState,
27
+ PageResidencySnapshot,
27
28
  UiListener,
28
29
  UiUnsubscribe,
29
30
  WorkflowMarkupMode,
@@ -86,6 +87,52 @@ export const subscribeMetadata: ApiV3FnMetadata = {
86
87
  rwdReference: "§UI API § ui.viewport.subscribe. Adapter delegates to UiController.subscribeViewport; throws when the active binding has no hook. Subscribe call emits one `ux.response.ui.viewport.subscribe` acknowledgement; per-tick ViewportState deliveries flow through the listener (rAF-coalesced, U7).",
87
88
  };
88
89
 
90
+ export const getPageResidencyMetadata: ApiV3FnMetadata = {
91
+ name: "ui.viewport.getPageResidency",
92
+ status: "live-with-adapter",
93
+ sourceLayer: "presentation",
94
+ liveEvidence: {
95
+ runnerTest: "test/api/v3/ui/viewport-residency.test.ts",
96
+ commit: "refactor-10-pe2-residency",
97
+ },
98
+ mockShape: {
99
+ deterministic: true,
100
+ seededFrom: "fixed",
101
+ shapeDescription: "Mock PageResidencySnapshot with residency='evicted' and status='unavailable' when no mounted controller exposes L10 residency policy.",
102
+ carriesMockFlag: true,
103
+ },
104
+ uxIntent: { uiVisible: false },
105
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-read" },
106
+ stateClass: "C-local",
107
+ persistsTo: "none",
108
+ rwdReference: "§PE2 § Virtual Page Windowing. Reads L10 page residency policy (realized/cold/evicted) without probing L05 geometry or realizing L11 DOM.",
109
+ };
110
+
111
+ export const subscribePageResidencyMetadata: ApiV3FnMetadata = {
112
+ name: "ui.viewport.subscribePageResidency",
113
+ status: "live-with-adapter",
114
+ sourceLayer: "presentation",
115
+ liveEvidence: {
116
+ runnerTest: "test/api/v3/ui/viewport-residency.test.ts",
117
+ commit: "refactor-10-pe2-residency",
118
+ },
119
+ uxIntent: {
120
+ uiVisible: true,
121
+ expectsUxResponse: "surface-refresh",
122
+ expectedDelta: "page-residency subscriber attached; future realized/cold/evicted changes propagate through the listener",
123
+ },
124
+ agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-viewport-subscribe" },
125
+ stateClass: "C-local",
126
+ persistsTo: "none",
127
+ bidirectional: true,
128
+ subscriptionShape: {
129
+ eventType: "ui.viewport.page_residency_changed",
130
+ payloadType: "PageResidencySnapshot",
131
+ coalescing: "raf",
132
+ },
133
+ rwdReference: "§PE2 § Virtual Page Windowing. Adapter delegates to UiController.subscribePageResidency; emissions are plain snapshots and do not dispatch PM transactions or force geometry rehydration.",
134
+ };
135
+
89
136
  // ----- scrollToPage (coord-10 §γ — visual-fidelity / Go-to-page UX) -----
90
137
 
91
138
  export const scrollToPageMetadata: ApiV3FnMetadata = {
@@ -245,6 +292,23 @@ export function createViewportFamily(ctx: UiApiContext) {
245
292
  notifyMarkupModeSubscribers();
246
293
  });
247
294
 
295
+ function normalizePageIndex(pageIndex: number): number {
296
+ if (!Number.isFinite(pageIndex)) return 0;
297
+ return Math.max(0, Math.floor(pageIndex));
298
+ }
299
+
300
+ function unavailableResidency(pageIndex: number): PageResidencySnapshot {
301
+ return Object.freeze({
302
+ __mock: true,
303
+ pageIndex: normalizePageIndex(pageIndex),
304
+ residency: "evicted",
305
+ status: "unavailable",
306
+ revision: 0,
307
+ source: "unavailable",
308
+ reason: "page-residency policy is not wired on the active controller",
309
+ } as const);
310
+ }
311
+
248
312
  return {
249
313
  // Slice 11 — composes scroll/dpr/zoom from `handle.geometry` with
250
314
  // width/height from the bound controller (see
@@ -279,6 +343,39 @@ export function createViewportFamily(ctx: UiApiContext) {
279
343
  });
280
344
  return unsubscribe;
281
345
  },
346
+ getPageResidency(pageIndex: number): PageResidencySnapshot {
347
+ const normalized = normalizePageIndex(pageIndex);
348
+ const resolver = ctx.binding?.controller.getPageResidency;
349
+ if (!resolver) return unavailableResidency(normalized);
350
+ return resolver(normalized);
351
+ },
352
+ subscribePageResidency(
353
+ pageIndex: number,
354
+ listener: UiListener<PageResidencySnapshot>,
355
+ ): UiUnsubscribe {
356
+ const controller = ctx.binding?.controller;
357
+ if (!controller) {
358
+ throw new Error(
359
+ "ui.viewport.subscribePageResidency: no controller bound — call ui.session.bind(controller) first",
360
+ );
361
+ }
362
+ if (!controller.subscribePageResidency) {
363
+ throw new Error(
364
+ `ui.viewport.subscribePageResidency: controller of kind "${controller.kind}" did not provide a subscribePageResidency hook`,
365
+ );
366
+ }
367
+ const normalized = normalizePageIndex(pageIndex);
368
+ const unsubscribe = controller.subscribePageResidency(normalized, listener);
369
+ emitUxResponse(ctx.handle, {
370
+ apiFn: subscribePageResidencyMetadata.name,
371
+ intent: subscribePageResidencyMetadata.uxIntent.expectedDelta ?? "",
372
+ mockOrLive: "live-with-adapter",
373
+ uiVisible: true,
374
+ expectedDelta: subscribePageResidencyMetadata.uxIntent.expectedDelta,
375
+ actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.viewport.pageResidency", pageIndex: normalized } },
376
+ });
377
+ return unsubscribe;
378
+ },
282
379
 
283
380
  // ----- scrollToPage (coord-10 §γ) -----
284
381
 
@@ -65,6 +65,9 @@ export type {
65
65
  RuntimeLayoutDivergenceKind,
66
66
  RuntimeLayoutDivergence,
67
67
  RuntimePageFrame,
68
+ RuntimePageLocalStoryInstance,
69
+ RuntimeResolvedStoryField,
70
+ RuntimeStoryAnchoredObject,
68
71
  RuntimeBlockFragment,
69
72
  RuntimeLineBox,
70
73
  RuntimeNoteAllocation,
@@ -107,6 +107,7 @@ export interface RuntimeLayoutDivergence {
107
107
  message: string;
108
108
  regionKinds?: RuntimePageRegion["kind"][];
109
109
  fragmentIds?: string[];
110
+ objectIds?: string[];
110
111
  }
111
112
 
112
113
  export interface RuntimePageFrame {
@@ -117,10 +118,111 @@ export interface RuntimePageFrame {
117
118
  displayPageNumber: number;
118
119
  physicalBoundsTwips: RuntimeTwipsRect;
119
120
  regions: RuntimeResolvedRegions;
121
+ pageLocalStories: RuntimePageLocalStoryInstance[];
120
122
  divergenceIds: string[];
121
123
  signature: string;
122
124
  }
123
125
 
126
+ export interface RuntimePageLocalStoryInstance {
127
+ instanceId: string;
128
+ storyKey: string;
129
+ pageId: string;
130
+ kind: "header" | "footer";
131
+ variant: "default" | "first" | "even" | "odd";
132
+ relationshipId: string;
133
+ sectionIndex?: number;
134
+ resolvedFields: RuntimeResolvedStoryField[];
135
+ anchoredObjects: RuntimeStoryAnchoredObject[];
136
+ measuredFrameHeightTwips: number;
137
+ signature: string;
138
+ }
139
+
140
+ export interface RuntimeResolvedStoryField {
141
+ fieldId: string;
142
+ family: string;
143
+ displayText: string;
144
+ }
145
+
146
+ export interface RuntimeStoryAnchoredObject {
147
+ objectId: string;
148
+ sourceType:
149
+ | "image"
150
+ | "drawing-frame"
151
+ | "chart-preview"
152
+ | "smartart-preview"
153
+ | "shape"
154
+ | "wordart"
155
+ | "vml-shape"
156
+ | "ole-embed"
157
+ | "opaque-inline";
158
+ display: "inline" | "floating" | "unknown";
159
+ extentTwips?: {
160
+ widthTwips: number;
161
+ heightTwips: number;
162
+ };
163
+ relationshipIds?: readonly string[];
164
+ preserveOnly: boolean;
165
+ divergenceIds: string[];
166
+ }
167
+
168
+ export type RuntimeLayoutContinuationCursor =
169
+ | RuntimeParagraphContinuationCursor
170
+ | RuntimeTableContinuationCursor;
171
+
172
+ export interface RuntimeParagraphContinuationCursor {
173
+ kind: "paragraph";
174
+ sequenceIndex: number;
175
+ sliceCount: number;
176
+ lineRange: {
177
+ from: number;
178
+ to: number;
179
+ totalLines: number;
180
+ };
181
+ continuesFromPreviousPage: boolean;
182
+ continuesToNextPage: boolean;
183
+ }
184
+
185
+ export interface RuntimeTableContinuationCursor {
186
+ kind: "table";
187
+ sequenceIndex: number;
188
+ sliceCount: number;
189
+ rowRange: {
190
+ from: number;
191
+ to: number;
192
+ totalRows: number;
193
+ };
194
+ continuesFromPreviousPage: boolean;
195
+ continuesToNextPage: boolean;
196
+ repeatedHeaderRowIndexes: readonly number[];
197
+ verticalMergeCarry: readonly RuntimeTableVerticalMergeCarry[];
198
+ }
199
+
200
+ export interface RuntimeTableVerticalMergeCarry {
201
+ columnIndex: number;
202
+ restartRowIndex: number;
203
+ }
204
+
205
+ export type RuntimeFragmentLayoutObjectKind =
206
+ | "paragraph"
207
+ | "numbered-paragraph"
208
+ | "table"
209
+ | "field-region"
210
+ | "footnote-body"
211
+ | "sdt-block"
212
+ | "opaque-block";
213
+
214
+ export interface RuntimeFragmentLayoutObject {
215
+ objectId: string;
216
+ kind: RuntimeFragmentLayoutObjectKind;
217
+ sourceBlockId: string;
218
+ paginationRole: "whole" | "slice" | "continuation";
219
+ measuredExtentTwips: {
220
+ heightTwips: number;
221
+ widthTwips?: number;
222
+ };
223
+ fieldFamilies?: readonly string[];
224
+ }
225
+
124
226
  // ---------------------------------------------------------------------------
125
227
  // Fragment types
126
228
  // ---------------------------------------------------------------------------
@@ -176,6 +278,22 @@ export interface RuntimeBlockFragment {
176
278
  to: number;
177
279
  totalRows: number;
178
280
  };
281
+ /**
282
+ * PE2 Slice 3 — typed continuation cursor for fragments that render a
283
+ * logical block over multiple page/column frames. Existing slice fields
284
+ * remain for compatibility; this cursor makes repeated-header,
285
+ * line/row-range, and vertical-merge carry explicit for debug/render
286
+ * consumers without reading DOM or canonical tables again.
287
+ */
288
+ continuation?: RuntimeLayoutContinuationCursor;
289
+ /**
290
+ * PE2 Slice 3 — measured layout-object descriptor for this fragment.
291
+ * This is the internal object protocol layer: one stable row per
292
+ * page-local fragment, expressed only in ids/enums/twips. It lets debug,
293
+ * render, and future truth joins reason about paragraph/table/note
294
+ * placement without re-reading DOM or source model objects.
295
+ */
296
+ layoutObject?: RuntimeFragmentLayoutObject;
179
297
  /**
180
298
  * Slice 5 — opaque style-chain ref derived from the block's `styleId`.
181
299
  * Used by `analyzeStylesChange` to bound invalidation to the first page
@@ -14,6 +14,10 @@
14
14
  * layer (`src/runtime/layout/page-graph.ts`).
15
15
  */
16
16
 
17
+ import type {
18
+ FooterDocument,
19
+ HeaderDocument,
20
+ } from "../canonical-document.ts";
17
21
  import type {
18
22
  RuntimeBlockFragment,
19
23
  RuntimeLayoutDivergence,
@@ -115,4 +119,13 @@ export interface BuildPageGraphInput {
115
119
  * `page-${revision}-${index}` pageId in advance.
116
120
  */
117
121
  noteAllocationsByPageIndex?: ReadonlyMap<number, RuntimeNoteAllocation[]>;
122
+ /**
123
+ * Optional canonical header/footer parts. When present, `buildPageGraph`
124
+ * resolves page-instance fields inside page-local header/footer story
125
+ * records without making render/UI consumers re-walk subparts.
126
+ */
127
+ subParts?: {
128
+ headers?: ReadonlyArray<HeaderDocument>;
129
+ footers?: ReadonlyArray<FooterDocument>;
130
+ };
118
131
  }
@@ -3271,14 +3271,6 @@ export function createDocumentRuntime(
3271
3271
  const timestamp = clock();
3272
3272
 
3273
3273
  if (suggesting) {
3274
- if (activeStory.kind !== "main") {
3275
- this.emitBlockedCommand(commandName, [{
3276
- code: "suggesting_unsupported",
3277
- message: `"${commandName}" is not supported in suggesting mode for this story.`,
3278
- }]);
3279
- return;
3280
- }
3281
-
3282
3274
  if (
3283
3275
  operation.type === "set-alignment" ||
3284
3276
  operation.type === "indent" ||
@@ -3313,9 +3305,14 @@ export function createDocumentRuntime(
3313
3305
  },
3314
3306
  timestamp,
3315
3307
  );
3308
+ const nextFullDocument = stitchActiveStoryDocument(
3309
+ persistedDocument,
3310
+ activeStory,
3311
+ nextDocument,
3312
+ );
3316
3313
  this.dispatch({
3317
3314
  type: "document.replace",
3318
- document: { ...nextDocument, updatedAt: timestamp },
3315
+ document: { ...nextFullDocument, updatedAt: timestamp },
3319
3316
  mapping: createEmptyMapping(),
3320
3317
  selection: toInternalSelectionSnapshot(result.selection),
3321
3318
  origin: createOrigin("api", timestamp),
@@ -3353,9 +3350,14 @@ export function createDocumentRuntime(
3353
3350
  },
3354
3351
  timestamp,
3355
3352
  );
3353
+ const nextFullDocument = stitchActiveStoryDocument(
3354
+ persistedDocument,
3355
+ activeStory,
3356
+ nextDocument,
3357
+ );
3356
3358
  this.dispatch({
3357
3359
  type: "document.replace",
3358
- document: { ...nextDocument, updatedAt: timestamp },
3360
+ document: { ...nextFullDocument, updatedAt: timestamp },
3359
3361
  mapping: createEmptyMapping(),
3360
3362
  selection: toInternalSelectionSnapshot(result.selection),
3361
3363
  origin: createOrigin("api", timestamp),
@@ -4187,13 +4189,6 @@ export function createDocumentRuntime(
4187
4189
  const suggesting =
4188
4190
  workflowCoordinator.getEffectiveDocumentMode(state.selection) === "suggesting";
4189
4191
  if (suggesting) {
4190
- if (activeStory.kind !== "main") {
4191
- this.emitBlockedCommand("clearHighlight", [{
4192
- code: "suggesting_unsupported",
4193
- message: `"clearHighlight" is not supported in suggesting mode for this story.`,
4194
- }]);
4195
- return;
4196
- }
4197
4192
  const segment = findSingleSelectedTextSegment(syntheticSnapshot);
4198
4193
  if (!segment) {
4199
4194
  this.emitBlockedCommand("clearHighlight", [{
@@ -4225,9 +4220,14 @@ export function createDocumentRuntime(
4225
4220
  },
4226
4221
  timestamp,
4227
4222
  );
4223
+ const nextFullDocument = stitchActiveStoryDocument(
4224
+ state.document,
4225
+ activeStory,
4226
+ nextDocument,
4227
+ );
4228
4228
  this.dispatch({
4229
4229
  type: "document.replace",
4230
- document: nextDocument,
4230
+ document: { ...nextFullDocument, updatedAt: timestamp },
4231
4231
  mapping: createEmptyMapping(),
4232
4232
  origin: createOrigin("api", timestamp),
4233
4233
  });
@@ -9223,6 +9223,27 @@ function appendPropertyChangeSuggestion(
9223
9223
  };
9224
9224
  }
9225
9225
 
9226
+ function stitchActiveStoryDocument(
9227
+ persistedDocument: CanonicalDocumentEnvelope,
9228
+ activeStory: EditorStoryTarget,
9229
+ storyDocument: CanonicalDocumentEnvelope,
9230
+ ): CanonicalDocumentEnvelope {
9231
+ if (activeStory.kind === "main") {
9232
+ return storyDocument;
9233
+ }
9234
+
9235
+ return replaceStoryBlocks(
9236
+ {
9237
+ ...persistedDocument,
9238
+ review: storyDocument.review,
9239
+ diagnostics: storyDocument.diagnostics,
9240
+ preservation: storyDocument.preservation,
9241
+ },
9242
+ activeStory,
9243
+ storyDocument.content.children,
9244
+ );
9245
+ }
9246
+
9226
9247
  function createRuntimeSuggestionChangeId(
9227
9248
  existing: CanonicalDocumentEnvelope["review"]["revisions"],
9228
9249
  timestamp: string,
@@ -26,7 +26,6 @@ export function describeEventImpact(
26
26
  case "workflow_overlay_changed":
27
27
  case "workflow_active_work_item_changed":
28
28
  case "workflow_shared_state_changed":
29
- case "workflow_visibility_policy_changed":
30
29
  case "workflow_markup_mode_policy_changed":
31
30
  return {
32
31
  invalidate: [
@@ -40,9 +39,27 @@ export function describeEventImpact(
40
39
  staleTargets: ["anchors"],
41
40
  changeKinds: ["workflow"],
42
41
  };
42
+ case "workflow_visibility_policy_changed":
43
+ return {
44
+ invalidate: [
45
+ "workflowScope",
46
+ "workflowMarkup",
47
+ "reviewWork",
48
+ "chunks",
49
+ "contextAnalytics",
50
+ ],
51
+ staleTargets: ["none"],
52
+ changeKinds: ["workflow"],
53
+ };
43
54
  case "workflow_metadata_changed":
44
55
  return {
45
- invalidate: ["workflowMarkup", "render", "locations", "reviewWork", "chunks"],
56
+ invalidate: [
57
+ "workflowMarkup",
58
+ "locations",
59
+ "reviewWork",
60
+ "chunks",
61
+ "contextAnalytics",
62
+ ],
46
63
  staleTargets: ["anchors"],
47
64
  changeKinds: ["workflow"],
48
65
  };
@@ -50,10 +67,6 @@ export function describeEventImpact(
50
67
  case "change_accepted":
51
68
  case "change_rejected":
52
69
  case "suggestion_authored":
53
- case "suggestion_updated":
54
- case "comment_added":
55
- case "comment_resolved":
56
- case "comments_changed":
57
70
  return {
58
71
  invalidate: [
59
72
  "render",
@@ -72,6 +85,20 @@ export function describeEventImpact(
72
85
  staleTargets: ["search_results", "anchors"],
73
86
  changeKinds: ["content", "review", "structure"],
74
87
  };
88
+ case "suggestion_updated":
89
+ return {
90
+ invalidate: ["trackedChanges", "reviewWork", "chunks", "contextAnalytics"],
91
+ staleTargets: ["none"],
92
+ changeKinds: ["review"],
93
+ };
94
+ case "comment_added":
95
+ case "comment_resolved":
96
+ case "comments_changed":
97
+ return {
98
+ invalidate: ["comments", "reviewWork", "chunks", "contextAnalytics"],
99
+ staleTargets: ["none"],
100
+ changeKinds: ["review"],
101
+ };
75
102
  case "warning_added":
76
103
  case "warning_cleared":
77
104
  case "error":
@@ -259,7 +259,15 @@ export function createGeometryFacet(
259
259
  getGeometryCoverage() {
260
260
  const kernel = input.renderKernel?.();
261
261
  if (!kernel) return createUnavailableGeometryCoverage();
262
- return summarizeGeometryCoverageFromFrame(kernel.getRenderFrame());
262
+ const frame = kernel.getRenderFrame();
263
+ const canonicalDocument = input.getCanonicalDocument?.() ?? null;
264
+ if (canonicalDocument) {
265
+ return (
266
+ projectGeometryIndexFromFrame(frame, { canonicalDocument })?.coverage ??
267
+ createUnavailableGeometryCoverage()
268
+ );
269
+ }
270
+ return summarizeGeometryCoverageFromFrame(frame);
263
271
  },
264
272
 
265
273
  hitTest(point) {