@beyondwork/docx-react-component 1.0.106 → 1.0.108

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
@@ -22,11 +22,10 @@
22
22
  * `attachExplanation` / `createIssue` inputs, this resolver accepts
23
23
  * the override via the second argument.
24
24
  *
25
- * Non-paragraph kinds (table / table-row / table-cell / field /
26
- * comment-thread / revision) are not in scope for §13c CLM hot-path
27
- * dominates paragraph / heading / list-item. Table-kind resolution is
28
- * straightforward to add later (the scopeId grammar is `table:<index>`)
29
- * but no consumer asks for it yet.
25
+ * Table-family scopeIds are also supported for metadata fallback. Their
26
+ * compiler ranges are synthetic subdivisions of the table block, so metadata
27
+ * anchors intentionally bind to the containing table block while preserving
28
+ * the exact `scopeId` on the metadata entry.
30
29
  */
31
30
 
32
31
  import type {
@@ -42,6 +41,16 @@ import {
42
41
  detectParagraphKind,
43
42
  } from "../../model/paragraph-scope-ids.ts";
44
43
 
44
+ type TableFamilyScopeRef =
45
+ | { readonly kind: "table"; readonly blockIndex: number }
46
+ | { readonly kind: "row"; readonly blockIndex: number; readonly rowIndex: number }
47
+ | {
48
+ readonly kind: "cell";
49
+ readonly blockIndex: number;
50
+ readonly rowIndex: number;
51
+ readonly cellIndex: number;
52
+ };
53
+
45
54
  function inlineLength(node: InlineNode): number {
46
55
  switch (node.type) {
47
56
  case "text":
@@ -69,6 +78,38 @@ function paragraphLength(paragraph: ParagraphNode): number {
69
78
  );
70
79
  }
71
80
 
81
+ function parseTableFamilyScopeId(scopeId: string): TableFamilyScopeRef | null {
82
+ const table = /^table:(\d+)$/.exec(scopeId);
83
+ if (table) return { kind: "table", blockIndex: Number(table[1]) };
84
+ const row = /^row:(\d+):(\d+)$/.exec(scopeId);
85
+ if (row) {
86
+ return {
87
+ kind: "row",
88
+ blockIndex: Number(row[1]),
89
+ rowIndex: Number(row[2]),
90
+ };
91
+ }
92
+ const cell = /^cell:(\d+):(\d+):(\d+)$/.exec(scopeId);
93
+ if (cell) {
94
+ return {
95
+ kind: "cell",
96
+ blockIndex: Number(cell[1]),
97
+ rowIndex: Number(cell[2]),
98
+ cellIndex: Number(cell[3]),
99
+ };
100
+ }
101
+ return null;
102
+ }
103
+
104
+ function tableRefExists(block: BlockNode, ref: TableFamilyScopeRef): boolean {
105
+ if (block.type !== "table") return false;
106
+ if (ref.kind === "table") return true;
107
+ const row = block.rows[ref.rowIndex];
108
+ if (!row) return false;
109
+ if (ref.kind === "row") return true;
110
+ return row.cells[ref.cellIndex] !== undefined;
111
+ }
112
+
72
113
  /**
73
114
  * Walk the canonical document and resolve a derived paragraph-like
74
115
  * scopeId to its range anchor. Returns `null` when no paragraph /
@@ -79,11 +120,11 @@ export function resolveDerivedScopeAnchorFromCanonical(
79
120
  scopeId: string,
80
121
  assoc: { readonly start: -1 | 1; readonly end: -1 | 1 } = { start: 1, end: -1 },
81
122
  ): EditorAnchorProjection | null {
82
- // Fast-reject: derived paragraph-like scopeIds are always
83
- // `para:…` / `heading:…` / `li:…`. Anything else (`table:…`,
84
- // `field:…`, comment / revision ids, arbitrary host strings) can't
85
- // match in this pass.
123
+ const tableRef = parseTableFamilyScopeId(scopeId);
124
+ // Fast-reject: derived metadata targets are paragraph-like or table-family
125
+ // scopeIds. Field/review/comment ids need their own source identity.
86
126
  if (
127
+ !tableRef &&
87
128
  !scopeId.startsWith("para:") &&
88
129
  !scopeId.startsWith("heading:") &&
89
130
  !scopeId.startsWith("li:")
@@ -118,6 +159,19 @@ export function resolveDerivedScopeAnchorFromCanonical(
118
159
  };
119
160
  }
120
161
 
162
+ if (
163
+ tableRef &&
164
+ tableRef.blockIndex === index &&
165
+ tableRefExists(block, tableRef)
166
+ ) {
167
+ return {
168
+ kind: "range",
169
+ from: cursor,
170
+ to: cursor + thisBlockLength,
171
+ assoc,
172
+ };
173
+ }
174
+
121
175
  cursor += thisBlockLength;
122
176
  if (index < blocks.length - 1) cursor += 1;
123
177
  }
@@ -24,3 +24,6 @@ export * from "./coordinator.ts";
24
24
  export * from "./visibility-policy.ts";
25
25
  export * from "./markup-mode-policy.ts";
26
26
  export * from "./scope-rail-composer.ts";
27
+ export * from "./word-field-matrix-calibration.ts";
28
+ export * from "./redline-posture-calibration.ts";
29
+ export * from "./ai-issue-lifecycle.ts";
@@ -0,0 +1,58 @@
1
+ import type { GeometryRect } from "../geometry/geometry-types.ts";
2
+
3
+ export type WorkflowOverlayAnchorQuery =
4
+ | { kind: "block"; value: string }
5
+ | { kind: "scope"; value: string }
6
+ | { kind: "comment"; value: string }
7
+ | { kind: "revision"; value: string }
8
+ | { kind: "page"; value: number }
9
+ | { kind: "selection" };
10
+
11
+ export type WorkflowOverlayLaneKind =
12
+ | "selection"
13
+ | "caret"
14
+ | "redlines"
15
+ | "field-scopes"
16
+ | "broad-scopes"
17
+ | "comments"
18
+ | "issues"
19
+ | "tables"
20
+ | "objects"
21
+ | "search"
22
+ | "presence";
23
+
24
+ export type WorkflowOverlayLaneStatus =
25
+ | "resolved"
26
+ | "requires-rehydration"
27
+ | "unavailable";
28
+
29
+ export type WorkflowOverlayLaneSource =
30
+ | "geometry"
31
+ | "workflow"
32
+ | "search"
33
+ | "awareness"
34
+ | "controller"
35
+ | "unavailable";
36
+
37
+ export interface WorkflowOverlayLaneEntry {
38
+ readonly id: string;
39
+ readonly status: WorkflowOverlayLaneStatus;
40
+ readonly anchor?: WorkflowOverlayAnchorQuery;
41
+ readonly rects?: readonly GeometryRect[];
42
+ readonly reason?: string;
43
+ /**
44
+ * Lane-specific plain payload. Examples: peer identity for presence,
45
+ * issue severity, search rank, table/object classification. Kept plain
46
+ * so non-React consumers and debug runners can serialize snapshots.
47
+ */
48
+ readonly data?: Readonly<Record<string, unknown>>;
49
+ }
50
+
51
+ export interface WorkflowOverlayLaneSnapshot {
52
+ readonly kind: WorkflowOverlayLaneKind;
53
+ readonly status: WorkflowOverlayLaneStatus;
54
+ readonly entries: readonly WorkflowOverlayLaneEntry[];
55
+ readonly revision: number;
56
+ readonly source: WorkflowOverlayLaneSource;
57
+ readonly reason?: string;
58
+ }
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  CommentSidebarSnapshot,
3
+ OverlayKind,
3
4
  SuggestionsSnapshot,
4
5
  TrackedChangesSnapshot,
5
6
  WorkflowMarkupSnapshot,
@@ -7,16 +8,44 @@ import type {
7
8
  } from "../../api/public-types.ts";
8
9
  import { ISSUE_METADATA_ID } from "../../api/public-types.ts";
9
10
  import type {
10
- UiOverlayLaneEntry,
11
- UiOverlayLaneKind,
12
- UiOverlayLaneSnapshot,
13
- UiOverlayLaneStatus,
14
- } from "../../api/v3/ui/index.ts";
11
+ WorkflowOverlayLaneEntry,
12
+ WorkflowOverlayLaneKind,
13
+ WorkflowOverlayLaneSnapshot,
14
+ WorkflowOverlayLaneStatus,
15
+ } from "./overlay-lane-types.ts";
15
16
 
16
17
  export type WorkflowReviewOverlayLaneKind = Extract<
17
- UiOverlayLaneKind,
18
+ WorkflowOverlayLaneKind,
18
19
  "redlines" | "comments" | "issues" | "field-scopes" | "broad-scopes" | "presence"
19
20
  >;
21
+ export type WorkflowReviewOverlayLaneSnapshot = WorkflowOverlayLaneSnapshot;
22
+ export type WorkflowReviewOverlayLaneListener = (
23
+ snapshot: WorkflowReviewOverlayLaneSnapshot,
24
+ ) => void;
25
+ export type WorkflowReviewOverlayLaneRefreshEvent = {
26
+ readonly type:
27
+ | "ready"
28
+ | "comment_added"
29
+ | "comment_resolved"
30
+ | "comments_changed"
31
+ | "change_authored"
32
+ | "change_accepted"
33
+ | "change_rejected"
34
+ | "suggestion_authored"
35
+ | "suggestion_updated"
36
+ | "workflow_metadata_changed"
37
+ | "workflow_overlay_changed"
38
+ | "workflow_active_work_item_changed"
39
+ | "workflow_shared_state_changed"
40
+ | "workflow_visibility_policy_changed"
41
+ | "workflow_markup_mode_policy_changed"
42
+ | "toc_auto_refreshed"
43
+ | "warning_added"
44
+ | "warning_cleared"
45
+ | "error"
46
+ | string;
47
+ readonly kind?: unknown;
48
+ };
20
49
 
21
50
  export interface WorkflowReviewOverlayLaneInput {
22
51
  readonly comments?: CommentSidebarSnapshot;
@@ -38,7 +67,7 @@ export interface WorkflowReviewOverlayLaneInput {
38
67
  export function projectWorkflowReviewOverlayLane(
39
68
  kind: WorkflowReviewOverlayLaneKind,
40
69
  input: WorkflowReviewOverlayLaneInput,
41
- ): UiOverlayLaneSnapshot {
70
+ ): WorkflowOverlayLaneSnapshot {
42
71
  if (kind === "presence") {
43
72
  return {
44
73
  kind,
@@ -60,10 +89,94 @@ export function projectWorkflowReviewOverlayLane(
60
89
  };
61
90
  }
62
91
 
92
+ export function shouldRefreshWorkflowReviewOverlayLane(
93
+ kind: WorkflowReviewOverlayLaneKind,
94
+ event: WorkflowReviewOverlayLaneRefreshEvent,
95
+ ): boolean {
96
+ if (kind === "presence") return false;
97
+ switch (event.type) {
98
+ case "ready":
99
+ return true;
100
+ case "comment_added":
101
+ case "comment_resolved":
102
+ case "comments_changed":
103
+ return kind === "comments";
104
+ case "change_authored":
105
+ case "change_accepted":
106
+ case "change_rejected":
107
+ case "suggestion_authored":
108
+ case "suggestion_updated":
109
+ return kind === "redlines";
110
+ case "workflow_metadata_changed":
111
+ return kind === "issues" || kind === "broad-scopes";
112
+ case "workflow_overlay_changed":
113
+ case "workflow_active_work_item_changed":
114
+ case "workflow_shared_state_changed":
115
+ return kind === "issues" || kind === "broad-scopes";
116
+ case "workflow_visibility_policy_changed":
117
+ return shouldRefreshForVisibilityPolicy(
118
+ kind,
119
+ isOverlayKind(event.kind) ? event.kind : undefined,
120
+ );
121
+ case "workflow_markup_mode_policy_changed":
122
+ return kind === "redlines";
123
+ case "toc_auto_refreshed":
124
+ case "warning_added":
125
+ case "warning_cleared":
126
+ case "error":
127
+ return kind === "field-scopes";
128
+ default:
129
+ return false;
130
+ }
131
+ }
132
+
133
+ export function areWorkflowReviewOverlayLaneSnapshotsEqual(
134
+ left: WorkflowReviewOverlayLaneSnapshot,
135
+ right: WorkflowReviewOverlayLaneSnapshot,
136
+ ): boolean {
137
+ return (
138
+ left.kind === right.kind &&
139
+ left.status === right.status &&
140
+ left.source === right.source &&
141
+ left.reason === right.reason &&
142
+ entriesEqual(left.entries, right.entries)
143
+ );
144
+ }
145
+
146
+ function isOverlayKind(value: unknown): value is OverlayKind {
147
+ return (
148
+ value === "scope-rail" ||
149
+ value === "comments" ||
150
+ value === "tracked-changes" ||
151
+ value === "suggestions" ||
152
+ value === "debug-panel" ||
153
+ value === "presence"
154
+ );
155
+ }
156
+
157
+ function shouldRefreshForVisibilityPolicy(
158
+ kind: Exclude<WorkflowReviewOverlayLaneKind, "presence">,
159
+ overlayKind: OverlayKind | undefined,
160
+ ): boolean {
161
+ switch (overlayKind) {
162
+ case "comments":
163
+ return kind === "comments";
164
+ case "tracked-changes":
165
+ case "suggestions":
166
+ return kind === "redlines";
167
+ case "scope-rail":
168
+ return kind === "broad-scopes";
169
+ case "presence":
170
+ case "debug-panel":
171
+ case undefined:
172
+ return false;
173
+ }
174
+ }
175
+
63
176
  function projectEntries(
64
177
  kind: Exclude<WorkflowReviewOverlayLaneKind, "presence">,
65
178
  input: WorkflowReviewOverlayLaneInput,
66
- ): UiOverlayLaneEntry[] {
179
+ ): WorkflowOverlayLaneEntry[] {
67
180
  switch (kind) {
68
181
  case "comments":
69
182
  return (input.comments?.threads ?? []).map((thread) => ({
@@ -165,7 +278,7 @@ function projectEntries(
165
278
  }
166
279
  }
167
280
 
168
- function projectIssueEntries(input: WorkflowReviewOverlayLaneInput): UiOverlayLaneEntry[] {
281
+ function projectIssueEntries(input: WorkflowReviewOverlayLaneInput): WorkflowOverlayLaneEntry[] {
169
282
  const suggestionGroupsByIssueId = new Map<string, string[]>();
170
283
  for (const group of input.suggestions?.groups ?? []) {
171
284
  if (!group.issueId) continue;
@@ -207,12 +320,57 @@ function projectIssueEntries(input: WorkflowReviewOverlayLaneInput): UiOverlayLa
207
320
  });
208
321
  }
209
322
 
210
- function summarizeStatus(entries: readonly UiOverlayLaneEntry[]): UiOverlayLaneStatus {
323
+ function summarizeStatus(entries: readonly WorkflowOverlayLaneEntry[]): WorkflowOverlayLaneStatus {
211
324
  return entries.some((entry) => entry.status === "requires-rehydration")
212
325
  ? "requires-rehydration"
213
326
  : "resolved";
214
327
  }
215
328
 
329
+ function entriesEqual(
330
+ left: readonly WorkflowOverlayLaneEntry[],
331
+ right: readonly WorkflowOverlayLaneEntry[],
332
+ ): boolean {
333
+ if (left.length !== right.length) return false;
334
+ for (let index = 0; index < left.length; index += 1) {
335
+ const leftEntry = left[index];
336
+ const rightEntry = right[index];
337
+ if (!leftEntry || !rightEntry) return false;
338
+ if (
339
+ leftEntry.id !== rightEntry.id ||
340
+ leftEntry.status !== rightEntry.status ||
341
+ leftEntry.reason !== rightEntry.reason ||
342
+ !jsonValueEqual(leftEntry.anchor, rightEntry.anchor) ||
343
+ !jsonValueEqual(leftEntry.rects, rightEntry.rects) ||
344
+ !jsonValueEqual(leftEntry.data, rightEntry.data)
345
+ ) {
346
+ return false;
347
+ }
348
+ }
349
+ return true;
350
+ }
351
+
352
+ function jsonValueEqual(left: unknown, right: unknown): boolean {
353
+ return stableJson(left) === stableJson(right);
354
+ }
355
+
356
+ function stableJson(value: unknown): string {
357
+ return JSON.stringify(sortJsonValue(value));
358
+ }
359
+
360
+ function sortJsonValue(value: unknown): unknown {
361
+ if (Array.isArray(value)) {
362
+ return value.map(sortJsonValue);
363
+ }
364
+ if (!isRecord(value)) {
365
+ return value;
366
+ }
367
+ const sorted: Record<string, unknown> = {};
368
+ for (const key of Object.keys(value).sort()) {
369
+ sorted[key] = sortJsonValue(value[key]);
370
+ }
371
+ return sorted;
372
+ }
373
+
216
374
  function compactData(input: Record<string, unknown>): Readonly<Record<string, unknown>> {
217
375
  const output: Record<string, unknown> = {};
218
376
  for (const [key, value] of Object.entries(input)) {
@@ -22,8 +22,8 @@
22
22
  *
23
23
  * What it does NOT own (by design — those belong to the coordinator):
24
24
  * - interaction-guard / workflow-scope / workflow-markup snapshot
25
- * caches (require runtime-scope deps like revisionToken, render
26
- * snapshot, selection).
25
+ * caches (require runtime-scope deps like render snapshot,
26
+ * selection, protection, and active story).
27
27
  * - blocked-reason composition + scope-matching heuristics (require
28
28
  * protection snapshot + viewState.documentMode).
29
29
  * - rail / card composition (requires page graph + render frame).
@@ -0,0 +1,257 @@
1
+ export type WorkflowRedlinePosture = "all-markup" | "final" | "original" | "unknown";
2
+
3
+ export interface WorkflowRedlineDatasetDocumentLike {
4
+ readonly docId: string;
5
+ readonly status: string;
6
+ readonly pages: number;
7
+ readonly revisions: number;
8
+ readonly comments: number;
9
+ readonly fields?: number;
10
+ readonly uiaLines?: number;
11
+ readonly datasetPath?: string;
12
+ }
13
+
14
+ export interface WorkflowRedlineDatasetManifestLike {
15
+ readonly schemaVersion: string;
16
+ readonly sourceRun: string;
17
+ readonly captureMatrix?: {
18
+ readonly markupModes?: readonly string[];
19
+ };
20
+ readonly targetedWord?: {
21
+ readonly version?: string;
22
+ readonly build?: string;
23
+ readonly activePrinter?: string;
24
+ };
25
+ readonly documents: readonly WorkflowRedlineDatasetDocumentLike[];
26
+ }
27
+
28
+ export interface WorkflowRedlineFieldIdentityTotalsLike {
29
+ readonly wordFields: number;
30
+ readonly joined: number;
31
+ readonly missingSourceField: number;
32
+ readonly missingCanonicalField: number;
33
+ readonly missingRuntimeRegion: number;
34
+ readonly actionableL04: number;
35
+ }
36
+
37
+ export type WorkflowRedlinePostureFactId =
38
+ | "all-markup-review-posture"
39
+ | "revision-comment-counts"
40
+ | "clean-all-markup-measurement-handoff"
41
+ | "redline-field-identity-posture";
42
+
43
+ export type WorkflowRedlinePostureRepresentation =
44
+ | "durable-workflow-review-posture"
45
+ | "measurement-handoff"
46
+ | "field-region-posture-handoff"
47
+ | "blocked-until-identity-join";
48
+
49
+ export interface WorkflowRedlinePostureFact {
50
+ readonly factId: WorkflowRedlinePostureFactId;
51
+ readonly representation: WorkflowRedlinePostureRepresentation;
52
+ readonly posture: WorkflowRedlinePosture;
53
+ readonly sourceRun: string;
54
+ readonly l06Surfaces: readonly string[];
55
+ readonly downstreamRoutes: readonly string[];
56
+ readonly evidence: Readonly<Record<string, number | string | boolean>>;
57
+ readonly note: string;
58
+ }
59
+
60
+ export interface WorkflowRedlinePostureCalibration {
61
+ readonly schemaVersion: "l06-redline-posture-calibration/v0";
62
+ readonly facts: readonly WorkflowRedlinePostureFact[];
63
+ readonly summary: {
64
+ readonly documentCount: number;
65
+ readonly availableDocumentCount: number;
66
+ readonly pageCount: number;
67
+ readonly revisionCount: number;
68
+ readonly commentCount: number;
69
+ readonly fieldCount: number;
70
+ readonly uiaLineCount: number;
71
+ readonly posture: WorkflowRedlinePosture;
72
+ readonly downstreamRouteCount: number;
73
+ readonly l04ActionableFieldRows: number;
74
+ readonly ownsGeometry: false;
75
+ readonly ownsVisualPaint: false;
76
+ };
77
+ }
78
+
79
+ export interface WorkflowRedlinePostureCalibrationInput {
80
+ readonly manifest: WorkflowRedlineDatasetManifestLike;
81
+ readonly fieldIdentityTotals?: WorkflowRedlineFieldIdentityTotalsLike;
82
+ }
83
+
84
+ interface WorkflowRedlinePostureTotals {
85
+ readonly documentCount: number;
86
+ readonly availableDocumentCount: number;
87
+ readonly pageCount: number;
88
+ readonly revisionCount: number;
89
+ readonly commentCount: number;
90
+ readonly fieldCount: number;
91
+ readonly uiaLineCount: number;
92
+ }
93
+
94
+ export function calibrateWorkflowReviewRedlinePosture(
95
+ input: WorkflowRedlinePostureCalibrationInput,
96
+ ): WorkflowRedlinePostureCalibration {
97
+ const documents = input.manifest.documents;
98
+ const availableDocuments = documents.filter((document) => document.status === "available");
99
+ const posture = resolvePosture(input.manifest.captureMatrix?.markupModes);
100
+ const totals = {
101
+ documentCount: documents.length,
102
+ availableDocumentCount: availableDocuments.length,
103
+ pageCount: sum(availableDocuments, (document) => document.pages),
104
+ revisionCount: sum(availableDocuments, (document) => document.revisions),
105
+ commentCount: sum(availableDocuments, (document) => document.comments),
106
+ fieldCount: sum(availableDocuments, (document) => document.fields ?? 0),
107
+ uiaLineCount: sum(availableDocuments, (document) => document.uiaLines ?? 0),
108
+ };
109
+
110
+ const facts = [
111
+ buildAllMarkupPostureFact(input.manifest, posture, totals),
112
+ buildReviewCountFact(input.manifest, posture, totals),
113
+ buildMeasurementHandoffFact(input.manifest, posture, totals),
114
+ buildFieldIdentityFact(input.manifest, posture, totals, input.fieldIdentityTotals),
115
+ ];
116
+
117
+ return {
118
+ schemaVersion: "l06-redline-posture-calibration/v0",
119
+ facts,
120
+ summary: {
121
+ ...totals,
122
+ posture,
123
+ downstreamRouteCount: new Set(facts.flatMap((fact) => fact.downstreamRoutes)).size,
124
+ l04ActionableFieldRows: input.fieldIdentityTotals?.actionableL04 ?? 0,
125
+ ownsGeometry: false,
126
+ ownsVisualPaint: false,
127
+ },
128
+ };
129
+ }
130
+
131
+ function buildAllMarkupPostureFact(
132
+ manifest: WorkflowRedlineDatasetManifestLike,
133
+ posture: WorkflowRedlinePosture,
134
+ totals: WorkflowRedlinePostureTotals,
135
+ ): WorkflowRedlinePostureFact {
136
+ return {
137
+ factId: "all-markup-review-posture",
138
+ representation: "durable-workflow-review-posture",
139
+ posture,
140
+ sourceRun: manifest.sourceRun,
141
+ l06Surfaces: [
142
+ "WorkflowMarkupModePolicy",
143
+ "WorkflowMarkupSnapshot.revisions",
144
+ "TrackedChangesSnapshot.revisions",
145
+ ],
146
+ downstreamRoutes: ["L04 redline measurement posture", "L11 redline overlay paint"],
147
+ evidence: {
148
+ documents: totals.documentCount,
149
+ availableDocuments: totals.availableDocumentCount,
150
+ wordVersion: manifest.targetedWord?.version ?? "unknown",
151
+ wordBuild: manifest.targetedWord?.build ?? "unknown",
152
+ },
153
+ note:
154
+ "The redline Word JSON dataset is captured in all-markup posture; L06 names that durable review posture before layout or paint consume it.",
155
+ };
156
+ }
157
+
158
+ function buildReviewCountFact(
159
+ manifest: WorkflowRedlineDatasetManifestLike,
160
+ posture: WorkflowRedlinePosture,
161
+ totals: WorkflowRedlinePostureTotals,
162
+ ): WorkflowRedlinePostureFact {
163
+ return {
164
+ factId: "revision-comment-counts",
165
+ representation: "durable-workflow-review-posture",
166
+ posture,
167
+ sourceRun: manifest.sourceRun,
168
+ l06Surfaces: [
169
+ "WorkflowMarkupSnapshot.revisions",
170
+ "WorkflowMarkupSnapshot.comments",
171
+ "TrackedChangesSnapshot.revisions",
172
+ "CommentSidebarSnapshot.threads",
173
+ ],
174
+ downstreamRoutes: ["L11 review/comment lane visibility", "L08 comment/revision scope posture"],
175
+ evidence: {
176
+ revisions: totals.revisionCount,
177
+ comments: totals.commentCount,
178
+ documentsWithReviewData: totals.availableDocumentCount,
179
+ },
180
+ note:
181
+ "Revision and comment totals are L06 review-store facts; geometry and balloon placement stay with downstream consumers.",
182
+ };
183
+ }
184
+
185
+ function buildMeasurementHandoffFact(
186
+ manifest: WorkflowRedlineDatasetManifestLike,
187
+ posture: WorkflowRedlinePosture,
188
+ totals: WorkflowRedlinePostureTotals,
189
+ ): WorkflowRedlinePostureFact {
190
+ return {
191
+ factId: "clean-all-markup-measurement-handoff",
192
+ representation: "measurement-handoff",
193
+ posture,
194
+ sourceRun: manifest.sourceRun,
195
+ l06Surfaces: ["EffectiveLayoutFormatting.revisionPosture", "WorkflowMarkupModePolicy"],
196
+ downstreamRoutes: ["L04 clean-vs-all-markup page/frame delta", "L11 render-word assertion planning"],
197
+ evidence: {
198
+ pagesInAllMarkupPosture: totals.pageCount,
199
+ uiaLinesInAllMarkupPosture: totals.uiaLineCount,
200
+ ownsGeometry: false,
201
+ },
202
+ note:
203
+ "L06 names the measurement-affecting review posture; L04 owns page/frame deltas and L11 owns visual assertion planning.",
204
+ };
205
+ }
206
+
207
+ function buildFieldIdentityFact(
208
+ manifest: WorkflowRedlineDatasetManifestLike,
209
+ posture: WorkflowRedlinePosture,
210
+ totals: WorkflowRedlinePostureTotals,
211
+ fieldIdentityTotals: WorkflowRedlineFieldIdentityTotalsLike | undefined,
212
+ ): WorkflowRedlinePostureFact {
213
+ const actionableRows = fieldIdentityTotals?.actionableL04 ?? 0;
214
+ return {
215
+ factId: "redline-field-identity-posture",
216
+ representation: actionableRows > 0 ? "field-region-posture-handoff" : "blocked-until-identity-join",
217
+ posture,
218
+ sourceRun: manifest.sourceRun,
219
+ l06Surfaces: ["WorkflowMarkupModePolicy", "WorkflowMarkupSnapshot.fields"],
220
+ downstreamRoutes: [
221
+ "L01 source field identity",
222
+ "L02 canonical field identity",
223
+ "L04 redline field-region measurement",
224
+ ],
225
+ evidence: {
226
+ redlineWordFields: fieldIdentityTotals?.wordFields ?? totals.fieldCount,
227
+ sourceJoinedRows: fieldIdentityTotals?.joined ?? 0,
228
+ missingSourceField: fieldIdentityTotals?.missingSourceField ?? 0,
229
+ missingCanonicalField: fieldIdentityTotals?.missingCanonicalField ?? 0,
230
+ missingRuntimeRegion: fieldIdentityTotals?.missingRuntimeRegion ?? 0,
231
+ actionableL04: actionableRows,
232
+ },
233
+ note:
234
+ actionableRows > 0
235
+ ? "L06 review posture is named and the field rows may proceed to L04 with identity joins present."
236
+ : "L06 review posture is named as all-markup, but redline field-region rows remain blocked on source/canonical/runtime identity joins.",
237
+ };
238
+ }
239
+
240
+ function resolvePosture(markupModes: readonly string[] | undefined): WorkflowRedlinePosture {
241
+ if (!markupModes || markupModes.length !== 1) return "unknown";
242
+ switch (markupModes[0]) {
243
+ case "all":
244
+ case "all-markup":
245
+ return "all-markup";
246
+ case "final":
247
+ return "final";
248
+ case "original":
249
+ return "original";
250
+ default:
251
+ return "unknown";
252
+ }
253
+ }
254
+
255
+ function sum<T>(items: readonly T[], selector: (item: T) => number): number {
256
+ return items.reduce((total, item) => total + selector(item), 0);
257
+ }