@beyondwork/docx-react-component 1.0.105 → 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.
- package/package.json +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +10 -2
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +310 -6
- package/src/api/v3/ai/attach.ts +29 -4
- package/src/api/v3/ai/bundle.ts +6 -2
- package/src/api/v3/ai/inspect.ts +6 -2
- package/src/api/v3/ai/replacement.ts +112 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +8 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +202 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +17 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +119 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +147 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +279 -115
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +661 -16
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +188 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +23 -4
- package/src/runtime/layout/paginated-layout-engine.ts +149 -15
- package/src/runtime/layout/project-block-fragments.ts +351 -14
- package/src/runtime/layout/public-facet.ts +162 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +4 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +386 -0
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +130 -0
- package/src/ui/ui-controller-factory.ts +17 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared KI-014 refusal facts for marker-backed multi-paragraph scopes.
|
|
3
|
+
*
|
|
4
|
+
* These blockers are evidence only: they describe why broad replacement is
|
|
5
|
+
* still disabled and which facts must exist before L08 can safely lower it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type MultiParagraphReplacementShape = "text" | "fragment" | "unknown";
|
|
9
|
+
|
|
10
|
+
export const MULTI_PARAGRAPH_REPLACEMENT_REFUSAL =
|
|
11
|
+
"compile-refused:scope:multi-paragraph-replace-not-implemented";
|
|
12
|
+
|
|
13
|
+
function shapeBlocker(shape: MultiParagraphReplacementShape): string {
|
|
14
|
+
switch (shape) {
|
|
15
|
+
case "text":
|
|
16
|
+
return "compile-refused:scope:multi-paragraph-text-replace-not-implemented";
|
|
17
|
+
case "fragment":
|
|
18
|
+
return "compile-refused:scope:multi-paragraph-fragment-replace-not-implemented";
|
|
19
|
+
default:
|
|
20
|
+
return "compile-refused:scope:multi-paragraph-replace-shape-not-implemented";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function multiParagraphReplacementBlockers(
|
|
25
|
+
shape: MultiParagraphReplacementShape = "unknown",
|
|
26
|
+
): readonly string[] {
|
|
27
|
+
return Object.freeze([
|
|
28
|
+
MULTI_PARAGRAPH_REPLACEMENT_REFUSAL,
|
|
29
|
+
shapeBlocker(shape),
|
|
30
|
+
"capability:scope:block-granular-replacement-lowering-required",
|
|
31
|
+
"capability:scope:provenance:marker-backed-required",
|
|
32
|
+
"capability:scope:layout-completeness-required",
|
|
33
|
+
"capability:scope:geometry-completeness-required",
|
|
34
|
+
"capability:scope:continuation-state-required",
|
|
35
|
+
"capability:scope:preservation-verdict-required",
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Layer 08 — preservation-boundary helper (Slice 4 interim).
|
|
3
3
|
*
|
|
4
4
|
* Computes whether a scope's canonical range crosses any preserve-only
|
|
5
|
-
* boundary that would be destroyed by a
|
|
5
|
+
* boundary that would be destroyed by a replacement operation.
|
|
6
6
|
* Sources consulted:
|
|
7
7
|
*
|
|
8
8
|
* - `document.preservation.opaqueFragments` — `OpaqueFragmentRecord[]`
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import type { CanonicalDocument } from "../../model/canonical-document.ts";
|
|
30
30
|
import { findOpaqueFragmentsIntersectingRange } from "../../preservation/store.ts";
|
|
31
31
|
|
|
32
|
+
import { findContentControlsIntersectingRange } from "./content-control-evidence.ts";
|
|
32
33
|
import type { ScopePositionMap, ScopePositionRange } from "./position-map.ts";
|
|
33
34
|
|
|
34
35
|
export interface PreservationVerdict {
|
|
@@ -69,6 +70,11 @@ export function computePreservationVerdict(
|
|
|
69
70
|
);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
const contentControls = findContentControlsIntersectingRange(document, range);
|
|
74
|
+
for (const control of contentControls) {
|
|
75
|
+
reasons.push(`content-control:${control.evidenceId}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
if (positionMap) {
|
|
73
79
|
for (const [scopeId, markerRange] of positionMap.markerScopes) {
|
|
74
80
|
// A marker range strictly inside the target range would be
|
|
@@ -33,6 +33,10 @@ import { enumerateScopes } from "../enumerate-scopes.ts";
|
|
|
33
33
|
import type { EnumeratedScope } from "../enumerate-scopes.ts";
|
|
34
34
|
import { emitScopeActionAudit } from "../audit-bundle.ts";
|
|
35
35
|
import { compileReplacement } from "./compile.ts";
|
|
36
|
+
import {
|
|
37
|
+
MULTI_PARAGRAPH_REPLACEMENT_REFUSAL,
|
|
38
|
+
multiParagraphReplacementBlockers,
|
|
39
|
+
} from "../multi-paragraph-refusal.ts";
|
|
36
40
|
import type {
|
|
37
41
|
ReplacementScope,
|
|
38
42
|
RuntimeOperationPlan,
|
|
@@ -47,6 +51,7 @@ export interface ApplyScopeReplacementSink {
|
|
|
47
51
|
readonly getInteractionGuardSnapshot: () => InteractionGuardSnapshot;
|
|
48
52
|
readonly getCompatibilityReport: () => CompatibilityReport;
|
|
49
53
|
readonly applyScopeReplacement: (plan: RuntimeOperationPlan) => void;
|
|
54
|
+
readonly verifyReadback?: boolean;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export interface ApplyScopeReplacementInputs {
|
|
@@ -80,19 +85,28 @@ export interface ApplyScopeReplacementResult {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
function documentHash(doc: CanonicalDocumentEnvelope): string {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
// Structural content hash. This is intentionally stronger than length-only
|
|
89
|
+
// so same-length stale/no-op replacements do not report as successful edits.
|
|
90
|
+
return JSON.stringify(doc.content);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function compileScopeById(
|
|
94
|
+
document: CanonicalDocumentEnvelope,
|
|
95
|
+
overlay: WorkflowOverlay | undefined,
|
|
96
|
+
scopeId: string,
|
|
97
|
+
): { readonly scope: SemanticScope; readonly entry: EnumeratedScope } | null {
|
|
98
|
+
const paragraphIndexByBlockIndex = buildParagraphIndexMap(document);
|
|
99
|
+
const enumerateInputs = overlay ? { overlay } : {};
|
|
100
|
+
for (const entry of enumerateScopes(document, enumerateInputs)) {
|
|
101
|
+
if (entry.handle.scopeId !== scopeId) continue;
|
|
102
|
+
const compiled = compileScope(entry, {
|
|
103
|
+
document,
|
|
104
|
+
...(overlay ? { overlay } : {}),
|
|
105
|
+
paragraphIndexByBlockIndex,
|
|
106
|
+
});
|
|
107
|
+
if (compiled) return { scope: compiled, entry };
|
|
94
108
|
}
|
|
95
|
-
return
|
|
109
|
+
return null;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
export function applyScopeReplacement(
|
|
@@ -102,27 +116,18 @@ export function applyScopeReplacement(
|
|
|
102
116
|
const docBefore = inputs.sink.getCanonicalDocument();
|
|
103
117
|
const overlay = inputs.sink.getWorkflowOverlay() ?? undefined;
|
|
104
118
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
document: docBefore,
|
|
113
|
-
...(overlay ? { overlay } : {}),
|
|
114
|
-
paragraphIndexByBlockIndex,
|
|
115
|
-
});
|
|
116
|
-
if (compiled) {
|
|
117
|
-
resolvedScope = compiled;
|
|
118
|
-
resolvedEnumerated = entry;
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
119
|
+
const resolved = compileScopeById(
|
|
120
|
+
docBefore,
|
|
121
|
+
overlay,
|
|
122
|
+
proposed.targetHandle.scopeId,
|
|
123
|
+
);
|
|
124
|
+
const resolvedScope = resolved?.scope ?? null;
|
|
125
|
+
const resolvedEnumerated = resolved?.entry ?? null;
|
|
122
126
|
|
|
123
127
|
if (!resolvedScope || !resolvedEnumerated) {
|
|
124
128
|
const validation: ValidationResult = {
|
|
125
129
|
safe: false,
|
|
130
|
+
posture: "hard-refusal",
|
|
126
131
|
blockedReasons: Object.freeze([
|
|
127
132
|
`scope-not-resolvable:${proposed.targetHandle.scopeId}`,
|
|
128
133
|
]),
|
|
@@ -207,15 +212,30 @@ export function applyScopeReplacement(
|
|
|
207
212
|
// `blockers[0]` / `reason` see the actionable sub-reason directly
|
|
208
213
|
// (rather than the bare `compile-refused:scope`). Grammar matches
|
|
209
214
|
// §10 `compile-refused:<kind>:<sub-reason>` (74a45eaf, 2026-04-23).
|
|
210
|
-
const
|
|
215
|
+
const blockers =
|
|
211
216
|
resolvedScope.kind === "scope"
|
|
212
|
-
?
|
|
217
|
+
? multiParagraphReplacementBlockers(
|
|
218
|
+
proposed.proposedContent.kind === "structured" ? "fragment" : "text",
|
|
219
|
+
)
|
|
220
|
+
: paragraphLike &&
|
|
221
|
+
proposed.operation === "replace" &&
|
|
222
|
+
proposed.preserve?.opaqueFragments === true
|
|
223
|
+
? [
|
|
224
|
+
`compile-refused:${resolvedScope.kind}:opaque-preserving-text-target-unavailable`,
|
|
225
|
+
]
|
|
213
226
|
: paragraphLike && proposed.operation !== "replace"
|
|
214
|
-
?
|
|
215
|
-
|
|
227
|
+
? [
|
|
228
|
+
`compile-refused:${resolvedScope.kind}:operation-not-implemented:${proposed.operation}`,
|
|
229
|
+
]
|
|
230
|
+
: [`compile-refused:${resolvedScope.kind}`];
|
|
231
|
+
const blocker =
|
|
232
|
+
resolvedScope.kind === "scope"
|
|
233
|
+
? MULTI_PARAGRAPH_REPLACEMENT_REFUSAL
|
|
234
|
+
: blockers[0] ?? `compile-refused:${resolvedScope.kind}`;
|
|
216
235
|
const refused: ValidationResult = {
|
|
217
236
|
safe: false,
|
|
218
|
-
|
|
237
|
+
posture: "hard-refusal",
|
|
238
|
+
blockedReasons: Object.freeze([...blockers]),
|
|
219
239
|
warnings: verdict.warnings,
|
|
220
240
|
};
|
|
221
241
|
// Coord-08 U5 — `reason` mirrors `blockers[0]` for symmetry. Agents
|
|
@@ -251,6 +271,49 @@ export function applyScopeReplacement(
|
|
|
251
271
|
|
|
252
272
|
const docAfter = inputs.sink.getCanonicalDocument();
|
|
253
273
|
const documentHashAfter = documentHash(docAfter);
|
|
274
|
+
const readback = compileScopeById(
|
|
275
|
+
docAfter,
|
|
276
|
+
inputs.sink.getWorkflowOverlay() ?? undefined,
|
|
277
|
+
proposed.targetHandle.scopeId,
|
|
278
|
+
);
|
|
279
|
+
const proposedText =
|
|
280
|
+
proposed.proposedContent.kind === "text"
|
|
281
|
+
? proposed.proposedContent.text ?? ""
|
|
282
|
+
: null;
|
|
283
|
+
const shouldVerifyReadback =
|
|
284
|
+
inputs.sink.verifyReadback === true &&
|
|
285
|
+
proposed.preserve?.opaqueFragments === true;
|
|
286
|
+
const readbackFailureReason =
|
|
287
|
+
shouldVerifyReadback &&
|
|
288
|
+
documentHashAfter === documentHashBefore &&
|
|
289
|
+
posture === "direct-edit" &&
|
|
290
|
+
!readback
|
|
291
|
+
? `apply-readback-unresolvable:${proposed.targetHandle.scopeId}`
|
|
292
|
+
: shouldVerifyReadback &&
|
|
293
|
+
documentHashAfter === documentHashBefore &&
|
|
294
|
+
posture === "direct-edit" &&
|
|
295
|
+
proposed.operation === "replace" &&
|
|
296
|
+
proposedText !== null &&
|
|
297
|
+
proposedText !== resolvedScope.content.text &&
|
|
298
|
+
readback?.scope.content.text === resolvedScope.content.text
|
|
299
|
+
? `apply-readback-unchanged:${proposed.targetHandle.scopeId}`
|
|
300
|
+
: undefined;
|
|
301
|
+
|
|
302
|
+
if (readbackFailureReason) {
|
|
303
|
+
return {
|
|
304
|
+
applied: false,
|
|
305
|
+
reason: readbackFailureReason,
|
|
306
|
+
validation: {
|
|
307
|
+
safe: false,
|
|
308
|
+
posture: "hard-refusal",
|
|
309
|
+
blockedReasons: Object.freeze([readbackFailureReason]),
|
|
310
|
+
warnings: verdict.warnings,
|
|
311
|
+
},
|
|
312
|
+
plan,
|
|
313
|
+
scope: readback?.scope ?? resolvedScope,
|
|
314
|
+
authoredRevisionIds: Object.freeze([]),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
254
317
|
|
|
255
318
|
const authoredRevisionIds: string[] = [];
|
|
256
319
|
for (const id of Object.keys(docAfter.review.revisions ?? {})) {
|
|
@@ -128,6 +128,55 @@ function longestTextOnlyRangeInParagraph(
|
|
|
128
128
|
return best;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function textInParagraphRange(
|
|
132
|
+
paragraph: ParagraphLikeEnumeratedScope["paragraph"],
|
|
133
|
+
paragraphFrom: number,
|
|
134
|
+
target: { readonly from: number; readonly to: number },
|
|
135
|
+
): string {
|
|
136
|
+
let cursor = paragraphFrom;
|
|
137
|
+
const parts: string[] = [];
|
|
138
|
+
|
|
139
|
+
const appendText = (text: string, from: number, to: number) => {
|
|
140
|
+
const clippedFrom = Math.max(from, target.from);
|
|
141
|
+
const clippedTo = Math.min(to, target.to);
|
|
142
|
+
if (clippedTo <= clippedFrom) return;
|
|
143
|
+
const localFrom = clippedFrom - from;
|
|
144
|
+
const localTo = clippedTo - from;
|
|
145
|
+
parts.push(Array.from(text).slice(localFrom, localTo).join(""));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const walkInline = (node: InlineNode, from: number): number => {
|
|
149
|
+
switch (node.type) {
|
|
150
|
+
case "text": {
|
|
151
|
+
const chars = Array.from(node.text);
|
|
152
|
+
appendText(node.text, from, from + chars.length);
|
|
153
|
+
return chars.length;
|
|
154
|
+
}
|
|
155
|
+
case "tab":
|
|
156
|
+
appendText("\t", from, from + 1);
|
|
157
|
+
return 1;
|
|
158
|
+
case "hard_break":
|
|
159
|
+
appendText("\n", from, from + 1);
|
|
160
|
+
return 1;
|
|
161
|
+
case "hyperlink":
|
|
162
|
+
case "field": {
|
|
163
|
+
let local = from;
|
|
164
|
+
for (const child of node.children as readonly InlineNode[]) {
|
|
165
|
+
local += walkInline(child, local);
|
|
166
|
+
}
|
|
167
|
+
return local - from;
|
|
168
|
+
}
|
|
169
|
+
default:
|
|
170
|
+
return inlineLengthLocal(node);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
for (const child of paragraph.children) {
|
|
175
|
+
cursor += walkInline(child, cursor);
|
|
176
|
+
}
|
|
177
|
+
return parts.join("");
|
|
178
|
+
}
|
|
179
|
+
|
|
131
180
|
export interface CompileParagraphOptions {
|
|
132
181
|
readonly document?: CanonicalDocument;
|
|
133
182
|
readonly paragraphIndex?: number;
|
|
@@ -198,13 +247,17 @@ export interface CompileParagraphReplacementOptions {
|
|
|
198
247
|
* Slice 5 — lower a paragraph-scope replacement proposal into a
|
|
199
248
|
* `RuntimeOperationPlan`. Handles two content kinds:
|
|
200
249
|
*
|
|
201
|
-
* - `kind: "text"` → single `text-replace` step spanning
|
|
202
|
-
* paragraph's
|
|
203
|
-
* the existing `text.insert` runtime command.
|
|
250
|
+
* - `replace` + `kind: "text"` → single `text-replace` step spanning
|
|
251
|
+
* the paragraph's effective scope range; passes a flat string
|
|
252
|
+
* through the existing `text.insert` runtime command.
|
|
253
|
+
* - `insert-before` / `insert-after` + `kind: "text"` → single
|
|
254
|
+
* collapsed `text-replace` (or tracked insert in suggest mode) at
|
|
255
|
+
* the effective scope edge.
|
|
204
256
|
* - `kind: "structured"` → single `fragment-replace` step carrying
|
|
205
257
|
* a `CanonicalDocumentFragment` payload; dispatches via the
|
|
206
|
-
* runtime's `insertFragment` pipeline with the
|
|
207
|
-
* selection
|
|
258
|
+
* runtime's `insertFragment` pipeline with the effective range as
|
|
259
|
+
* selection for replace, or a collapsed edge for insert-before /
|
|
260
|
+
* insert-after. Unblocked 2026-04-22 once L02 shipped
|
|
208
261
|
* `CanonicalDocumentFragment` (`src/model/canonical-document.ts`).
|
|
209
262
|
*
|
|
210
263
|
* Determinism (S3): the plan is a pure projection of (blockIndex,
|
|
@@ -215,7 +268,13 @@ export function compileParagraphReplacement(
|
|
|
215
268
|
proposed: ReplacementScope,
|
|
216
269
|
options: CompileParagraphReplacementOptions,
|
|
217
270
|
): RuntimeOperationPlan | null {
|
|
218
|
-
if (
|
|
271
|
+
if (
|
|
272
|
+
proposed.operation !== "replace" &&
|
|
273
|
+
proposed.operation !== "insert-before" &&
|
|
274
|
+
proposed.operation !== "insert-after"
|
|
275
|
+
) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
219
278
|
|
|
220
279
|
const blocks = computeBlockPositions(options.document);
|
|
221
280
|
const blockRange = blocks.find((b) => b.blockIndex === entry.blockIndex);
|
|
@@ -259,7 +318,10 @@ export function compileParagraphReplacement(
|
|
|
259
318
|
// replace range to the longest contiguous text-only sub-range so
|
|
260
319
|
// opaque inlines (images, charts, preserve-only fragments) survive
|
|
261
320
|
// the apply at their original positions.
|
|
262
|
-
if (
|
|
321
|
+
if (
|
|
322
|
+
proposed.operation === "replace" &&
|
|
323
|
+
proposed.preserve?.opaqueFragments === true
|
|
324
|
+
) {
|
|
263
325
|
const textOnly = longestTextOnlyRangeInParagraph(
|
|
264
326
|
entry.paragraph,
|
|
265
327
|
blockRange.from,
|
|
@@ -276,8 +338,22 @@ export function compileParagraphReplacement(
|
|
|
276
338
|
rangeKind = "opaque-preserving-text";
|
|
277
339
|
}
|
|
278
340
|
|
|
341
|
+
const operationRange =
|
|
342
|
+
proposed.operation === "insert-before"
|
|
343
|
+
? { from: effectiveRange.from, to: effectiveRange.from }
|
|
344
|
+
: proposed.operation === "insert-after"
|
|
345
|
+
? { from: effectiveRange.to, to: effectiveRange.to }
|
|
346
|
+
: effectiveRange;
|
|
347
|
+
|
|
279
348
|
if (proposed.proposedContent.kind === "text") {
|
|
280
349
|
const text = proposed.proposedContent.text ?? "";
|
|
350
|
+
if (
|
|
351
|
+
proposed.operation === "replace" &&
|
|
352
|
+
proposed.preserve?.opaqueFragments === true &&
|
|
353
|
+
textInParagraphRange(entry.paragraph, blockRange.from, operationRange) === text
|
|
354
|
+
) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
281
357
|
const stepKind =
|
|
282
358
|
options.posture === "suggest-mode" ? "text-insert-tracked" : "text-replace";
|
|
283
359
|
const summaryScope =
|
|
@@ -286,6 +362,16 @@ export function compileParagraphReplacement(
|
|
|
286
362
|
: rangeKind === "opaque-preserving-text"
|
|
287
363
|
? `paragraph #${entry.blockIndex} opaque-preserving text range [${effectiveRange.from}..${effectiveRange.to}]`
|
|
288
364
|
: `paragraph #${entry.blockIndex}`;
|
|
365
|
+
const actionVerb =
|
|
366
|
+
proposed.operation === "insert-before"
|
|
367
|
+
? "insert before"
|
|
368
|
+
: proposed.operation === "insert-after"
|
|
369
|
+
? "insert after"
|
|
370
|
+
: "replace";
|
|
371
|
+
const actionSummary =
|
|
372
|
+
proposed.operation === "replace"
|
|
373
|
+
? `${actionVerb} ${summaryScope} text (len ${text.length})`
|
|
374
|
+
: `${actionVerb} ${summaryScope} text at ${operationRange.from} (len ${text.length})`;
|
|
289
375
|
return {
|
|
290
376
|
scopeId: entry.handle.scopeId,
|
|
291
377
|
targetKind: "paragraph",
|
|
@@ -295,9 +381,9 @@ export function compileParagraphReplacement(
|
|
|
295
381
|
kind: stepKind,
|
|
296
382
|
summary:
|
|
297
383
|
stepKind === "text-replace"
|
|
298
|
-
?
|
|
299
|
-
: `suggest-mode ${
|
|
300
|
-
range: { from:
|
|
384
|
+
? actionSummary
|
|
385
|
+
: `suggest-mode ${actionSummary}`,
|
|
386
|
+
range: { from: operationRange.from, to: operationRange.to },
|
|
301
387
|
text,
|
|
302
388
|
...(proposed.formatting ? { formatting: proposed.formatting } : {}),
|
|
303
389
|
},
|
|
@@ -325,6 +411,16 @@ export function compileParagraphReplacement(
|
|
|
325
411
|
: rangeKind === "opaque-preserving-text"
|
|
326
412
|
? `paragraph #${entry.blockIndex} opaque-preserving text range [${effectiveRange.from}..${effectiveRange.to}]`
|
|
327
413
|
: `paragraph #${entry.blockIndex}`;
|
|
414
|
+
const actionVerb =
|
|
415
|
+
proposed.operation === "insert-before"
|
|
416
|
+
? "insert before"
|
|
417
|
+
: proposed.operation === "insert-after"
|
|
418
|
+
? "insert after"
|
|
419
|
+
: "replace";
|
|
420
|
+
const actionSummary =
|
|
421
|
+
proposed.operation === "replace"
|
|
422
|
+
? `${actionVerb} ${summaryScope} with structured fragment (${blockCount} block(s))`
|
|
423
|
+
: `${actionVerb} ${summaryScope} structured fragment at ${operationRange.from} (${blockCount} block(s))`;
|
|
328
424
|
return {
|
|
329
425
|
scopeId: entry.handle.scopeId,
|
|
330
426
|
targetKind: "paragraph",
|
|
@@ -332,8 +428,8 @@ export function compileParagraphReplacement(
|
|
|
332
428
|
steps: Object.freeze([
|
|
333
429
|
{
|
|
334
430
|
kind: "fragment-replace",
|
|
335
|
-
summary:
|
|
336
|
-
range: { from:
|
|
431
|
+
summary: actionSummary,
|
|
432
|
+
range: { from: operationRange.from, to: operationRange.to },
|
|
337
433
|
fragment,
|
|
338
434
|
},
|
|
339
435
|
]),
|