@beyondwork/docx-react-component 1.0.78 → 1.0.80
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 +60 -1
- package/src/api/v3/ai/resolve.ts +13 -7
- package/src/api/v3/runtime/workflow.ts +12 -2
- package/src/core/commands/add-scope.ts +222 -69
- package/src/runtime/document-runtime.ts +77 -2
- package/src/runtime/formatting/formatting-types.ts +16 -0
- package/src/runtime/formatting/revision-display.ts +16 -10
- package/src/runtime/layout/inert-layout-facet.ts +1 -0
- package/src/runtime/layout/layout-engine-version.ts +27 -1
- package/src/runtime/layout/public-facet.ts +35 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +9 -1
- package/src/runtime/scopes/compile-scope.ts +16 -0
- package/src/runtime/scopes/enumerate-scopes.ts +116 -3
- package/src/runtime/scopes/replaceability.ts +16 -0
- package/src/runtime/scopes/replacement/apply.ts +13 -3
- package/src/runtime/scopes/resolve-reference.ts +5 -0
- package/src/runtime/scopes/scope-kinds/scope.ts +87 -0
- package/src/runtime/scopes/scope-range.ts +11 -0
- package/src/runtime/workflow/coordinator.ts +60 -10
- package/src/runtime/workflow/scope-writer.ts +69 -2
- package/src/ui/headless/revision-decoration-model.ts +10 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +20 -1
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +17 -2
- package/src/ui-tailwind/theme/editor-theme.css +10 -1
|
@@ -276,10 +276,15 @@ export type CreateScopeFromAnchorResult =
|
|
|
276
276
|
readonly reason:
|
|
277
277
|
| "from-negative"
|
|
278
278
|
| "to-less-than-from"
|
|
279
|
-
| "range-exceeds-story-length"
|
|
279
|
+
| "range-exceeds-story-length"
|
|
280
|
+
| "non-paragraph-target"
|
|
281
|
+
| "empty-document";
|
|
280
282
|
readonly from: number;
|
|
281
283
|
readonly to: number;
|
|
282
284
|
readonly storyLength: number;
|
|
285
|
+
/** Non-paragraph target only — the offending block's index and kind. */
|
|
286
|
+
readonly blockIndex?: number;
|
|
287
|
+
readonly blockKind?: string;
|
|
283
288
|
/**
|
|
284
289
|
* Single-sentence, agent-actionable explanation. Tells the caller
|
|
285
290
|
* what the failure was and the concrete next step — no guesswork
|
|
@@ -291,7 +296,8 @@ export type CreateScopeFromAnchorResult =
|
|
|
291
296
|
* Short machine-routable next-step hint for thin consumers that
|
|
292
297
|
* don't want to pattern-match on `reason`. Examples:
|
|
293
298
|
* "clamp-from-to-zero", "swap-from-and-to",
|
|
294
|
-
* "clamp-to-to-storyLength-or-pick-a-different-range"
|
|
299
|
+
* "clamp-to-to-storyLength-or-pick-a-different-range",
|
|
300
|
+
* "pick-a-paragraph-target".
|
|
295
301
|
*/
|
|
296
302
|
readonly nextStep: string;
|
|
297
303
|
};
|
|
@@ -420,5 +426,66 @@ export function createScopeFromAnchor(
|
|
|
420
426
|
...(scopeMetadataFields.length > 0 ? { scopeMetadataFields } : {}),
|
|
421
427
|
});
|
|
422
428
|
|
|
429
|
+
// Pre-2026-04-24 the coordinator silently returned a minted scopeId
|
|
430
|
+
// even when insertScopeMarkers refused to plant (non-paragraph
|
|
431
|
+
// target, out-of-bounds after the story-length check passed).
|
|
432
|
+
// Cross-paragraph ranges now plant successfully (2026-04-24
|
|
433
|
+
// multi-paragraph-scopes slice), so that refusal variant is retired.
|
|
434
|
+
// Remaining reasons translate into the same `range-invalid` shape
|
|
435
|
+
// used by the bounds checks above so the caller gets one uniform
|
|
436
|
+
// discriminator to branch on.
|
|
437
|
+
if (result.plantStatus && result.plantStatus.planted === false) {
|
|
438
|
+
const ps = result.plantStatus;
|
|
439
|
+
if (ps.reason === "non-paragraph-target") {
|
|
440
|
+
return {
|
|
441
|
+
status: "range-invalid",
|
|
442
|
+
reason: "non-paragraph-target",
|
|
443
|
+
from,
|
|
444
|
+
to,
|
|
445
|
+
storyLength,
|
|
446
|
+
blockIndex: ps.blockIndex ?? -1,
|
|
447
|
+
blockKind: ps.blockKind ?? "unknown",
|
|
448
|
+
message:
|
|
449
|
+
`createScopeFromAnchor refused: range [${from}, ${to}] targets a ` +
|
|
450
|
+
`${ps.blockKind ?? "non-paragraph"} block (index ${ps.blockIndex}). ` +
|
|
451
|
+
`Marker scopes only plant inside paragraphs today. Pick a paragraph ` +
|
|
452
|
+
`target, or use runtime.workflow.createScope({blockId}) for ` +
|
|
453
|
+
`whole-block scopes on the containing structure.`,
|
|
454
|
+
nextStep: "pick-a-paragraph-target",
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (ps.reason === "range-out-of-bounds") {
|
|
458
|
+
// Shouldn't happen — storyLength was checked above — but surface
|
|
459
|
+
// it as a first-class failure in case the underlying length math
|
|
460
|
+
// drifts from our bounds check.
|
|
461
|
+
return {
|
|
462
|
+
status: "range-invalid",
|
|
463
|
+
reason: "range-exceeds-story-length",
|
|
464
|
+
from,
|
|
465
|
+
to,
|
|
466
|
+
storyLength: ps.storyLength ?? storyLength,
|
|
467
|
+
message:
|
|
468
|
+
`createScopeFromAnchor refused: coordinator reports range [${from}, ${to}] ` +
|
|
469
|
+
`is out of bounds (storyLength=${ps.storyLength}). This is usually a ` +
|
|
470
|
+
`stale-offset bug (KI-P9) — re-derive positions from the current ` +
|
|
471
|
+
`document and retry.`,
|
|
472
|
+
nextStep: "clamp-to-to-storyLength-or-pick-a-different-range",
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
// empty-document — target has no canonical blocks.
|
|
476
|
+
return {
|
|
477
|
+
status: "range-invalid",
|
|
478
|
+
reason: "empty-document",
|
|
479
|
+
from,
|
|
480
|
+
to,
|
|
481
|
+
storyLength,
|
|
482
|
+
message:
|
|
483
|
+
`createScopeFromAnchor refused: the target document has no blocks; ` +
|
|
484
|
+
`cannot plant scope markers. Open or initialize a document before ` +
|
|
485
|
+
`creating sub-block scopes.`,
|
|
486
|
+
nextStep: "initialize-document-before-creating-scopes",
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
423
490
|
return { status: "created", scopeId: result.scopeId, anchor: result.anchor };
|
|
424
491
|
}
|
|
@@ -155,6 +155,16 @@ export function getRevisionRangeState(
|
|
|
155
155
|
* by `test/runtime/formatting/production-boundary.test.ts`.
|
|
156
156
|
*/
|
|
157
157
|
export interface RevisionDisplayFlags {
|
|
158
|
+
/**
|
|
159
|
+
* Identity of the attached revision. Mirrors
|
|
160
|
+
* `SurfaceInlineSegment.revisionDisplay.revisionId`.
|
|
161
|
+
*/
|
|
162
|
+
revisionId: string;
|
|
163
|
+
/**
|
|
164
|
+
* Mirrors `RevisionRecord.kind`. Consumers branch on insertion /
|
|
165
|
+
* deletion variants without reading the review store.
|
|
166
|
+
*/
|
|
167
|
+
kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
|
|
158
168
|
markupMode: "clean" | "simple" | "all";
|
|
159
169
|
hidden?: boolean;
|
|
160
170
|
strikethrough?: boolean;
|
|
@@ -199,6 +199,17 @@ export const editorSchema = new Schema({
|
|
|
199
199
|
pageBreakBefore: { default: null },
|
|
200
200
|
hiddenTextOnly: { default: null },
|
|
201
201
|
placeholderCulled: { default: null },
|
|
202
|
+
/**
|
|
203
|
+
* Rendered height (in twips) of the block that this placeholder
|
|
204
|
+
* stands in for, supplied by `DocumentRuntime` from L04's page
|
|
205
|
+
* graph. When present on a `placeholderCulled` paragraph, `toDOM`
|
|
206
|
+
* emits a fixed-height `<div>` (`${twips/20}pt`) instead of the
|
|
207
|
+
* `min-height: 20px` fallback, eliminating the scroll-path
|
|
208
|
+
* "paragraphs jump around pagination gaps" flicker that occurred
|
|
209
|
+
* when blocks realized at real heights larger than one line.
|
|
210
|
+
* Null / undefined preserves the pre-existing 20 px minimum.
|
|
211
|
+
*/
|
|
212
|
+
placeholderHeightTwips: { default: null },
|
|
202
213
|
blockId: { default: null },
|
|
203
214
|
/**
|
|
204
215
|
* `<w:framePr>` projection from `SurfaceBlockFragment.frameProperties`
|
|
@@ -214,6 +225,11 @@ export const editorSchema = new Schema({
|
|
|
214
225
|
toDOM(node) {
|
|
215
226
|
// Viewport-culled placeholder paragraph — cheap size-preserving leaf.
|
|
216
227
|
if (node.attrs.placeholderCulled) {
|
|
228
|
+
const heightTwips = node.attrs.placeholderHeightTwips as number | null;
|
|
229
|
+
const heightStyle =
|
|
230
|
+
typeof heightTwips === "number" && heightTwips > 0
|
|
231
|
+
? `height: ${heightTwips / 20}pt`
|
|
232
|
+
: "min-height: 20px";
|
|
217
233
|
return [
|
|
218
234
|
"div",
|
|
219
235
|
{
|
|
@@ -221,7 +237,10 @@ export const editorSchema = new Schema({
|
|
|
221
237
|
"data-placeholder-culled": "true",
|
|
222
238
|
"data-placeholder-size": String(node.nodeSize),
|
|
223
239
|
"data-placeholder-block-id": node.attrs.blockId ?? "",
|
|
224
|
-
|
|
240
|
+
...(typeof heightTwips === "number" && heightTwips > 0
|
|
241
|
+
? { "data-placeholder-height-twips": String(heightTwips) }
|
|
242
|
+
: {}),
|
|
243
|
+
style: `${heightStyle}; contain: strict;`,
|
|
225
244
|
"aria-hidden": "true",
|
|
226
245
|
},
|
|
227
246
|
0,
|
|
@@ -867,10 +867,25 @@ function buildOpaqueBlock(
|
|
|
867
867
|
const placeholderSize = block.placeholderSize ?? null;
|
|
868
868
|
if (placeholderSize !== null) {
|
|
869
869
|
const targetSize = placeholderSize as number;
|
|
870
|
+
// Flicker fix — when DocumentRuntime has enriched the placeholder with
|
|
871
|
+
// the block's known rendered height (from L04's page graph), thread it
|
|
872
|
+
// onto the paragraph node so `pm-schema.ts::toDOM` emits a fixed
|
|
873
|
+
// `height` style matching the real block. Without this, the placeholder
|
|
874
|
+
// renders at `min-height: 20px` and inflates to its real height when
|
|
875
|
+
// the block realizes on scroll, dragging content below the scroll
|
|
876
|
+
// pointer ("paragraphs jump around pagination gaps").
|
|
877
|
+
const placeholderHeightTwips = block.placeholderHeightTwips ?? null;
|
|
878
|
+
const placeholderAttrs: Record<string, unknown> = {
|
|
879
|
+
blockId: block.blockId,
|
|
880
|
+
placeholderCulled: true,
|
|
881
|
+
};
|
|
882
|
+
if (placeholderHeightTwips !== null) {
|
|
883
|
+
placeholderAttrs.placeholderHeightTwips = placeholderHeightTwips;
|
|
884
|
+
}
|
|
870
885
|
if (targetSize <= 2) {
|
|
871
886
|
// Edge case: bare empty paragraph claims exactly 2 positions.
|
|
872
887
|
return editorSchema.nodes.paragraph.create(
|
|
873
|
-
|
|
888
|
+
placeholderAttrs,
|
|
874
889
|
Fragment.empty,
|
|
875
890
|
);
|
|
876
891
|
}
|
|
@@ -878,7 +893,7 @@ function buildOpaqueBlock(
|
|
|
878
893
|
// total PM positions = 1 (open) + (targetSize - 2) (text) + 1 (close) = targetSize.
|
|
879
894
|
const filler = "\u200b".repeat(targetSize - 2);
|
|
880
895
|
return editorSchema.nodes.paragraph.create(
|
|
881
|
-
|
|
896
|
+
placeholderAttrs,
|
|
882
897
|
editorSchema.text(filler),
|
|
883
898
|
);
|
|
884
899
|
}
|
|
@@ -1075,7 +1075,16 @@
|
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
1077
|
.wre-page-band:hover {
|
|
1078
|
-
|
|
1078
|
+
/*
|
|
1079
|
+
* N3 audit (2026-04-24): the prior `color-mix(bg-muted 80%, surface 20%)`
|
|
1080
|
+
* blended two near-adjacent tokens — in dark mode (#17211C vs #182420)
|
|
1081
|
+
* the result moved ≤2 RGB units from the base, leaving no visible
|
|
1082
|
+
* hover affordance. `--color-bg-hover` is the token the design system
|
|
1083
|
+
* already dedicates to this signal (light #EAF6EF vs muted #F7FAF8;
|
|
1084
|
+
* dark #21342A vs muted #17211C) and resolves the dark-mode legibility
|
|
1085
|
+
* gap without regressing light-mode subtlety.
|
|
1086
|
+
*/
|
|
1087
|
+
background-color: var(--color-bg-hover);
|
|
1079
1088
|
}
|
|
1080
1089
|
|
|
1081
1090
|
.wre-page-band[data-active="true"] {
|