@beyondwork/docx-react-component 1.0.104 → 1.0.105
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/ai/_pe2-evidence.ts +153 -0
- package/src/api/v3/ai/bundle.ts +13 -5
- package/src/api/v3/ai/inspect.ts +7 -1
- package/src/api/v3/ai/replacement.ts +113 -0
- package/src/api/v3/ui/_types.ts +86 -0
- package/src/api/v3/ui/index.ts +5 -0
- package/src/api/v3/ui/overlays.ts +104 -0
- package/src/model/layout/index.ts +3 -0
- package/src/model/layout/page-graph-types.ts +89 -0
- package/src/model/layout/runtime-page-graph-types.ts +13 -0
- 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 +388 -11
- 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 +29 -1
- package/src/runtime/layout/page-graph.ts +695 -10
- package/src/runtime/layout/project-block-fragments.ts +101 -1
- package/src/runtime/layout/public-facet.ts +152 -0
- package/src/runtime/prerender/graph-canonicalize.ts +14 -0
- package/src/ui/ui-controller-factory.ts +11 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.105",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
package/src/api/public-types.ts
CHANGED
|
@@ -177,9 +177,11 @@ export type {
|
|
|
177
177
|
PublicBlockMeasurement,
|
|
178
178
|
PublicFieldDirtinessReport,
|
|
179
179
|
PublicLineBox,
|
|
180
|
+
PublicLayoutDivergence,
|
|
180
181
|
PublicMeasurementFidelity,
|
|
181
182
|
PublicNoteAllocation,
|
|
182
183
|
PublicPageAnchor,
|
|
184
|
+
PublicPageFrame,
|
|
183
185
|
PublicPageNode,
|
|
184
186
|
PublicPageRegion,
|
|
185
187
|
PublicPageRegions,
|
|
@@ -190,6 +192,7 @@ export type {
|
|
|
190
192
|
PublicResolvedParagraphFormatting,
|
|
191
193
|
PublicResolvedRunFormatting,
|
|
192
194
|
PublicSectionNode,
|
|
195
|
+
PublicTwipsRect,
|
|
193
196
|
// R0.5: named page-format + margin-preset catalogs
|
|
194
197
|
PageFormatDefinition,
|
|
195
198
|
ActivePageFormat,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 09 PE2 evidence projection.
|
|
3
|
+
*
|
|
4
|
+
* The AI API should not leak the full runtime geometry index. Agents need a
|
|
5
|
+
* small, JSON-stable read model that says whether PE2 geometry evidence is
|
|
6
|
+
* available and, for scope reads, whether a replacement envelope exists for
|
|
7
|
+
* the requested scope.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { RuntimeApiHandle } from "../_runtime-handle.ts";
|
|
11
|
+
import type {
|
|
12
|
+
GeometryIndexCoverage,
|
|
13
|
+
GeometryPrecision,
|
|
14
|
+
GeometryRehydrationStatus,
|
|
15
|
+
GeometryReplacementEnvelopeEntry,
|
|
16
|
+
GeometrySourceIdentity,
|
|
17
|
+
} from "../../../runtime/geometry/index.ts";
|
|
18
|
+
|
|
19
|
+
export interface AiPe2GeometryCoverageEvidence {
|
|
20
|
+
readonly status: GeometryRehydrationStatus;
|
|
21
|
+
readonly pageCount: number;
|
|
22
|
+
readonly regionCount: number;
|
|
23
|
+
readonly sliceCount: number;
|
|
24
|
+
readonly lineCount: number;
|
|
25
|
+
readonly anchorCount: number;
|
|
26
|
+
readonly hitTargetCount: number;
|
|
27
|
+
readonly semanticEntryCount: number;
|
|
28
|
+
readonly replacementEnvelopeCount: number;
|
|
29
|
+
readonly objectHandleCount: number;
|
|
30
|
+
readonly precision: {
|
|
31
|
+
readonly exact: number;
|
|
32
|
+
readonly "within-tolerance": number;
|
|
33
|
+
readonly heuristic: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AiPe2DocumentEvidence {
|
|
38
|
+
readonly geometry: AiPe2GeometryCoverageEvidence;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AiPe2ScopeReplacementEnvelopeEvidence {
|
|
42
|
+
readonly status: GeometryRehydrationStatus;
|
|
43
|
+
readonly precision: GeometryPrecision;
|
|
44
|
+
readonly rectCount: number;
|
|
45
|
+
readonly pageIds?: readonly string[];
|
|
46
|
+
readonly sourceIdentity?: GeometrySourceIdentity;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AiPe2ScopeEvidence {
|
|
50
|
+
readonly geometry: {
|
|
51
|
+
readonly coverage: AiPe2GeometryCoverageEvidence;
|
|
52
|
+
readonly replacementEnvelope?: AiPe2ScopeReplacementEnvelopeEvidence;
|
|
53
|
+
readonly reason?: "geometry-index-unavailable" | "scope-envelope-not-found";
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function copyCoverage(coverage: GeometryIndexCoverage): AiPe2GeometryCoverageEvidence {
|
|
58
|
+
return {
|
|
59
|
+
status: coverage.status,
|
|
60
|
+
pageCount: coverage.pageCount,
|
|
61
|
+
regionCount: coverage.regionCount,
|
|
62
|
+
sliceCount: coverage.sliceCount,
|
|
63
|
+
lineCount: coverage.lineCount,
|
|
64
|
+
anchorCount: coverage.anchorCount,
|
|
65
|
+
hitTargetCount: coverage.hitTargetCount,
|
|
66
|
+
semanticEntryCount: coverage.semanticEntryCount,
|
|
67
|
+
replacementEnvelopeCount: coverage.replacementEnvelopeCount,
|
|
68
|
+
objectHandleCount: coverage.objectHandleCount,
|
|
69
|
+
precision: {
|
|
70
|
+
exact: coverage.precision.exact,
|
|
71
|
+
"within-tolerance": coverage.precision["within-tolerance"],
|
|
72
|
+
heuristic: coverage.precision.heuristic,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function copyEnvelope(
|
|
78
|
+
envelope: GeometryReplacementEnvelopeEntry,
|
|
79
|
+
): AiPe2ScopeReplacementEnvelopeEvidence {
|
|
80
|
+
return {
|
|
81
|
+
status: envelope.status,
|
|
82
|
+
precision: envelope.precision,
|
|
83
|
+
rectCount: envelope.rects.length,
|
|
84
|
+
...(envelope.pageIds ? { pageIds: [...envelope.pageIds] } : {}),
|
|
85
|
+
...(envelope.sourceIdentity ? { sourceIdentity: { ...envelope.sourceIdentity } } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const UNAVAILABLE_COVERAGE: AiPe2GeometryCoverageEvidence = {
|
|
90
|
+
status: "unavailable",
|
|
91
|
+
pageCount: 0,
|
|
92
|
+
regionCount: 0,
|
|
93
|
+
sliceCount: 0,
|
|
94
|
+
lineCount: 0,
|
|
95
|
+
anchorCount: 0,
|
|
96
|
+
hitTargetCount: 0,
|
|
97
|
+
semanticEntryCount: 0,
|
|
98
|
+
replacementEnvelopeCount: 0,
|
|
99
|
+
objectHandleCount: 0,
|
|
100
|
+
precision: { exact: 0, "within-tolerance": 0, heuristic: 0 },
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export function projectDocumentPe2Evidence(
|
|
104
|
+
runtime: RuntimeApiHandle,
|
|
105
|
+
): AiPe2DocumentEvidence {
|
|
106
|
+
if (!runtime.geometry) {
|
|
107
|
+
return { geometry: UNAVAILABLE_COVERAGE };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
geometry: copyCoverage(runtime.geometry.getGeometryCoverage()),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function projectScopePe2Evidence(
|
|
115
|
+
runtime: RuntimeApiHandle,
|
|
116
|
+
scopeId: string,
|
|
117
|
+
): AiPe2ScopeEvidence {
|
|
118
|
+
if (!runtime.geometry) {
|
|
119
|
+
return {
|
|
120
|
+
geometry: {
|
|
121
|
+
coverage: UNAVAILABLE_COVERAGE,
|
|
122
|
+
reason: "geometry-index-unavailable",
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const coverage = copyCoverage(runtime.geometry.getGeometryCoverage());
|
|
127
|
+
const index = runtime.geometry.getGeometryIndex();
|
|
128
|
+
if (!index) {
|
|
129
|
+
return {
|
|
130
|
+
geometry: {
|
|
131
|
+
coverage,
|
|
132
|
+
reason: "geometry-index-unavailable",
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const envelope = index.replacementEnvelopes.find((entry) => entry.scopeId === scopeId);
|
|
138
|
+
if (!envelope) {
|
|
139
|
+
return {
|
|
140
|
+
geometry: {
|
|
141
|
+
coverage,
|
|
142
|
+
reason: "scope-envelope-not-found",
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
geometry: {
|
|
149
|
+
coverage,
|
|
150
|
+
replacementEnvelope: copyEnvelope(envelope),
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
package/src/api/v3/ai/bundle.ts
CHANGED
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
type SemanticScope,
|
|
25
25
|
} from "../../../runtime/scopes/index.ts";
|
|
26
26
|
import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
|
|
27
|
+
import {
|
|
28
|
+
projectScopePe2Evidence,
|
|
29
|
+
type AiPe2ScopeEvidence,
|
|
30
|
+
} from "./_pe2-evidence.ts";
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Handle-shaped input (architecture A3 — scope-handle-first targeting).
|
|
@@ -42,8 +46,9 @@ export interface GetScopeBundleInput {
|
|
|
42
46
|
readonly nowUtc: string;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
export type ScopeBundle = RuntimeScopeBundle & {
|
|
50
|
+
readonly pe2Evidence: AiPe2ScopeEvidence;
|
|
51
|
+
};
|
|
47
52
|
|
|
48
53
|
export interface ScopeBundleNotFound {
|
|
49
54
|
readonly notFound: true;
|
|
@@ -91,7 +96,7 @@ export const getScopeBundleMetadata: ApiV3FnMetadata = {
|
|
|
91
96
|
boundedScope: "scope",
|
|
92
97
|
auditCategory: "scope-bundle",
|
|
93
98
|
contextPromptShape:
|
|
94
|
-
"Bundle content + formatting + layout + geometry + workflow + replaceability for prompt context.",
|
|
99
|
+
"Bundle content + formatting + layout + geometry + workflow + replaceability + PE2 geometry coverage/envelope evidence for prompt context.",
|
|
95
100
|
},
|
|
96
101
|
stateClass: "A-canonical",
|
|
97
102
|
persistsTo: "canonical",
|
|
@@ -110,7 +115,7 @@ export function createBundleFamily(runtime: RuntimeApiHandle) {
|
|
|
110
115
|
return compiled?.scope ?? null;
|
|
111
116
|
},
|
|
112
117
|
|
|
113
|
-
getScopeBundle(input: GetScopeBundleInput):
|
|
118
|
+
getScopeBundle(input: GetScopeBundleInput): ScopeBundle | ScopeBundleNotFound {
|
|
114
119
|
// @endStateApi — live-with-adapter. Routes through the compiler-
|
|
115
120
|
// service facade.
|
|
116
121
|
//
|
|
@@ -126,7 +131,10 @@ export function createBundleFamily(runtime: RuntimeApiHandle) {
|
|
|
126
131
|
reason: "no scope with this id in canonical document or review store",
|
|
127
132
|
};
|
|
128
133
|
}
|
|
129
|
-
return
|
|
134
|
+
return {
|
|
135
|
+
...bundle,
|
|
136
|
+
pe2Evidence: projectScopePe2Evidence(runtime, scopeId),
|
|
137
|
+
};
|
|
130
138
|
},
|
|
131
139
|
};
|
|
132
140
|
}
|
package/src/api/v3/ai/inspect.ts
CHANGED
|
@@ -16,6 +16,10 @@ 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 AiPe2DocumentEvidence,
|
|
22
|
+
} from "./_pe2-evidence.ts";
|
|
19
23
|
|
|
20
24
|
export interface InspectDocumentResult {
|
|
21
25
|
readonly documentId: string;
|
|
@@ -23,6 +27,7 @@ export interface InspectDocumentResult {
|
|
|
23
27
|
readonly pageCount?: number;
|
|
24
28
|
readonly semanticSummary: string;
|
|
25
29
|
readonly kindDistribution: Readonly<Partial<Record<SemanticScopeKind, number>>>;
|
|
30
|
+
readonly pe2Evidence: AiPe2DocumentEvidence;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export const inspectDocumentMetadata: ApiV3FnMetadata = {
|
|
@@ -40,7 +45,7 @@ export const inspectDocumentMetadata: ApiV3FnMetadata = {
|
|
|
40
45
|
boundedScope: "document",
|
|
41
46
|
auditCategory: "document-inspect",
|
|
42
47
|
contextPromptShape:
|
|
43
|
-
"Summarize document by scope count + page count + semantic kind distribution.",
|
|
48
|
+
"Summarize document by scope count + page count + semantic kind distribution + PE2 geometry coverage evidence.",
|
|
44
49
|
},
|
|
45
50
|
stateClass: "A-canonical",
|
|
46
51
|
persistsTo: "canonical",
|
|
@@ -97,6 +102,7 @@ export function createInspectFamily(runtime: RuntimeApiHandle) {
|
|
|
97
102
|
scopeCount: scopes.length,
|
|
98
103
|
semanticSummary: summary,
|
|
99
104
|
kindDistribution,
|
|
105
|
+
pe2Evidence: projectDocumentPe2Evidence(runtime),
|
|
100
106
|
};
|
|
101
107
|
},
|
|
102
108
|
|
|
@@ -123,6 +123,7 @@ export interface ApplyResult {
|
|
|
123
123
|
readonly applied: boolean;
|
|
124
124
|
readonly reason?: string;
|
|
125
125
|
readonly blockers?: readonly string[];
|
|
126
|
+
readonly blockerDetails?: readonly ActionBlockerDetail[];
|
|
126
127
|
readonly auditHint?: string;
|
|
127
128
|
/**
|
|
128
129
|
* Gap A (post-Slice-7 integration) — revision IDs authored during
|
|
@@ -133,6 +134,19 @@ export interface ApplyResult {
|
|
|
133
134
|
readonly authoredRevisionIds: readonly string[];
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
export interface ActionBlockerDetail {
|
|
138
|
+
readonly code: string;
|
|
139
|
+
readonly category:
|
|
140
|
+
| "unsupported-scope-kind"
|
|
141
|
+
| "unsupported-operation"
|
|
142
|
+
| "unresolved-scope"
|
|
143
|
+
| "policy-or-guard";
|
|
144
|
+
readonly message: string;
|
|
145
|
+
readonly nextStep: string;
|
|
146
|
+
readonly scopeKind?: string;
|
|
147
|
+
readonly operation?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
136
150
|
export interface ApplyReplacementScopeInput {
|
|
137
151
|
readonly targetScopeId: string;
|
|
138
152
|
readonly operation?: ReplacementOperationKind;
|
|
@@ -263,6 +277,97 @@ function projectValidationResult(
|
|
|
263
277
|
};
|
|
264
278
|
}
|
|
265
279
|
|
|
280
|
+
function blockerDetailFor(code: string): ActionBlockerDetail | null {
|
|
281
|
+
if (code.startsWith("scope-not-resolvable:")) {
|
|
282
|
+
return {
|
|
283
|
+
code,
|
|
284
|
+
category: "unresolved-scope",
|
|
285
|
+
message: "The target scope no longer resolves in the current document.",
|
|
286
|
+
nextStep:
|
|
287
|
+
"Call ai.resolveReference or ai.queryScopeAtPosition again, then retry with the returned handle's scopeId.",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!code.startsWith("compile-refused:")) return null;
|
|
292
|
+
|
|
293
|
+
const [, scopeKind = "unknown", ...rest] = code.split(":");
|
|
294
|
+
const suffix = rest.join(":");
|
|
295
|
+
if (suffix.startsWith("operation-not-implemented:")) {
|
|
296
|
+
const operation = suffix.slice("operation-not-implemented:".length);
|
|
297
|
+
return {
|
|
298
|
+
code,
|
|
299
|
+
category: "unsupported-operation",
|
|
300
|
+
scopeKind,
|
|
301
|
+
operation,
|
|
302
|
+
message: `The ${operation} operation is not implemented for ${scopeKind} scopes.`,
|
|
303
|
+
nextStep:
|
|
304
|
+
"Retry with operation:\"replace\" when that is acceptable, or attach an explanation/issue instead of mutating.",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (scopeKind === "scope" && suffix === "multi-paragraph-replace-not-implemented") {
|
|
309
|
+
return {
|
|
310
|
+
code,
|
|
311
|
+
category: "unsupported-scope-kind",
|
|
312
|
+
scopeKind,
|
|
313
|
+
message: "Multi-paragraph scope replacement is not implemented.",
|
|
314
|
+
nextStep:
|
|
315
|
+
"Split the request into paragraph-scoped replacements, or attach an explanation/issue until the multi-paragraph planner ships.",
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (scopeKind === "table" || scopeKind === "table-row" || scopeKind === "table-cell") {
|
|
320
|
+
return {
|
|
321
|
+
code,
|
|
322
|
+
category: "unsupported-scope-kind",
|
|
323
|
+
scopeKind,
|
|
324
|
+
message: `Flat text replacement is not implemented for ${scopeKind} scopes because it can break table structure.`,
|
|
325
|
+
nextStep:
|
|
326
|
+
"Use ai.attachExplanation or ai.createIssue for now, or wait for the Layer 08 table-family replacement planner.",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (scopeKind === "field") {
|
|
331
|
+
return {
|
|
332
|
+
code,
|
|
333
|
+
category: "unsupported-scope-kind",
|
|
334
|
+
scopeKind,
|
|
335
|
+
message: "Field result replacement is preserve-only because field text is computed from instructions.",
|
|
336
|
+
nextStep:
|
|
337
|
+
"Attach an explanation/issue, or use a future field-aware edit path that updates field instructions safely.",
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (scopeKind === "image" || scopeKind === "note") {
|
|
342
|
+
return {
|
|
343
|
+
code,
|
|
344
|
+
category: "unsupported-scope-kind",
|
|
345
|
+
scopeKind,
|
|
346
|
+
message: `Replacement is not implemented for ${scopeKind} scopes.`,
|
|
347
|
+
nextStep:
|
|
348
|
+
"Attach an explanation/issue rather than mutating until a scope-specific planner exists.",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
code,
|
|
354
|
+
category: "unsupported-scope-kind",
|
|
355
|
+
scopeKind,
|
|
356
|
+
message: `Replacement is not implemented for ${scopeKind} scopes.`,
|
|
357
|
+
nextStep:
|
|
358
|
+
"Use a supported paragraph-like scope, or attach an explanation/issue and route the unsupported planner to the owning layer.",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function projectBlockerDetails(
|
|
363
|
+
blockers: readonly string[],
|
|
364
|
+
): readonly ActionBlockerDetail[] | undefined {
|
|
365
|
+
const details = blockers
|
|
366
|
+
.map((code) => blockerDetailFor(code))
|
|
367
|
+
.filter((detail): detail is ActionBlockerDetail => detail !== null);
|
|
368
|
+
return details.length > 0 ? Object.freeze(details) : undefined;
|
|
369
|
+
}
|
|
370
|
+
|
|
266
371
|
export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
267
372
|
const compiler = createScopeCompilerService(runtime);
|
|
268
373
|
return {
|
|
@@ -371,6 +476,9 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
371
476
|
expectedDelta: applyReplacementScopeMetadata.uxIntent.expectedDelta,
|
|
372
477
|
});
|
|
373
478
|
|
|
479
|
+
const blockerDetails = projectBlockerDetails(
|
|
480
|
+
result.validation.blockedReasons,
|
|
481
|
+
);
|
|
374
482
|
return {
|
|
375
483
|
proposalId,
|
|
376
484
|
applied: result.applied,
|
|
@@ -378,6 +486,7 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
378
486
|
...(result.validation.blockedReasons.length > 0
|
|
379
487
|
? { blockers: Object.freeze([...result.validation.blockedReasons]) }
|
|
380
488
|
: {}),
|
|
489
|
+
...(blockerDetails ? { blockerDetails } : {}),
|
|
381
490
|
...(result.audit ? { auditHint: result.audit.actionId } : {}),
|
|
382
491
|
authoredRevisionIds: result.authoredRevisionIds,
|
|
383
492
|
};
|
|
@@ -412,6 +521,9 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
412
521
|
expectedDelta: applyScopeActionMetadata.uxIntent.expectedDelta,
|
|
413
522
|
});
|
|
414
523
|
|
|
524
|
+
const blockerDetails = projectBlockerDetails(
|
|
525
|
+
result.validation.blockedReasons,
|
|
526
|
+
);
|
|
415
527
|
return {
|
|
416
528
|
proposalId,
|
|
417
529
|
applied: result.applied,
|
|
@@ -419,6 +531,7 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
|
|
|
419
531
|
...(result.validation.blockedReasons.length > 0
|
|
420
532
|
? { blockers: Object.freeze([...result.validation.blockedReasons]) }
|
|
421
533
|
: {}),
|
|
534
|
+
...(blockerDetails ? { blockerDetails } : {}),
|
|
422
535
|
...(result.audit ? { auditHint: result.audit.actionId } : {}),
|
|
423
536
|
authoredRevisionIds: result.authoredRevisionIds,
|
|
424
537
|
};
|
package/src/api/v3/ui/_types.ts
CHANGED
|
@@ -127,6 +127,21 @@ export interface UiController {
|
|
|
127
127
|
readonly subscribeOverlays?: (
|
|
128
128
|
listener: UiListener<OverlayAnchorQuery>,
|
|
129
129
|
) => UiUnsubscribe;
|
|
130
|
+
/**
|
|
131
|
+
* PE2 overlay-lane snapshot resolver. This is the mounted-surface lane
|
|
132
|
+
* contract for selection/caret/redline/scope/comment/table/object/search
|
|
133
|
+
* and presence overlays. The bind-side supplies already-projected lane
|
|
134
|
+
* entries; L10 owns the observable shape, not layout or geometry caches.
|
|
135
|
+
*/
|
|
136
|
+
readonly getOverlayLane?: (kind: UiOverlayLaneKind) => UiOverlayLaneSnapshot;
|
|
137
|
+
/**
|
|
138
|
+
* PE2 overlay-lane subscription. Must fan out lane changes without
|
|
139
|
+
* dispatching PM document transactions or invalidating L04 layout.
|
|
140
|
+
*/
|
|
141
|
+
readonly subscribeOverlayLane?: (
|
|
142
|
+
kind: UiOverlayLaneKind,
|
|
143
|
+
listener: UiListener<UiOverlayLaneSnapshot>,
|
|
144
|
+
) => UiUnsubscribe;
|
|
130
145
|
/**
|
|
131
146
|
* Overlay anchor resolver. Expected to be a direct projection of the
|
|
132
147
|
* geometry facet (U4 — overlays derive from geometry, not DOM). Returns
|
|
@@ -267,6 +282,60 @@ export type OverlayAnchorQuery =
|
|
|
267
282
|
| { kind: "page"; value: number }
|
|
268
283
|
| { kind: "selection" };
|
|
269
284
|
|
|
285
|
+
/* ================================================================== */
|
|
286
|
+
/* PE2 overlay lanes */
|
|
287
|
+
/* ================================================================== */
|
|
288
|
+
|
|
289
|
+
export type UiOverlayLaneKind =
|
|
290
|
+
| "selection"
|
|
291
|
+
| "caret"
|
|
292
|
+
| "redlines"
|
|
293
|
+
| "field-scopes"
|
|
294
|
+
| "broad-scopes"
|
|
295
|
+
| "comments"
|
|
296
|
+
| "issues"
|
|
297
|
+
| "tables"
|
|
298
|
+
| "objects"
|
|
299
|
+
| "search"
|
|
300
|
+
| "presence";
|
|
301
|
+
|
|
302
|
+
export type UiOverlayLaneStatus =
|
|
303
|
+
| "resolved"
|
|
304
|
+
| "requires-rehydration"
|
|
305
|
+
| "unavailable";
|
|
306
|
+
|
|
307
|
+
export type UiOverlayLaneSource =
|
|
308
|
+
| "geometry"
|
|
309
|
+
| "workflow"
|
|
310
|
+
| "search"
|
|
311
|
+
| "awareness"
|
|
312
|
+
| "controller"
|
|
313
|
+
| "unavailable";
|
|
314
|
+
|
|
315
|
+
export interface UiOverlayLaneEntry {
|
|
316
|
+
readonly id: string;
|
|
317
|
+
readonly status: UiOverlayLaneStatus;
|
|
318
|
+
readonly anchor?: OverlayAnchorQuery;
|
|
319
|
+
readonly rects?: readonly GeometryRect[];
|
|
320
|
+
readonly reason?: string;
|
|
321
|
+
/**
|
|
322
|
+
* Lane-specific plain payload. Examples: peer identity for presence,
|
|
323
|
+
* issue severity, search rank, table/object classification. Kept plain
|
|
324
|
+
* so non-React consumers and debug runners can serialize snapshots.
|
|
325
|
+
*/
|
|
326
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export interface UiOverlayLaneSnapshot {
|
|
330
|
+
readonly __mock?: true;
|
|
331
|
+
readonly kind: UiOverlayLaneKind;
|
|
332
|
+
readonly status: UiOverlayLaneStatus;
|
|
333
|
+
readonly entries: readonly UiOverlayLaneEntry[];
|
|
334
|
+
readonly revision: number;
|
|
335
|
+
readonly source: UiOverlayLaneSource;
|
|
336
|
+
readonly reason?: string;
|
|
337
|
+
}
|
|
338
|
+
|
|
270
339
|
/* ================================================================== */
|
|
271
340
|
/* Overlay visibility — U9 (state-classes X3) */
|
|
272
341
|
/* ================================================================== */
|
|
@@ -453,6 +522,23 @@ export interface ApiV3UiOverlays {
|
|
|
453
522
|
query: OverlayAnchorQuery,
|
|
454
523
|
listener: UiListener<GeometryRect | null>,
|
|
455
524
|
): UiUnsubscribe;
|
|
525
|
+
/**
|
|
526
|
+
* PE2 overlay-lane read. Returns a plain snapshot for a mounted lane
|
|
527
|
+
* such as selection, caret, redlines, comments, tables, objects,
|
|
528
|
+
* search, or presence. When the host has not wired a lane yet, returns
|
|
529
|
+
* an explicit `unavailable` snapshot instead of measuring DOM or
|
|
530
|
+
* forcing geometry rehydration.
|
|
531
|
+
*/
|
|
532
|
+
getLane(kind: UiOverlayLaneKind): UiOverlayLaneSnapshot;
|
|
533
|
+
/**
|
|
534
|
+
* PE2 overlay-lane subscription. The listener receives lane snapshots,
|
|
535
|
+
* never PM transactions. Presence/awareness updates must travel through
|
|
536
|
+
* this channel without invalidating L04 layout.
|
|
537
|
+
*/
|
|
538
|
+
subscribeLane(
|
|
539
|
+
kind: UiOverlayLaneKind,
|
|
540
|
+
listener: UiListener<UiOverlayLaneSnapshot>,
|
|
541
|
+
): UiUnsubscribe;
|
|
456
542
|
|
|
457
543
|
/**
|
|
458
544
|
* U9 · Composed overlay visibility. Merges the class-A policy from
|
package/src/api/v3/ui/index.ts
CHANGED
|
@@ -43,6 +43,11 @@ export type {
|
|
|
43
43
|
DebugSession,
|
|
44
44
|
DebugAttachment,
|
|
45
45
|
GeometryRect,
|
|
46
|
+
UiOverlayLaneEntry,
|
|
47
|
+
UiOverlayLaneKind,
|
|
48
|
+
UiOverlayLaneSnapshot,
|
|
49
|
+
UiOverlayLaneSource,
|
|
50
|
+
UiOverlayLaneStatus,
|
|
46
51
|
} from "./_types.ts";
|
|
47
52
|
|
|
48
53
|
// 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 {
|
|
@@ -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,
|