@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
@@ -0,0 +1,228 @@
1
+ import type {
2
+ CommentSidebarSnapshot,
3
+ SuggestionsSnapshot,
4
+ TrackedChangesSnapshot,
5
+ WorkflowMarkupSnapshot,
6
+ WorkflowScopeSnapshot,
7
+ } from "../../api/public-types.ts";
8
+ import { ISSUE_METADATA_ID } from "../../api/public-types.ts";
9
+ import type {
10
+ UiOverlayLaneEntry,
11
+ UiOverlayLaneKind,
12
+ UiOverlayLaneSnapshot,
13
+ UiOverlayLaneStatus,
14
+ } from "../../api/v3/ui/index.ts";
15
+
16
+ export type WorkflowReviewOverlayLaneKind = Extract<
17
+ UiOverlayLaneKind,
18
+ "redlines" | "comments" | "issues" | "field-scopes" | "broad-scopes" | "presence"
19
+ >;
20
+
21
+ export interface WorkflowReviewOverlayLaneInput {
22
+ readonly comments?: CommentSidebarSnapshot;
23
+ readonly trackedChanges?: TrackedChangesSnapshot;
24
+ readonly suggestions?: SuggestionsSnapshot | null;
25
+ readonly workflowScope?: WorkflowScopeSnapshot;
26
+ readonly workflowMarkup?: WorkflowMarkupSnapshot;
27
+ readonly revision?: number;
28
+ }
29
+
30
+ /**
31
+ * Layer-06 data handoff for L10/L11 PE2 overlay lanes.
32
+ *
33
+ * This projector emits plain lane snapshots only. It does not resolve geometry,
34
+ * read DOM state, subscribe to awareness, dispatch PM transactions, or wake
35
+ * layout. Presence is intentionally unavailable here because live cursor/
36
+ * collaborator state is sourced from Yjs awareness, not durable workflow truth.
37
+ */
38
+ export function projectWorkflowReviewOverlayLane(
39
+ kind: WorkflowReviewOverlayLaneKind,
40
+ input: WorkflowReviewOverlayLaneInput,
41
+ ): UiOverlayLaneSnapshot {
42
+ if (kind === "presence") {
43
+ return {
44
+ kind,
45
+ status: "unavailable",
46
+ entries: [],
47
+ revision: input.revision ?? 0,
48
+ source: "awareness",
49
+ reason: "presence lane is sourced from Yjs awareness, not durable workflow/review state",
50
+ };
51
+ }
52
+
53
+ const entries = projectEntries(kind, input);
54
+ return {
55
+ kind,
56
+ status: summarizeStatus(entries),
57
+ entries,
58
+ revision: input.revision ?? 0,
59
+ source: "workflow",
60
+ };
61
+ }
62
+
63
+ function projectEntries(
64
+ kind: Exclude<WorkflowReviewOverlayLaneKind, "presence">,
65
+ input: WorkflowReviewOverlayLaneInput,
66
+ ): UiOverlayLaneEntry[] {
67
+ switch (kind) {
68
+ case "comments":
69
+ return (input.comments?.threads ?? []).map((thread) => ({
70
+ id: thread.commentId,
71
+ status: thread.status === "detached" ? "requires-rehydration" : "resolved",
72
+ anchor: { kind: "comment", value: thread.commentId },
73
+ reason: thread.detachedReason,
74
+ data: compactData({
75
+ laneItemKind: "comment",
76
+ commentId: thread.commentId,
77
+ editorAnchor: thread.anchor,
78
+ status: thread.status,
79
+ createdAt: thread.createdAt,
80
+ createdBy: thread.createdBy,
81
+ entryCount: thread.entryCount,
82
+ warningCount: thread.warningCount,
83
+ linkedRevisionId: thread.linkedRevisionId,
84
+ excerpt: thread.excerpt,
85
+ anchorLabel: thread.anchorLabel,
86
+ }),
87
+ }));
88
+ case "redlines":
89
+ return (input.trackedChanges?.revisions ?? []).map((revision) => ({
90
+ id: revision.revisionId,
91
+ status: revision.status === "detached" ? "requires-rehydration" : "resolved",
92
+ anchor: { kind: "revision", value: revision.revisionId },
93
+ reason: revision.preserveOnlyReason,
94
+ data: compactData({
95
+ laneItemKind: "revision",
96
+ revisionId: revision.revisionId,
97
+ editorAnchor: revision.anchor,
98
+ revisionKind: revision.kind,
99
+ semanticKind: revision.semanticKind,
100
+ suggestionId: revision.suggestionId,
101
+ status: revision.status,
102
+ actionability: revision.actionability,
103
+ authorId: revision.authorId,
104
+ createdAt: revision.createdAt,
105
+ label: revision.label,
106
+ excerpt: revision.excerpt,
107
+ storyTarget: revision.storyTarget,
108
+ canAccept: revision.canAccept,
109
+ canReject: revision.canReject,
110
+ warningCount: revision.warningCount,
111
+ commentThreadIds: revision.commentThreadIds,
112
+ }),
113
+ }));
114
+ case "issues":
115
+ return projectIssueEntries(input);
116
+ case "field-scopes":
117
+ return (input.workflowMarkup?.fields ?? []).map((field) => ({
118
+ id: `field:${field.fieldIndex}`,
119
+ status: "resolved",
120
+ data: compactData({
121
+ laneItemKind: "field",
122
+ fieldIndex: field.fieldIndex,
123
+ editorAnchor: field.anchor,
124
+ fieldFamily: field.fieldFamily,
125
+ fieldTarget: field.fieldTarget,
126
+ refreshStatus: field.refreshStatus,
127
+ displayText: field.displayText,
128
+ label: field.label,
129
+ storyTarget: field.storyTarget,
130
+ }),
131
+ }));
132
+ case "broad-scopes":
133
+ return [
134
+ ...(input.workflowScope?.scopes ?? []).map((scope) => ({
135
+ id: scope.scopeId,
136
+ status: "resolved" as const,
137
+ anchor: { kind: "scope" as const, value: scope.scopeId },
138
+ data: compactData({
139
+ laneItemKind: "workflow-scope",
140
+ scopeId: scope.scopeId,
141
+ editorAnchor: scope.anchor,
142
+ mode: scope.mode,
143
+ label: scope.label,
144
+ domain: scope.domain,
145
+ workItemId: scope.workItemId,
146
+ visibility: scope.visibility,
147
+ guardPolicy: scope.guardPolicy,
148
+ storyTarget: scope.storyTarget,
149
+ metadataRefs: scope.metadataRefs,
150
+ }),
151
+ })),
152
+ ...(input.workflowScope?.candidates ?? []).map((candidate) => ({
153
+ id: candidate.candidateId,
154
+ status: "resolved" as const,
155
+ data: compactData({
156
+ laneItemKind: "workflow-candidate",
157
+ candidateId: candidate.candidateId,
158
+ editorAnchor: candidate.anchor,
159
+ label: candidate.label,
160
+ source: candidate.source,
161
+ storyTarget: candidate.storyTarget,
162
+ }),
163
+ })),
164
+ ];
165
+ }
166
+ }
167
+
168
+ function projectIssueEntries(input: WorkflowReviewOverlayLaneInput): UiOverlayLaneEntry[] {
169
+ const suggestionGroupsByIssueId = new Map<string, string[]>();
170
+ for (const group of input.suggestions?.groups ?? []) {
171
+ if (!group.issueId) continue;
172
+ const ids = suggestionGroupsByIssueId.get(group.issueId) ?? [];
173
+ ids.push(group.groupId);
174
+ suggestionGroupsByIssueId.set(group.issueId, ids);
175
+ }
176
+
177
+ return (input.workflowMarkup?.metadata ?? [])
178
+ .filter((entry) => entry.metadataId === ISSUE_METADATA_ID)
179
+ .map((entry) => {
180
+ const issueValue = isRecord(entry.value) ? entry.value : undefined;
181
+ const issueId = typeof issueValue?.issueId === "string" ? issueValue.issueId : undefined;
182
+ return {
183
+ id: issueId ?? entry.entryId,
184
+ status: "resolved",
185
+ anchor: entry.scopeId ? { kind: "scope", value: entry.scopeId } : undefined,
186
+ data: compactData({
187
+ laneItemKind: "issue",
188
+ entryId: entry.entryId,
189
+ metadataId: entry.metadataId,
190
+ issueId,
191
+ editorAnchor: entry.anchor,
192
+ scopeId: entry.scopeId,
193
+ workItemId: entry.workItemId,
194
+ label: entry.label,
195
+ title: typeof issueValue?.title === "string" ? issueValue.title : undefined,
196
+ topic: typeof issueValue?.topic === "string" ? issueValue.topic : undefined,
197
+ severity: typeof issueValue?.severity === "string" ? issueValue.severity : undefined,
198
+ checklistState:
199
+ typeof issueValue?.checklistState === "string" ? issueValue.checklistState : undefined,
200
+ suggestionGroupIds: issueId ? suggestionGroupsByIssueId.get(issueId) ?? [] : [],
201
+ persistence: entry.persistence,
202
+ metadataPersistence: entry.metadataPersistence,
203
+ storageRef: entry.storageRef,
204
+ metadataVersion: entry.metadataVersion,
205
+ }),
206
+ };
207
+ });
208
+ }
209
+
210
+ function summarizeStatus(entries: readonly UiOverlayLaneEntry[]): UiOverlayLaneStatus {
211
+ return entries.some((entry) => entry.status === "requires-rehydration")
212
+ ? "requires-rehydration"
213
+ : "resolved";
214
+ }
215
+
216
+ function compactData(input: Record<string, unknown>): Readonly<Record<string, unknown>> {
217
+ const output: Record<string, unknown> = {};
218
+ for (const [key, value] of Object.entries(input)) {
219
+ if (value !== undefined) {
220
+ output[key] = value;
221
+ }
222
+ }
223
+ return output;
224
+ }
225
+
226
+ function isRecord(value: unknown): value is Record<string, unknown> {
227
+ return typeof value === "object" && value !== null && !Array.isArray(value);
228
+ }
@@ -0,0 +1,131 @@
1
+ import {
2
+ getRemoteCursorStates,
3
+ type RemoteCursorState,
4
+ } from "../api/v3/runtime/collab.ts";
5
+ import type {
6
+ GeometryRect,
7
+ UiListener,
8
+ UiOverlayLaneKind,
9
+ UiOverlayLaneSnapshot,
10
+ UiOverlayLaneStatus,
11
+ UiUnsubscribe,
12
+ } from "../api/v3/ui/_types.ts";
13
+
14
+ type AwarenessChangeListener = () => void;
15
+
16
+ export interface PresenceAwarenessSource {
17
+ readonly clientID: number;
18
+ getStates(): Map<number, Record<string, unknown>>;
19
+ on(event: "change", listener: AwarenessChangeListener): void;
20
+ off(event: "change", listener: AwarenessChangeListener): void;
21
+ }
22
+
23
+ export type PresenceCursorProjection =
24
+ | { readonly status: "resolved"; readonly rects: readonly GeometryRect[]; readonly reason?: string }
25
+ | { readonly status: "requires-rehydration" | "unavailable"; readonly rects?: undefined; readonly reason?: string };
26
+
27
+ export interface PresenceOverlayLaneSourceOptions {
28
+ readonly awareness: PresenceAwarenessSource;
29
+ readonly localClientId?: number;
30
+ readonly getRevision?: () => number;
31
+ readonly projectCursor?: (cursor: RemoteCursorState) => PresenceCursorProjection;
32
+ }
33
+
34
+ export interface PresenceOverlayLaneSource {
35
+ getLane(kind: UiOverlayLaneKind): UiOverlayLaneSnapshot;
36
+ subscribeLane(
37
+ kind: UiOverlayLaneKind,
38
+ listener: UiListener<UiOverlayLaneSnapshot>,
39
+ ): UiUnsubscribe;
40
+ }
41
+
42
+ function unavailable(kind: UiOverlayLaneKind, reason: string): UiOverlayLaneSnapshot {
43
+ return Object.freeze({
44
+ __mock: true,
45
+ kind,
46
+ status: "unavailable",
47
+ entries: Object.freeze([]),
48
+ revision: 0,
49
+ source: "unavailable",
50
+ reason,
51
+ } as const);
52
+ }
53
+
54
+ function coerceLaneStatus(
55
+ current: UiOverlayLaneStatus,
56
+ next: UiOverlayLaneStatus,
57
+ ): UiOverlayLaneStatus {
58
+ if (current === "unavailable" || next === "unavailable") return "unavailable";
59
+ if (current === "requires-rehydration" || next === "requires-rehydration") {
60
+ return "requires-rehydration";
61
+ }
62
+ return "resolved";
63
+ }
64
+
65
+ export function createPresenceOverlayLaneSource(
66
+ options: PresenceOverlayLaneSourceOptions,
67
+ ): PresenceOverlayLaneSource {
68
+ const localClientId = options.localClientId ?? options.awareness.clientID;
69
+
70
+ function readPresenceLane(): UiOverlayLaneSnapshot {
71
+ const cursors = getRemoteCursorStates(
72
+ options.awareness as Parameters<typeof getRemoteCursorStates>[0],
73
+ localClientId,
74
+ );
75
+ let laneStatus: UiOverlayLaneStatus = "resolved";
76
+ const entries = cursors.map((cursor) => {
77
+ const projection = options.projectCursor?.(cursor) ?? {
78
+ status: "requires-rehydration" as const,
79
+ reason: "presence cursor geometry projection is not wired",
80
+ };
81
+ laneStatus = coerceLaneStatus(laneStatus, projection.status);
82
+ return {
83
+ id: `presence:${cursor.userId}`,
84
+ status: projection.status,
85
+ ...(projection.rects ? { rects: projection.rects } : {}),
86
+ ...(projection.reason ? { reason: projection.reason } : {}),
87
+ data: {
88
+ kind: "remote-cursor",
89
+ userId: cursor.userId,
90
+ displayName: cursor.displayName,
91
+ color: cursor.color,
92
+ anchor: cursor.anchor,
93
+ head: cursor.head,
94
+ storyTarget: cursor.storyTarget,
95
+ },
96
+ };
97
+ });
98
+
99
+ return {
100
+ kind: "presence",
101
+ status: entries.length === 0 ? "resolved" : laneStatus,
102
+ entries,
103
+ revision: options.getRevision?.() ?? 0,
104
+ source: "awareness",
105
+ };
106
+ }
107
+
108
+ return {
109
+ getLane(kind: UiOverlayLaneKind): UiOverlayLaneSnapshot {
110
+ if (kind !== "presence") {
111
+ return unavailable(kind, "presence lane source only handles kind='presence'");
112
+ }
113
+ return readPresenceLane();
114
+ },
115
+ subscribeLane(
116
+ kind: UiOverlayLaneKind,
117
+ listener: UiListener<UiOverlayLaneSnapshot>,
118
+ ): UiUnsubscribe {
119
+ if (kind !== "presence") {
120
+ throw new Error(`presence lane source only handles kind='presence'; got "${kind}"`);
121
+ }
122
+ const onChange = (): void => {
123
+ listener(readPresenceLane());
124
+ };
125
+ options.awareness.on("change", onChange);
126
+ return () => {
127
+ options.awareness.off("change", onChange);
128
+ };
129
+ },
130
+ };
131
+ }
@@ -54,6 +54,9 @@ import type {
54
54
  ChromePosture,
55
55
  GeometryRect,
56
56
  OverlayAnchorQuery,
57
+ PageResidencySnapshot,
58
+ UiOverlayLaneKind,
59
+ UiOverlayLaneSnapshot,
57
60
  UiController,
58
61
  UiControllerFactory,
59
62
  UiListener,
@@ -111,6 +114,13 @@ export interface ShellUiControllerDeps {
111
114
  * dpr / container-resize. Returns unsubscribe.
112
115
  */
113
116
  readonly subscribeViewport?: (listener: UiListener<ViewportState>) => UiUnsubscribe;
117
+ /** PE2 page-residency policy read. */
118
+ readonly getPageResidency?: (pageIndex: number) => PageResidencySnapshot;
119
+ /** PE2 page-residency subscription channel. */
120
+ readonly subscribePageResidency?: (
121
+ pageIndex: number,
122
+ listener: UiListener<PageResidencySnapshot>,
123
+ ) => UiUnsubscribe;
114
124
  /**
115
125
  * Overlay invalidation stream — fires when geometry invalidation overlaps
116
126
  * an attached overlay query (U7). Typically delegated to the render
@@ -119,6 +129,13 @@ export interface ShellUiControllerDeps {
119
129
  readonly subscribeOverlays?: (
120
130
  listener: UiListener<OverlayAnchorQuery>,
121
131
  ) => UiUnsubscribe;
132
+ /** PE2 overlay-lane snapshot resolver. */
133
+ readonly getOverlayLane?: (kind: UiOverlayLaneKind) => UiOverlayLaneSnapshot;
134
+ /** PE2 overlay-lane subscription channel. */
135
+ readonly subscribeOverlayLane?: (
136
+ kind: UiOverlayLaneKind,
137
+ listener: UiListener<UiOverlayLaneSnapshot>,
138
+ ) => UiUnsubscribe;
122
139
  }
123
140
 
124
141
  /**
@@ -145,7 +162,11 @@ export function makeShellUiControllerFactory(
145
162
  ...(deps.subscribeChrome ? { subscribeChrome: deps.subscribeChrome } : {}),
146
163
  ...(deps.getViewport ? { getViewport: deps.getViewport } : {}),
147
164
  ...(deps.subscribeViewport ? { subscribeViewport: deps.subscribeViewport } : {}),
165
+ ...(deps.getPageResidency ? { getPageResidency: deps.getPageResidency } : {}),
166
+ ...(deps.subscribePageResidency ? { subscribePageResidency: deps.subscribePageResidency } : {}),
148
167
  ...(deps.subscribeOverlays ? { subscribeOverlays: deps.subscribeOverlays } : {}),
168
+ ...(deps.getOverlayLane ? { getOverlayLane: deps.getOverlayLane } : {}),
169
+ ...(deps.subscribeOverlayLane ? { subscribeOverlayLane: deps.subscribeOverlayLane } : {}),
149
170
  };
150
171
  return controller;
151
172
  };