@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.
- package/package.json +1 -1
- package/src/api/public-types.ts +3 -0
- package/src/api/v3/_create.ts +9 -2
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_pe2-evidence.ts +419 -0
- package/src/api/v3/ai/attach.ts +22 -2
- package/src/api/v3/ai/bundle.ts +18 -6
- package/src/api/v3/ai/inspect.ts +12 -2
- package/src/api/v3/ai/replacement.ts +124 -0
- package/src/api/v3/index.ts +7 -0
- package/src/api/v3/ui/_types.ts +139 -0
- package/src/api/v3/ui/index.ts +9 -0
- package/src/api/v3/ui/overlays.ts +104 -0
- package/src/api/v3/ui/viewport.ts +97 -0
- package/src/model/layout/index.ts +3 -0
- package/src/model/layout/page-graph-types.ts +118 -0
- package/src/model/layout/runtime-page-graph-types.ts +13 -0
- package/src/runtime/document-runtime.ts +39 -18
- package/src/runtime/event-refresh-hints.ts +33 -6
- package/src/runtime/geometry/geometry-facet.ts +9 -1
- package/src/runtime/geometry/geometry-index.ts +461 -10
- package/src/runtime/geometry/geometry-types.ts +6 -0
- package/src/runtime/geometry/object-handles.ts +7 -4
- package/src/runtime/layout/layout-engine-instance.ts +2 -0
- package/src/runtime/layout/layout-engine-version.ts +36 -1
- package/src/runtime/layout/page-graph.ts +697 -10
- package/src/runtime/layout/paginated-layout-engine.ts +10 -0
- package/src/runtime/layout/project-block-fragments.ts +187 -8
- package/src/runtime/layout/public-facet.ts +236 -0
- package/src/runtime/prerender/graph-canonicalize.ts +14 -0
- package/src/runtime/workflow/index.ts +1 -0
- package/src/runtime/workflow/overlay-lanes.ts +228 -0
- package/src/ui/presence-overlay-lane.ts +131 -0
- package/src/ui/ui-controller-factory.ts +21 -0
package/src/api/v3/ai/inspect.ts
CHANGED
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
type SemanticScopeKind,
|
|
17
17
|
} from "../../../runtime/scopes/index.ts";
|
|
18
18
|
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
19
|
+
import {
|
|
20
|
+
projectDocumentPe2Evidence,
|
|
21
|
+
type AiPe2EvidenceOptions,
|
|
22
|
+
type AiPe2DocumentEvidence,
|
|
23
|
+
} from "./_pe2-evidence.ts";
|
|
19
24
|
|
|
20
25
|
export interface InspectDocumentResult {
|
|
21
26
|
readonly documentId: string;
|
|
@@ -23,6 +28,7 @@ export interface InspectDocumentResult {
|
|
|
23
28
|
readonly pageCount?: number;
|
|
24
29
|
readonly semanticSummary: string;
|
|
25
30
|
readonly kindDistribution: Readonly<Partial<Record<SemanticScopeKind, number>>>;
|
|
31
|
+
readonly pe2Evidence: AiPe2DocumentEvidence;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
export const inspectDocumentMetadata: ApiV3FnMetadata = {
|
|
@@ -40,7 +46,7 @@ export const inspectDocumentMetadata: ApiV3FnMetadata = {
|
|
|
40
46
|
boundedScope: "document",
|
|
41
47
|
auditCategory: "document-inspect",
|
|
42
48
|
contextPromptShape:
|
|
43
|
-
"Summarize document by scope count + page count + semantic kind distribution.",
|
|
49
|
+
"Summarize document by scope count + page count + semantic kind distribution + PE2 geometry coverage evidence.",
|
|
44
50
|
},
|
|
45
51
|
stateClass: "A-canonical",
|
|
46
52
|
persistsTo: "canonical",
|
|
@@ -79,7 +85,10 @@ function buildKindDistribution(
|
|
|
79
85
|
return out;
|
|
80
86
|
}
|
|
81
87
|
|
|
82
|
-
export function createInspectFamily(
|
|
88
|
+
export function createInspectFamily(
|
|
89
|
+
runtime: RuntimeApiHandle,
|
|
90
|
+
pe2Evidence?: AiPe2EvidenceOptions,
|
|
91
|
+
) {
|
|
83
92
|
const compiler = createScopeCompilerService(runtime);
|
|
84
93
|
return {
|
|
85
94
|
inspectDocument(): InspectDocumentResult {
|
|
@@ -97,6 +106,7 @@ export function createInspectFamily(runtime: RuntimeApiHandle) {
|
|
|
97
106
|
scopeCount: scopes.length,
|
|
98
107
|
semanticSummary: summary,
|
|
99
108
|
kindDistribution,
|
|
109
|
+
pe2Evidence: projectDocumentPe2Evidence(runtime, pe2Evidence),
|
|
100
110
|
};
|
|
101
111
|
},
|
|
102
112
|
|
|
@@ -33,6 +33,10 @@ import type {
|
|
|
33
33
|
} from "../../../runtime/scopes/index.ts";
|
|
34
34
|
import type { AIAction } from "../../../runtime/workflow/ai-action-policy.ts";
|
|
35
35
|
import type { TextFormattingDirective } from "../../public-types.ts";
|
|
36
|
+
import {
|
|
37
|
+
projectAuditReference,
|
|
38
|
+
type AiActionAuditReference,
|
|
39
|
+
} from "./_audit-reference.ts";
|
|
36
40
|
|
|
37
41
|
export interface ReplacementProposalInput {
|
|
38
42
|
readonly targetScopeId: string;
|
|
@@ -123,7 +127,9 @@ export interface ApplyResult {
|
|
|
123
127
|
readonly applied: boolean;
|
|
124
128
|
readonly reason?: string;
|
|
125
129
|
readonly blockers?: readonly string[];
|
|
130
|
+
readonly blockerDetails?: readonly ActionBlockerDetail[];
|
|
126
131
|
readonly auditHint?: string;
|
|
132
|
+
readonly auditReference?: AiActionAuditReference;
|
|
127
133
|
/**
|
|
128
134
|
* Gap A (post-Slice-7 integration) — revision IDs authored during
|
|
129
135
|
* the apply. Populated for suggest-mode (tracked insert + delete);
|
|
@@ -133,6 +139,19 @@ export interface ApplyResult {
|
|
|
133
139
|
readonly authoredRevisionIds: readonly string[];
|
|
134
140
|
}
|
|
135
141
|
|
|
142
|
+
export interface ActionBlockerDetail {
|
|
143
|
+
readonly code: string;
|
|
144
|
+
readonly category:
|
|
145
|
+
| "unsupported-scope-kind"
|
|
146
|
+
| "unsupported-operation"
|
|
147
|
+
| "unresolved-scope"
|
|
148
|
+
| "policy-or-guard";
|
|
149
|
+
readonly message: string;
|
|
150
|
+
readonly nextStep: string;
|
|
151
|
+
readonly scopeKind?: string;
|
|
152
|
+
readonly operation?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
136
155
|
export interface ApplyReplacementScopeInput {
|
|
137
156
|
readonly targetScopeId: string;
|
|
138
157
|
readonly operation?: ReplacementOperationKind;
|
|
@@ -263,6 +282,97 @@ function projectValidationResult(
|
|
|
263
282
|
};
|
|
264
283
|
}
|
|
265
284
|
|
|
285
|
+
function blockerDetailFor(code: string): ActionBlockerDetail | null {
|
|
286
|
+
if (code.startsWith("scope-not-resolvable:")) {
|
|
287
|
+
return {
|
|
288
|
+
code,
|
|
289
|
+
category: "unresolved-scope",
|
|
290
|
+
message: "The target scope no longer resolves in the current document.",
|
|
291
|
+
nextStep:
|
|
292
|
+
"Call ai.resolveReference or ai.queryScopeAtPosition again, then retry with the returned handle's scopeId.",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!code.startsWith("compile-refused:")) return null;
|
|
297
|
+
|
|
298
|
+
const [, scopeKind = "unknown", ...rest] = code.split(":");
|
|
299
|
+
const suffix = rest.join(":");
|
|
300
|
+
if (suffix.startsWith("operation-not-implemented:")) {
|
|
301
|
+
const operation = suffix.slice("operation-not-implemented:".length);
|
|
302
|
+
return {
|
|
303
|
+
code,
|
|
304
|
+
category: "unsupported-operation",
|
|
305
|
+
scopeKind,
|
|
306
|
+
operation,
|
|
307
|
+
message: `The ${operation} operation is not implemented for ${scopeKind} scopes.`,
|
|
308
|
+
nextStep:
|
|
309
|
+
"Retry with operation:\"replace\" when that is acceptable, or attach an explanation/issue instead of mutating.",
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (scopeKind === "scope" && suffix === "multi-paragraph-replace-not-implemented") {
|
|
314
|
+
return {
|
|
315
|
+
code,
|
|
316
|
+
category: "unsupported-scope-kind",
|
|
317
|
+
scopeKind,
|
|
318
|
+
message: "Multi-paragraph scope replacement is not implemented.",
|
|
319
|
+
nextStep:
|
|
320
|
+
"Split the request into paragraph-scoped replacements, or attach an explanation/issue until the multi-paragraph planner ships.",
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (scopeKind === "table" || scopeKind === "table-row" || scopeKind === "table-cell") {
|
|
325
|
+
return {
|
|
326
|
+
code,
|
|
327
|
+
category: "unsupported-scope-kind",
|
|
328
|
+
scopeKind,
|
|
329
|
+
message: `Flat text replacement is not implemented for ${scopeKind} scopes because it can break table structure.`,
|
|
330
|
+
nextStep:
|
|
331
|
+
"Use ai.attachExplanation or ai.createIssue for now, or wait for the Layer 08 table-family replacement planner.",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (scopeKind === "field") {
|
|
336
|
+
return {
|
|
337
|
+
code,
|
|
338
|
+
category: "unsupported-scope-kind",
|
|
339
|
+
scopeKind,
|
|
340
|
+
message: "Field result replacement is preserve-only because field text is computed from instructions.",
|
|
341
|
+
nextStep:
|
|
342
|
+
"Attach an explanation/issue, or use a future field-aware edit path that updates field instructions safely.",
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (scopeKind === "image" || scopeKind === "note") {
|
|
347
|
+
return {
|
|
348
|
+
code,
|
|
349
|
+
category: "unsupported-scope-kind",
|
|
350
|
+
scopeKind,
|
|
351
|
+
message: `Replacement is not implemented for ${scopeKind} scopes.`,
|
|
352
|
+
nextStep:
|
|
353
|
+
"Attach an explanation/issue rather than mutating until a scope-specific planner exists.",
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
code,
|
|
359
|
+
category: "unsupported-scope-kind",
|
|
360
|
+
scopeKind,
|
|
361
|
+
message: `Replacement is not implemented for ${scopeKind} scopes.`,
|
|
362
|
+
nextStep:
|
|
363
|
+
"Use a supported paragraph-like scope, or attach an explanation/issue and route the unsupported planner to the owning layer.",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function projectBlockerDetails(
|
|
368
|
+
blockers: readonly string[],
|
|
369
|
+
): readonly ActionBlockerDetail[] | undefined {
|
|
370
|
+
const details = blockers
|
|
371
|
+
.map((code) => blockerDetailFor(code))
|
|
372
|
+
.filter((detail): detail is ActionBlockerDetail => detail !== null);
|
|
373
|
+
return details.length > 0 ? Object.freeze(details) : undefined;
|
|
374
|
+
}
|
|
375
|
+
|
|
266
376
|
export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
267
377
|
const compiler = createScopeCompilerService(runtime);
|
|
268
378
|
return {
|
|
@@ -371,6 +481,9 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
371
481
|
expectedDelta: applyReplacementScopeMetadata.uxIntent.expectedDelta,
|
|
372
482
|
});
|
|
373
483
|
|
|
484
|
+
const blockerDetails = projectBlockerDetails(
|
|
485
|
+
result.validation.blockedReasons,
|
|
486
|
+
);
|
|
374
487
|
return {
|
|
375
488
|
proposalId,
|
|
376
489
|
applied: result.applied,
|
|
@@ -378,7 +491,11 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
378
491
|
...(result.validation.blockedReasons.length > 0
|
|
379
492
|
? { blockers: Object.freeze([...result.validation.blockedReasons]) }
|
|
380
493
|
: {}),
|
|
494
|
+
...(blockerDetails ? { blockerDetails } : {}),
|
|
381
495
|
...(result.audit ? { auditHint: result.audit.actionId } : {}),
|
|
496
|
+
...(result.audit
|
|
497
|
+
? { auditReference: projectAuditReference(result.audit) }
|
|
498
|
+
: {}),
|
|
382
499
|
authoredRevisionIds: result.authoredRevisionIds,
|
|
383
500
|
};
|
|
384
501
|
},
|
|
@@ -412,6 +529,9 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
412
529
|
expectedDelta: applyScopeActionMetadata.uxIntent.expectedDelta,
|
|
413
530
|
});
|
|
414
531
|
|
|
532
|
+
const blockerDetails = projectBlockerDetails(
|
|
533
|
+
result.validation.blockedReasons,
|
|
534
|
+
);
|
|
415
535
|
return {
|
|
416
536
|
proposalId,
|
|
417
537
|
applied: result.applied,
|
|
@@ -419,7 +539,11 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
419
539
|
...(result.validation.blockedReasons.length > 0
|
|
420
540
|
? { blockers: Object.freeze([...result.validation.blockedReasons]) }
|
|
421
541
|
: {}),
|
|
542
|
+
...(blockerDetails ? { blockerDetails } : {}),
|
|
422
543
|
...(result.audit ? { auditHint: result.audit.actionId } : {}),
|
|
544
|
+
...(result.audit
|
|
545
|
+
? { auditReference: projectAuditReference(result.audit) }
|
|
546
|
+
: {}),
|
|
423
547
|
authoredRevisionIds: result.authoredRevisionIds,
|
|
424
548
|
};
|
|
425
549
|
},
|
package/src/api/v3/index.ts
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
export { createApiV3 } from "./_create.ts";
|
|
16
16
|
export type { ApiV3, CreateApiV3Opts } from "./_create.ts";
|
|
17
|
+
export type {
|
|
18
|
+
AiPe2EvidenceOptions,
|
|
19
|
+
AiPe2OracleEvidence,
|
|
20
|
+
AiPe2OracleEvidenceProvider,
|
|
21
|
+
AiPe2OracleEvidenceProviderInput,
|
|
22
|
+
AiPe2OracleVerdict,
|
|
23
|
+
} from "./ai/_pe2-evidence.ts";
|
|
17
24
|
|
|
18
25
|
export type {
|
|
19
26
|
ApiStatus,
|
package/src/api/v3/ui/_types.ts
CHANGED
|
@@ -118,6 +118,21 @@ export interface UiController {
|
|
|
118
118
|
* error rather than silently no-op.
|
|
119
119
|
*/
|
|
120
120
|
readonly subscribeViewport?: (listener: UiListener<ViewportState>) => UiUnsubscribe;
|
|
121
|
+
/**
|
|
122
|
+
* PE2 page-residency policy read. L10 owns the observable residency
|
|
123
|
+
* shape; L11 owns realized DOM/PM attachment and L05 owns geometry
|
|
124
|
+
* caches. Omitting the hook makes `ui.viewport.getPageResidency`
|
|
125
|
+
* return an explicit unavailable snapshot.
|
|
126
|
+
*/
|
|
127
|
+
readonly getPageResidency?: (pageIndex: number) => PageResidencySnapshot;
|
|
128
|
+
/**
|
|
129
|
+
* Page-residency subscription. Fires when the mounted surface changes
|
|
130
|
+
* whether a page is realized, cold, or evicted.
|
|
131
|
+
*/
|
|
132
|
+
readonly subscribePageResidency?: (
|
|
133
|
+
pageIndex: number,
|
|
134
|
+
listener: UiListener<PageResidencySnapshot>,
|
|
135
|
+
) => UiUnsubscribe;
|
|
121
136
|
/**
|
|
122
137
|
* Overlay invalidation subscription. Fires when geometry invalidation
|
|
123
138
|
* ranges overlap attached overlay queries (U7). `ui.overlays.subscribe`
|
|
@@ -127,6 +142,21 @@ export interface UiController {
|
|
|
127
142
|
readonly subscribeOverlays?: (
|
|
128
143
|
listener: UiListener<OverlayAnchorQuery>,
|
|
129
144
|
) => UiUnsubscribe;
|
|
145
|
+
/**
|
|
146
|
+
* PE2 overlay-lane snapshot resolver. This is the mounted-surface lane
|
|
147
|
+
* contract for selection/caret/redline/scope/comment/table/object/search
|
|
148
|
+
* and presence overlays. The bind-side supplies already-projected lane
|
|
149
|
+
* entries; L10 owns the observable shape, not layout or geometry caches.
|
|
150
|
+
*/
|
|
151
|
+
readonly getOverlayLane?: (kind: UiOverlayLaneKind) => UiOverlayLaneSnapshot;
|
|
152
|
+
/**
|
|
153
|
+
* PE2 overlay-lane subscription. Must fan out lane changes without
|
|
154
|
+
* dispatching PM document transactions or invalidating L04 layout.
|
|
155
|
+
*/
|
|
156
|
+
readonly subscribeOverlayLane?: (
|
|
157
|
+
kind: UiOverlayLaneKind,
|
|
158
|
+
listener: UiListener<UiOverlayLaneSnapshot>,
|
|
159
|
+
) => UiUnsubscribe;
|
|
130
160
|
/**
|
|
131
161
|
* Overlay anchor resolver. Expected to be a direct projection of the
|
|
132
162
|
* geometry facet (U4 — overlays derive from geometry, not DOM). Returns
|
|
@@ -219,6 +249,29 @@ export interface ViewportState {
|
|
|
219
249
|
readonly devicePixelRatio: number;
|
|
220
250
|
}
|
|
221
251
|
|
|
252
|
+
export type PageResidency = "realized" | "cold" | "evicted";
|
|
253
|
+
|
|
254
|
+
export type RehydrationStatus =
|
|
255
|
+
| "available"
|
|
256
|
+
| "requires-rehydration"
|
|
257
|
+
| "unavailable";
|
|
258
|
+
|
|
259
|
+
export type PageResidencySource =
|
|
260
|
+
| "controller"
|
|
261
|
+
| "unavailable";
|
|
262
|
+
|
|
263
|
+
export interface PageResidencySnapshot {
|
|
264
|
+
readonly __mock?: true;
|
|
265
|
+
/** 0-based page index, matching GeometryFacet.getPage(index). */
|
|
266
|
+
readonly pageIndex: number;
|
|
267
|
+
readonly pageId?: string;
|
|
268
|
+
readonly residency: PageResidency;
|
|
269
|
+
readonly status: RehydrationStatus;
|
|
270
|
+
readonly revision: number;
|
|
271
|
+
readonly source: PageResidencySource;
|
|
272
|
+
readonly reason?: string;
|
|
273
|
+
}
|
|
274
|
+
|
|
222
275
|
export type ScrollTargetBehavior = "auto" | "smooth" | "instant";
|
|
223
276
|
|
|
224
277
|
export type ScrollTarget =
|
|
@@ -267,6 +320,60 @@ export type OverlayAnchorQuery =
|
|
|
267
320
|
| { kind: "page"; value: number }
|
|
268
321
|
| { kind: "selection" };
|
|
269
322
|
|
|
323
|
+
/* ================================================================== */
|
|
324
|
+
/* PE2 overlay lanes */
|
|
325
|
+
/* ================================================================== */
|
|
326
|
+
|
|
327
|
+
export type UiOverlayLaneKind =
|
|
328
|
+
| "selection"
|
|
329
|
+
| "caret"
|
|
330
|
+
| "redlines"
|
|
331
|
+
| "field-scopes"
|
|
332
|
+
| "broad-scopes"
|
|
333
|
+
| "comments"
|
|
334
|
+
| "issues"
|
|
335
|
+
| "tables"
|
|
336
|
+
| "objects"
|
|
337
|
+
| "search"
|
|
338
|
+
| "presence";
|
|
339
|
+
|
|
340
|
+
export type UiOverlayLaneStatus =
|
|
341
|
+
| "resolved"
|
|
342
|
+
| "requires-rehydration"
|
|
343
|
+
| "unavailable";
|
|
344
|
+
|
|
345
|
+
export type UiOverlayLaneSource =
|
|
346
|
+
| "geometry"
|
|
347
|
+
| "workflow"
|
|
348
|
+
| "search"
|
|
349
|
+
| "awareness"
|
|
350
|
+
| "controller"
|
|
351
|
+
| "unavailable";
|
|
352
|
+
|
|
353
|
+
export interface UiOverlayLaneEntry {
|
|
354
|
+
readonly id: string;
|
|
355
|
+
readonly status: UiOverlayLaneStatus;
|
|
356
|
+
readonly anchor?: OverlayAnchorQuery;
|
|
357
|
+
readonly rects?: readonly GeometryRect[];
|
|
358
|
+
readonly reason?: string;
|
|
359
|
+
/**
|
|
360
|
+
* Lane-specific plain payload. Examples: peer identity for presence,
|
|
361
|
+
* issue severity, search rank, table/object classification. Kept plain
|
|
362
|
+
* so non-React consumers and debug runners can serialize snapshots.
|
|
363
|
+
*/
|
|
364
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface UiOverlayLaneSnapshot {
|
|
368
|
+
readonly __mock?: true;
|
|
369
|
+
readonly kind: UiOverlayLaneKind;
|
|
370
|
+
readonly status: UiOverlayLaneStatus;
|
|
371
|
+
readonly entries: readonly UiOverlayLaneEntry[];
|
|
372
|
+
readonly revision: number;
|
|
373
|
+
readonly source: UiOverlayLaneSource;
|
|
374
|
+
readonly reason?: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
270
377
|
/* ================================================================== */
|
|
271
378
|
/* Overlay visibility — U9 (state-classes X3) */
|
|
272
379
|
/* ================================================================== */
|
|
@@ -383,6 +490,21 @@ export interface ApiV3UiSurface {
|
|
|
383
490
|
export interface ApiV3UiViewport {
|
|
384
491
|
get(): ViewportState;
|
|
385
492
|
subscribe(listener: UiListener<ViewportState>): UiUnsubscribe;
|
|
493
|
+
/**
|
|
494
|
+
* Read L10's page-residency policy for a 0-based page index. When no
|
|
495
|
+
* mounted controller has supplied a residency policy yet, returns an
|
|
496
|
+
* explicit `unavailable` mock snapshot instead of probing geometry or
|
|
497
|
+
* realizing DOM.
|
|
498
|
+
*/
|
|
499
|
+
getPageResidency(pageIndex: number): PageResidencySnapshot;
|
|
500
|
+
/**
|
|
501
|
+
* Subscribe to residency changes for a 0-based page index. The listener
|
|
502
|
+
* receives plain snapshots; realization/teardown stays owned by L11.
|
|
503
|
+
*/
|
|
504
|
+
subscribePageResidency(
|
|
505
|
+
pageIndex: number,
|
|
506
|
+
listener: UiListener<PageResidencySnapshot>,
|
|
507
|
+
): UiUnsubscribe;
|
|
386
508
|
|
|
387
509
|
/**
|
|
388
510
|
* Scroll the mounted surface to a specific 1-based page number.
|
|
@@ -453,6 +575,23 @@ export interface ApiV3UiOverlays {
|
|
|
453
575
|
query: OverlayAnchorQuery,
|
|
454
576
|
listener: UiListener<GeometryRect | null>,
|
|
455
577
|
): UiUnsubscribe;
|
|
578
|
+
/**
|
|
579
|
+
* PE2 overlay-lane read. Returns a plain snapshot for a mounted lane
|
|
580
|
+
* such as selection, caret, redlines, comments, tables, objects,
|
|
581
|
+
* search, or presence. When the host has not wired a lane yet, returns
|
|
582
|
+
* an explicit `unavailable` snapshot instead of measuring DOM or
|
|
583
|
+
* forcing geometry rehydration.
|
|
584
|
+
*/
|
|
585
|
+
getLane(kind: UiOverlayLaneKind): UiOverlayLaneSnapshot;
|
|
586
|
+
/**
|
|
587
|
+
* PE2 overlay-lane subscription. The listener receives lane snapshots,
|
|
588
|
+
* never PM transactions. Presence/awareness updates must travel through
|
|
589
|
+
* this channel without invalidating L04 layout.
|
|
590
|
+
*/
|
|
591
|
+
subscribeLane(
|
|
592
|
+
kind: UiOverlayLaneKind,
|
|
593
|
+
listener: UiListener<UiOverlayLaneSnapshot>,
|
|
594
|
+
): UiUnsubscribe;
|
|
456
595
|
|
|
457
596
|
/**
|
|
458
597
|
* U9 · Composed overlay visibility. Merges the class-A policy from
|
package/src/api/v3/ui/index.ts
CHANGED
|
@@ -24,6 +24,10 @@ export type {
|
|
|
24
24
|
UiScopeListFilter,
|
|
25
25
|
UiScopeRailOptions,
|
|
26
26
|
ViewportState,
|
|
27
|
+
PageResidency,
|
|
28
|
+
PageResidencySnapshot,
|
|
29
|
+
PageResidencySource,
|
|
30
|
+
RehydrationStatus,
|
|
27
31
|
ScrollTarget,
|
|
28
32
|
ScrollTargetBehavior,
|
|
29
33
|
ScrollToPageResult,
|
|
@@ -43,6 +47,11 @@ export type {
|
|
|
43
47
|
DebugSession,
|
|
44
48
|
DebugAttachment,
|
|
45
49
|
GeometryRect,
|
|
50
|
+
UiOverlayLaneEntry,
|
|
51
|
+
UiOverlayLaneKind,
|
|
52
|
+
UiOverlayLaneSnapshot,
|
|
53
|
+
UiOverlayLaneSource,
|
|
54
|
+
UiOverlayLaneStatus,
|
|
46
55
|
} from "./_types.ts";
|
|
47
56
|
|
|
48
57
|
// Chrome composition types (U5.b) — relocated to layer 10 in Slice 8.
|
|
@@ -42,6 +42,8 @@ import type {
|
|
|
42
42
|
GeometryRect,
|
|
43
43
|
OverlayAnchorQuery,
|
|
44
44
|
OverlayVisibility,
|
|
45
|
+
UiOverlayLaneKind,
|
|
46
|
+
UiOverlayLaneSnapshot,
|
|
45
47
|
UiListener,
|
|
46
48
|
UiUnsubscribe,
|
|
47
49
|
} from "./_types.ts";
|
|
@@ -195,6 +197,57 @@ export const subscribeQueryMetadata: ApiV3FnMetadata = {
|
|
|
195
197
|
rwdReference: "§UI API § ui.overlays.subscribeQuery (KI-006 close). Per-query coalesced variant of `subscribe`. Registers a shared coarse `controller.subscribeOverlays` tick on the first query subscription; re-resolves each attached query on every tick via `getAnchor(query)` (memoized); fires per-query listeners only when the rect actually changes. Torn down on last unsubscribe. Listener receives the new `GeometryRect | null` value. Callers that need the full invalidation stream keep using `subscribe(listener)`.",
|
|
196
198
|
};
|
|
197
199
|
|
|
200
|
+
// ----- PE2 overlay-lane contract -------------------------------------------
|
|
201
|
+
|
|
202
|
+
export const getLaneMetadata: ApiV3FnMetadata = {
|
|
203
|
+
name: "ui.overlays.getLane",
|
|
204
|
+
status: "live-with-adapter",
|
|
205
|
+
sourceLayer: "presentation",
|
|
206
|
+
liveEvidence: {
|
|
207
|
+
runnerTest: "test/api/v3/ui/overlays-lanes.test.ts",
|
|
208
|
+
commit: "refactor-10-pe2-overlay-lanes",
|
|
209
|
+
},
|
|
210
|
+
mockShape: {
|
|
211
|
+
deterministic: true,
|
|
212
|
+
seededFrom: "fixed",
|
|
213
|
+
shapeDescription:
|
|
214
|
+
"Unavailable UiOverlayLaneSnapshot with entries:[] and revision:0 when the mounted controller has not wired the requested PE2 overlay lane yet.",
|
|
215
|
+
carriesMockFlag: true,
|
|
216
|
+
},
|
|
217
|
+
uxIntent: { uiVisible: false },
|
|
218
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-lane-read" },
|
|
219
|
+
stateClass: "C-local",
|
|
220
|
+
persistsTo: "none",
|
|
221
|
+
rwdReference:
|
|
222
|
+
"§UI API § PE2 overlay lanes. Reads the mounted controller's lane snapshot for selection/caret/redlines/field-scopes/broad-scopes/comments/issues/tables/objects/search/presence. Returns explicit status:'unavailable' when the lane is not wired; never measures DOM or wakes layout.",
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export const subscribeLaneMetadata: ApiV3FnMetadata = {
|
|
226
|
+
name: "ui.overlays.subscribeLane",
|
|
227
|
+
status: "live-with-adapter",
|
|
228
|
+
sourceLayer: "presentation",
|
|
229
|
+
liveEvidence: {
|
|
230
|
+
runnerTest: "test/api/v3/ui/overlays-lanes.test.ts",
|
|
231
|
+
commit: "refactor-10-pe2-overlay-lanes",
|
|
232
|
+
},
|
|
233
|
+
uxIntent: {
|
|
234
|
+
uiVisible: true,
|
|
235
|
+
expectsUxResponse: "surface-refresh",
|
|
236
|
+
expectedDelta: "overlay-lane subscriber attached; future lane snapshots propagate through the listener without PM document transactions",
|
|
237
|
+
},
|
|
238
|
+
agentMetadata: { readOrMutate: "read", boundedScope: "session", auditCategory: "ui-overlays-lane-subscribe" },
|
|
239
|
+
stateClass: "C-local",
|
|
240
|
+
persistsTo: "none",
|
|
241
|
+
bidirectional: true,
|
|
242
|
+
subscriptionShape: {
|
|
243
|
+
eventType: "ui.overlays.lane_changed",
|
|
244
|
+
payloadType: "UiOverlayLaneSnapshot",
|
|
245
|
+
coalescing: "raf",
|
|
246
|
+
},
|
|
247
|
+
rwdReference:
|
|
248
|
+
"§UI API § PE2 overlay lanes. Adapter delegates to UiController.subscribeOverlayLane(kind, listener). Presence/awareness, selection, search, and review lane updates are observable UI state only; this method does not dispatch PM document transactions or invalidate L04 layout.",
|
|
249
|
+
};
|
|
250
|
+
|
|
198
251
|
// ----- U9 overlay-visibility metadata (state-classes X3) -----
|
|
199
252
|
|
|
200
253
|
export const getVisibilityMetadata: ApiV3FnMetadata = {
|
|
@@ -354,6 +407,21 @@ export function createOverlaysFamily(ctx: UiApiContext) {
|
|
|
354
407
|
const queryChannels = new Map<string, QueryChannel>();
|
|
355
408
|
let queryCoarseUnsubscribe: (() => void) | null = null;
|
|
356
409
|
|
|
410
|
+
function unavailableLane(
|
|
411
|
+
kind: UiOverlayLaneKind,
|
|
412
|
+
reason = "overlay lane is not wired on the active controller",
|
|
413
|
+
): UiOverlayLaneSnapshot {
|
|
414
|
+
return {
|
|
415
|
+
__mock: true,
|
|
416
|
+
kind,
|
|
417
|
+
status: "unavailable",
|
|
418
|
+
entries: Object.freeze([]),
|
|
419
|
+
revision: 0,
|
|
420
|
+
source: "unavailable",
|
|
421
|
+
reason,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
357
425
|
function queryKey(q: OverlayAnchorQuery): string {
|
|
358
426
|
// `selection` is a singleton kind with no `value` — key by kind alone.
|
|
359
427
|
return q.kind === "selection" ? "selection" : `${q.kind}:${q.value}`;
|
|
@@ -560,6 +628,42 @@ export function createOverlaysFamily(ctx: UiApiContext) {
|
|
|
560
628
|
};
|
|
561
629
|
},
|
|
562
630
|
|
|
631
|
+
// ----- PE2 overlay lanes -----
|
|
632
|
+
|
|
633
|
+
getLane(kind: UiOverlayLaneKind): UiOverlayLaneSnapshot {
|
|
634
|
+
const hook = ctx.binding?.controller.getOverlayLane;
|
|
635
|
+
return hook
|
|
636
|
+
? hook(kind)
|
|
637
|
+
: unavailableLane(kind);
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
subscribeLane(
|
|
641
|
+
kind: UiOverlayLaneKind,
|
|
642
|
+
listener: UiListener<UiOverlayLaneSnapshot>,
|
|
643
|
+
): UiUnsubscribe {
|
|
644
|
+
const controller = ctx.binding?.controller;
|
|
645
|
+
if (!controller) {
|
|
646
|
+
throw new Error(
|
|
647
|
+
"ui.overlays.subscribeLane: no controller bound — call ui.session.bind(controller) first",
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
if (!controller.subscribeOverlayLane) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`ui.overlays.subscribeLane: controller of kind "${controller.kind}" did not provide a subscribeOverlayLane hook`,
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
const unsubscribe = controller.subscribeOverlayLane(kind, listener);
|
|
656
|
+
emitUxResponse(ctx.handle, {
|
|
657
|
+
apiFn: subscribeLaneMetadata.name,
|
|
658
|
+
intent: subscribeLaneMetadata.uxIntent.expectedDelta ?? "",
|
|
659
|
+
mockOrLive: "live-with-adapter",
|
|
660
|
+
uiVisible: true,
|
|
661
|
+
expectedDelta: subscribeLaneMetadata.uxIntent.expectedDelta,
|
|
662
|
+
actualDelta: { kind: "surface-refresh", payload: { subscribed: "ui.overlays.lane", lane: kind } },
|
|
663
|
+
});
|
|
664
|
+
return unsubscribe;
|
|
665
|
+
},
|
|
666
|
+
|
|
563
667
|
// ----- U9 overlay-visibility (state-classes X3) -----
|
|
564
668
|
|
|
565
669
|
getVisibility(kind: OverlayKind): OverlayVisibility {
|