@beyondwork/docx-react-component 1.0.71 → 1.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +964 -75
  2. package/package.json +1 -1
  3. package/src/api/public-types.ts +243 -1
  4. package/src/api/v3/_create.ts +16 -1
  5. package/src/api/v3/_runtime-handle.ts +2 -0
  6. package/src/api/v3/ai/evaluate.ts +113 -0
  7. package/src/api/v3/ai/outline.ts +140 -0
  8. package/src/api/v3/ai/replacement.ts +8 -0
  9. package/src/api/v3/ai/review.ts +342 -0
  10. package/src/api/v3/ai/stats.ts +62 -0
  11. package/src/api/v3/runtime/viewport.ts +181 -0
  12. package/src/api/v3/runtime/workflow.ts +114 -1
  13. package/src/api/v3/ui/_types.ts +35 -0
  14. package/src/api/v3/ui/index.ts +1 -0
  15. package/src/api/v3/ui/viewport.ts +112 -0
  16. package/src/compare/diff-engine.ts +2 -0
  17. package/src/core/commands/formatting-commands.ts +1 -0
  18. package/src/core/commands/table-structure-commands.ts +1 -0
  19. package/src/io/export/serialize-headers-footers.ts +1 -0
  20. package/src/io/export/serialize-main-document.ts +13 -0
  21. package/src/io/export/serialize-paragraph-formatting.ts +34 -0
  22. package/src/io/export/split-review-boundaries.ts +1 -0
  23. package/src/io/normalize/normalize-text.ts +11 -0
  24. package/src/io/ooxml/parse-main-document.ts +21 -5
  25. package/src/io/ooxml/parse-paragraph-formatting.ts +105 -0
  26. package/src/model/canonical-document.ts +401 -1
  27. package/src/runtime/formatting/formatting-context.ts +2 -1
  28. package/src/runtime/geometry/overlay-rects.ts +7 -10
  29. package/src/runtime/layout/layout-engine-version.ts +257 -1
  30. package/src/runtime/layout/paginated-layout-engine.ts +134 -8
  31. package/src/runtime/layout/resolved-formatting-state.ts +108 -13
  32. package/src/runtime/markdown-sanitizer.ts +21 -4
  33. package/src/runtime/render/render-kernel.ts +21 -1
  34. package/src/runtime/scopes/audit-bundle.ts +8 -0
  35. package/src/runtime/scopes/compiler-service.ts +1 -0
  36. package/src/runtime/scopes/enumerate-scopes.ts +61 -3
  37. package/src/runtime/scopes/replacement/apply.ts +49 -3
  38. package/src/runtime/scopes/semantic-scope-types.ts +8 -0
  39. package/src/runtime/surface-projection.ts +22 -0
  40. package/src/runtime/workflow/coordinator.ts +3 -0
  41. package/src/runtime/workflow/scope-writer.ts +34 -0
  42. package/src/session/export/embedded-reconstitute.ts +37 -3
  43. package/src/session/import/embedded-offload.ts +26 -1
  44. package/src/shell/media-previews.ts +8 -6
  45. package/src/ui/WordReviewEditor.tsx +1 -0
  46. package/src/ui/editor-surface-controller.tsx +11 -0
  47. package/src/ui/headless/selection-helpers.ts +2 -2
  48. package/src/ui/runtime-shortcut-dispatch.ts +4 -4
  49. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +22 -4
  50. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +11 -11
  51. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -1
  52. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +5 -0
  53. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +18 -1
  54. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +22 -6
  55. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +18 -1
  56. package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +98 -3
  57. package/src/ui-tailwind/editor-surface/pm-schema.ts +18 -4
  58. package/src/ui-tailwind/editor-surface/scroll-anchor.ts +8 -1
  59. package/src/ui-tailwind/editor-surface/search-plugin.ts +2 -4
  60. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +37 -0
  61. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +29 -4
  62. package/src/ui-tailwind/index.ts +4 -2
  63. package/src/ui-tailwind/page-chrome-model.ts +5 -7
  64. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +5 -2
  65. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +4 -1
  66. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +4 -1
  67. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +10 -1
  68. package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +4 -1
  69. package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +7 -1
  70. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +7 -1
  71. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +73 -8
  72. package/src/ui-tailwind/review/comment-markdown-renderer.tsx +1 -1
  73. package/src/ui-tailwind/review-workspace/page-chrome.ts +4 -4
  74. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +1 -1
  75. package/src/ui-tailwind/tw-review-workspace.tsx +1 -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.71",
4
+ "version": "1.0.72",
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": [
@@ -103,6 +103,27 @@ export type {
103
103
  export type { CollabSession } from "../runtime/collab-session.ts";
104
104
  export type { MetadataIntegrity } from "../runtime/workflow/tamper-gate.ts";
105
105
  export type { ScopeTagRegistry } from "../runtime/workflow/scope-tag-registry.ts";
106
+ /**
107
+ * Zero-arg factory for the default-seeded `ScopeTagRegistry`. Two
108
+ * equivalent call sites ship today; pick whichever matches your scope:
109
+ *
110
+ * - `createScopeTagRegistry()` (this re-export) — handle-free path for
111
+ * L11 consumers that already import from `public-types`. §4.17
112
+ * "simple pass-through of a runtime helper" precedent. Safe when
113
+ * there's no `api` in scope (tests, pre-mount, PM-surface
114
+ * component-internal memo).
115
+ *
116
+ * - `api.runtime.workflow.createScopeTagRegistry()` — L06 proper API
117
+ * home shipped 2026-04-23 per coord-10 §L11-4 (L11 delegation).
118
+ * Preferred when a handle is already threaded: the registry is then
119
+ * reachable through the same family as `queryScopes`, `getMarkup`,
120
+ * etc., and the call appears in agent-audit telemetry.
121
+ *
122
+ * Both paths delegate to the same implementation in
123
+ * `src/runtime/workflow/scope-tag-registry.ts` and return an identical
124
+ * `ScopeTagRegistry` shape. No behavior difference.
125
+ */
126
+ export { createScopeTagRegistry } from "../runtime/workflow/scope-tag-registry.ts";
106
127
  // Semantic-scope shapes — re-exported so L10 (ui.scope.*) + L09
107
128
  // (ai.getScopeBundle) + L11 (scope-card consumers) can import the
108
129
  // canonical types from the public-types choke point instead of
@@ -119,6 +140,23 @@ export type {
119
140
  } from "../runtime/scopes/semantic-scope-types.ts";
120
141
  export type { GeometryFacet } from "../runtime/geometry/index.ts";
121
142
  export type { RuntimePageGraph } from "../runtime/layout/page-graph.ts";
143
+ export type { RuntimePageNode } from "../runtime/layout/page-graph.ts";
144
+
145
+ /**
146
+ * `resolvePageFieldDisplayText` pure helper — resolves `PAGE` /
147
+ * `NUMPAGES` field text for a specific page + graph. Used by the PM
148
+ * page-break decoration renderer to substitute the cached preserve-
149
+ * only text with a live per-page number. Pure function over the
150
+ * already-public `RuntimePageGraph` / `RuntimePageNode` +
151
+ * `SupportedFieldFamily` types; no runtime handle in scope at the
152
+ * presentation-layer call site. Matches the §4.8 / §4.17 re-export
153
+ * precedent. Canonical impl lives on
154
+ * `src/runtime/layout/resolve-page-fields.ts`.
155
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17 L04 layout
156
+ * long-tail row after `RuntimePageGraph` graduated to public-types.
157
+ */
158
+ export type { PageFieldContext } from "../runtime/layout/resolve-page-fields.ts";
159
+ export { resolvePageFieldDisplayText } from "../runtime/layout/resolve-page-fields.ts";
122
160
 
123
161
  export type {
124
162
  WordReviewEditorLayoutFacet,
@@ -182,6 +220,63 @@ export type { ScopeRailPosture } from "../runtime/workflow/rail/compose.ts";
182
220
  // reaching into `src/runtime/**` directly (P3).
183
221
  export type { SessionCapabilities } from "../runtime/session-capabilities.ts";
184
222
 
223
+ /**
224
+ * `deriveCapabilities` value re-export — pure function over the
225
+ * already-public `RuntimeRenderSnapshot` + `WorkflowScopeSnapshot`
226
+ * inputs. Matches the `storyTargetsEqual` / unit-constant precedent
227
+ * for pure-helper re-exports where no runtime handle is in scope at
228
+ * the consumer. Called by `src/ui-tailwind/index.ts`'s public
229
+ * facade so hosts can derive capabilities without reaching into
230
+ * `src/runtime/**` directly. Re-exported 2026-04-24 per refactor/11
231
+ * handoff §4.17 self-closable row (L01 session-capabilities value).
232
+ */
233
+ export { deriveCapabilities } from "../runtime/session-capabilities.ts";
234
+
235
+ /**
236
+ * Editor capability table — frozen map + accompanying pure types.
237
+ * Re-exported 2026-04-24 per coord-10 §L11-1 (delegated from
238
+ * refactor/11 handoff register). Retires the direct reach-through
239
+ * from `src/ui/runtime-shortcut-dispatch.ts` into
240
+ * `src/runtime/editor-surface/capabilities.ts`. Matches the §4.8
241
+ * precedent class: frozen value + pure types, no runtime handle at
242
+ * the consumer. L07 may later formalize this as
243
+ * `api.runtime.capabilities.getById(id)` / `.list()`; the re-export
244
+ * here is the minimum-viable unblock.
245
+ */
246
+ export type {
247
+ CapabilityKind,
248
+ CapabilityCategory,
249
+ CapabilityShortcut,
250
+ CapabilityBlockReason,
251
+ EditorCapability,
252
+ } from "../runtime/editor-surface/capabilities.ts";
253
+ export { CAPABILITY_BY_ID } from "../runtime/editor-surface/capabilities.ts";
254
+
255
+ /**
256
+ * Per-page header/footer short-text preview maps — used by the in-flow
257
+ * page-chrome bands to show live content ("Page 3 of 5") instead of
258
+ * generic "Header" / "Footer" labels. Re-exported 2026-04-24 per coord-10
259
+ * §L11-2 (delegated from refactor/11 handoff register). `buildPagePreviewMaps`
260
+ * reads only `pages[].pageId / stories / isBlankFiller` on its graph input,
261
+ * and consumers already pass a structural adapter (see
262
+ * `tw-prosemirror-surface.tsx`); the minimum-viable unblock re-exports the
263
+ * function + result type without hoisting `RuntimePageGraph` itself. L04
264
+ * may later formalize as `api.runtime.layout.getPagePreviews(nav, subParts)`.
265
+ */
266
+ export type { PagePreviewMaps } from "../runtime/layout/resolve-page-previews.ts";
267
+ export { buildPagePreviewMaps } from "../runtime/layout/resolve-page-previews.ts";
268
+
269
+ /**
270
+ * Canvas measurement backend factory — browser-only `LayoutMeasurementProvider`
271
+ * that uses Canvas2D `measureText()` for font-metric-aware width computation.
272
+ * Re-exported 2026-04-24 per coord-10 §L11-5 (delegated from refactor/11
273
+ * handoff register). Consumers pass the result to
274
+ * `WordReviewEditorLayoutFacet.swapMeasurementProvider(...)`; the provider
275
+ * type is already threaded through the facet's public surface. L04 may
276
+ * later formalize as `api.runtime.layout.createCanvasBackend()`.
277
+ */
278
+ export { createCanvasBackend } from "../runtime/layout/measurement-backend-canvas.ts";
279
+
185
280
  export type FieldFamily = import("../model/canonical-document.ts").FieldFamily;
186
281
  export type FieldRefreshStatus = import("../model/canonical-document.ts").FieldRefreshStatus;
187
282
  export type SupportedFieldFamily = import("../model/canonical-document.ts").SupportedFieldFamily;
@@ -260,6 +355,89 @@ export type { EditorStoryTarget } from "../model/layout/page-layout-snapshot.ts"
260
355
  */
261
356
  export { storyTargetsEqual, MAIN_STORY_TARGET } from "../core/selection/mapping.ts";
262
357
 
358
+ /**
359
+ * `storyTargetKey` pure function — stable string key derivation for
360
+ * `EditorStoryTarget` (`main` / `header` / `footer` / `footnote` /
361
+ * `endnote`). Used by presentation-layer caches keyed by story
362
+ * target (e.g. floating-image overlay model). Matches the
363
+ * `storyTargetsEqual` precedent — pure function over a public type,
364
+ * no runtime handle in scope at the consumer. Canonical impl lives
365
+ * on `src/runtime/story-targeting.ts`.
366
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17 self-closable row.
367
+ */
368
+ export { storyTargetKey } from "../runtime/story-targeting.ts";
369
+
370
+ /**
371
+ * Public-shape anchor constructors — pure helpers producing the
372
+ * `EditorAnchorProjection` discriminated union used in
373
+ * `SelectionSnapshot.activeRange`. `createPublicRangeAnchor`
374
+ * normalizes `from <= to` internally; `createPublicNodeAnchor`
375
+ * tags a node anchor at `at` with boundary associativity. Canonical
376
+ * impl lives on `src/core/selection/anchor-conversion.ts` — the
377
+ * single authority enforced by
378
+ * `test/api/anchor-boundary-invariants.test.ts`.
379
+ *
380
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17 — the L03/L06
381
+ * selection-primitives row names `anchor-conversion` as a candidate
382
+ * for the public-types re-export pattern (chart family precedent)
383
+ * when the consumer is a leaf helper with no runtime handle in scope,
384
+ * which `src/ui/headless/selection-helpers.ts` is.
385
+ */
386
+ export {
387
+ createPublicRangeAnchor,
388
+ createPublicNodeAnchor,
389
+ } from "../core/selection/anchor-conversion.ts";
390
+
391
+ /**
392
+ * L04 pagination pure helpers — leaf functions over already-public
393
+ * `DocumentPageSnapshot` / `PageLayoutSnapshot` / `SurfaceBlockSnapshot`
394
+ * shapes, used by presentation-layer page-chrome surfaces
395
+ * (`src/ui-tailwind/page-chrome-model.ts`). No runtime handle in scope
396
+ * at call sites — exactly the §4.8 precedent class.
397
+ *
398
+ * - `findPageForOffset(pages, offset)` — index resolver.
399
+ * - `estimateBlockHeight`, `estimateParagraphLineHeight`,
400
+ * `estimateParagraphLineCount`, `getUsableColumnWidth` — pure
401
+ * layout estimators used before real layout is available (cold
402
+ * open + placeholder chrome).
403
+ *
404
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17 L04 layout
405
+ * family long-tail. Canonical impls live on
406
+ * `src/runtime/document-navigation.ts` +
407
+ * `src/runtime/page-layout-estimation.ts`.
408
+ */
409
+ export { findPageForOffset } from "../runtime/document-navigation.ts";
410
+ export {
411
+ estimateBlockHeight,
412
+ estimateParagraphLineHeight,
413
+ estimateParagraphLineCount,
414
+ getUsableColumnWidth,
415
+ } from "../runtime/page-layout-estimation.ts";
416
+
417
+ /**
418
+ * L03/L06 search-text pure helpers + type aliases — pure functions
419
+ * over `SurfaceBlockSnapshot` + `SearchOptions` (public types) used
420
+ * by the PM search plugin at `src/ui-tailwind/editor-surface/search-plugin.ts`.
421
+ * No runtime handle in scope. Matches the handoff §4.17 "L03/L06
422
+ * selection/search primitives — promote to public-types.ts as pure
423
+ * re-exports (chart family precedent)" alternative.
424
+ *
425
+ * Canonical impl lives on `src/core/search/search-text.ts`.
426
+ *
427
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17. The larger
428
+ * `runtime.content.search` family seam on L07 is still the preferred
429
+ * long-term home; until it ships, this pragmatic re-export unblocks
430
+ * the L11 boundary register.
431
+ */
432
+ export {
433
+ buildSearchPattern,
434
+ createSearchExcerpt,
435
+ findSearchMatches,
436
+ searchSecondaryStories,
437
+ type SecondaryStorySearchResult,
438
+ type SearchTextOptions,
439
+ } from "../core/search/search-text.ts";
440
+
263
441
  /**
264
442
  * OOXML unit-conversion constants shared with presentation-layer
265
443
  * renderers. EMU (English Metric Units) is the OOXML measurement
@@ -280,6 +458,39 @@ export {
280
458
  SRCRECT_UNITS_PER_PERCENT,
281
459
  } from "../runtime/units.ts";
282
460
 
461
+ /**
462
+ * Geometry-kernel + page-estimation unit constants used by presentation-
463
+ * layer chrome (table grip, page chrome). Both fold into public-types
464
+ * alongside the OOXML unit constants above so chrome overlays can
465
+ * convert between twips and CSS px without reaching into
466
+ * `src/runtime/**`.
467
+ *
468
+ * - `DEFAULT_PX_PER_TWIP = 96 / 1440` — kernel render-frame conversion
469
+ * used by `tw-table-grip-layer` for grip positioning.
470
+ * - `DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP = 1 / 15` — page-chrome
471
+ * estimation scale used for placeholder chrome before real layout.
472
+ *
473
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17 self-closable
474
+ * rows. Canonical values live on `src/runtime/render/render-frame-types.ts`
475
+ * and `src/runtime/page-layout-estimation.ts`.
476
+ */
477
+ export { DEFAULT_PX_PER_TWIP } from "../runtime/render/render-frame-types.ts";
478
+ export { DEFAULT_PAGE_ESTIMATE_PX_PER_TWIP } from "../runtime/page-layout-estimation.ts";
479
+
480
+ /**
481
+ * Markdown sanitizer re-exported at the public-types boundary so the
482
+ * Layer-11 comment-markdown renderer can consume it without reaching
483
+ * into `src/runtime/**`. Pure function over a string; no runtime handle
484
+ * in scope at the leaf renderer. Canonical implementation lives on
485
+ * `src/runtime/markdown-sanitizer.ts` and is shared with
486
+ * `runtime/comment-presentation.ts`.
487
+ * Re-exported 2026-04-24 per refactor/11 handoff §4.17 self-closable row.
488
+ */
489
+ export {
490
+ sanitizeMarkdown,
491
+ type SanitizeResult,
492
+ } from "../runtime/markdown-sanitizer.ts";
493
+
283
494
  export interface SelectionSnapshot {
284
495
  anchor: number;
285
496
  head: number;
@@ -904,7 +1115,16 @@ export type SurfaceTextMark =
904
1115
  | "imprint"
905
1116
  | "shadow"
906
1117
  | "smallCaps"
907
- | "allCaps";
1118
+ | "allCaps"
1119
+ /**
1120
+ * Present iff the run carried `w:highlight`. The concrete highlight color
1121
+ * is on `markAttrs.backgroundColor`. Consumers that only care about the
1122
+ * presence signal (e.g. "is this run highlighted?") read `marks`; those
1123
+ * that need the color read `markAttrs.backgroundColor`. Note that
1124
+ * `backgroundColor` can also originate from `w:shd` (run shading) — only
1125
+ * the `"highlight"` mark disambiguates the source.
1126
+ */
1127
+ | "highlight";
908
1128
 
909
1129
  /**
910
1130
  * V2c.4 / V2c.5 — DrawingFrame anchor geometry projected onto image + shape
@@ -1657,6 +1877,15 @@ export interface TableStructureContextSnapshot {
1657
1877
  columnCount: number;
1658
1878
  selectedCellCount: number;
1659
1879
  isSimpleTable: boolean;
1880
+ /**
1881
+ * Table-level alignment declared on the canonical `TableNode.alignment`.
1882
+ * `null` when the table has no explicit alignment (renders against the
1883
+ * parent's default — typically left). Mirrors `PublicTableSummary.alignment`
1884
+ * (refactor/11 §4.13) so the active-state of the whole-table alignment
1885
+ * toolbar button can read from the context snapshot directly without
1886
+ * a separate `getTable(tableBlockIndex).alignment` hop.
1887
+ */
1888
+ alignment: "left" | "center" | "right" | null;
1660
1889
  currentCell: {
1661
1890
  rowIndex: number;
1662
1891
  columnIndex: number;
@@ -2205,6 +2434,19 @@ export interface AddScopeParams {
2205
2434
  * held runtime-only, split across doc + workblock, or fully embedded.
2206
2435
  */
2207
2436
  metadata?: Partial<WorkflowMetadataEntry>;
2437
+ /**
2438
+ * Optional fields stamped onto the overlay scope's `metadata` array
2439
+ * at creation time. Distinct from `metadata?: Partial<WorkflowMetadataEntry>`
2440
+ * above (which writes a typed metadata *entry* keyed by `metadataId`);
2441
+ * `scopeMetadataFields` lands as plain `WorkflowScopeMetadataField[]`
2442
+ * on the `WorkflowScope.metadata` slot and survives serialization /
2443
+ * collab replication with the overlay.
2444
+ *
2445
+ * Used by coord-08 §9 — L06's `createScopeFromBlockId({stableRefHint})`
2446
+ * encodes the hint as `{key: "stableRefHint", value: <kind>}` here so
2447
+ * the L08 compiler can read it back during enumeration.
2448
+ */
2449
+ scopeMetadataFields?: readonly WorkflowScopeMetadataField[];
2208
2450
  /** Non-main-body stories (footnote / header / endnote). */
2209
2451
  storyTarget?: EditorStoryTarget;
2210
2452
  /** Optional display label for the scope card / rail. */
@@ -27,6 +27,7 @@ import { createClipboardFamily } from "./runtime/clipboard.ts";
27
27
  import { createChartFamily } from "./runtime/chart.ts";
28
28
  import { createSearchFamily } from "./runtime/search.ts";
29
29
  import { createTableFamily } from "./runtime/table.ts";
30
+ import { createViewportFamily } from "./runtime/viewport.ts";
30
31
 
31
32
  import { createInspectFamily } from "./ai/inspect.ts";
32
33
  import { createResolveFamily } from "./ai/resolve.ts";
@@ -36,6 +37,10 @@ import { createAttachFamily } from "./ai/attach.ts";
36
37
  import { createExportFamily } from "./ai/export.ts";
37
38
  import { createExplainFamily } from "./ai/explain.ts";
38
39
  import { createPolicyFamily } from "./ai/policy.ts";
40
+ import { createReviewFamily as createAiReviewFamily } from "./ai/review.ts";
41
+ import { createEvaluateFamily } from "./ai/evaluate.ts";
42
+ import { createStatsFamily } from "./ai/stats.ts";
43
+ import { createOutlineFamily } from "./ai/outline.ts";
39
44
 
40
45
  import { createUiApi } from "./ui/_create.ts";
41
46
  import type { ApiV3Ui, UiControllerFactory } from "./ui/_types.ts";
@@ -62,6 +67,7 @@ export type ApiV3Runtime = {
62
67
  readonly chart: ReturnType<typeof createChartFamily>;
63
68
  readonly search: ReturnType<typeof createSearchFamily>;
64
69
  readonly table: ReturnType<typeof createTableFamily>;
70
+ readonly viewport: ReturnType<typeof createViewportFamily>;
65
71
  };
66
72
 
67
73
  export type ApiV3Ai = ReturnType<typeof createInspectFamily>
@@ -71,7 +77,11 @@ export type ApiV3Ai = ReturnType<typeof createInspectFamily>
71
77
  & ReturnType<typeof createAttachFamily>
72
78
  & ReturnType<typeof createExportFamily>
73
79
  & ReturnType<typeof createExplainFamily>
74
- & ReturnType<typeof createPolicyFamily>;
80
+ & ReturnType<typeof createPolicyFamily>
81
+ & ReturnType<typeof createAiReviewFamily>
82
+ & ReturnType<typeof createEvaluateFamily>
83
+ & ReturnType<typeof createStatsFamily>
84
+ & ReturnType<typeof createOutlineFamily>;
75
85
 
76
86
  export interface ApiV3 {
77
87
  readonly runtime: ApiV3Runtime;
@@ -117,6 +127,10 @@ export function createApiV3(handle: RuntimeApiHandle, opts?: CreateApiV3Opts): A
117
127
  ...createExportFamily(handle),
118
128
  ...createExplainFamily(handle),
119
129
  ...createPolicyFamily(handle),
130
+ ...createAiReviewFamily(handle),
131
+ ...createEvaluateFamily(handle),
132
+ ...createStatsFamily(handle),
133
+ ...createOutlineFamily(handle),
120
134
  };
121
135
  const runtime: ApiV3Runtime = {
122
136
  document: createDocumentFamily(handle),
@@ -131,6 +145,7 @@ export function createApiV3(handle: RuntimeApiHandle, opts?: CreateApiV3Opts): A
131
145
  chart: createChartFamily(handle),
132
146
  search: createSearchFamily(handle),
133
147
  table: createTableFamily(handle),
148
+ viewport: createViewportFamily(handle),
134
149
  };
135
150
  // Construct the UI namespace only when a factory is supplied.
136
151
  // `ui` remains `undefined` otherwise — the shape in ApiV3 is optional
@@ -55,6 +55,7 @@ export type RuntimeApiHandle = Pick<
55
55
  // Review (runtime.review family)
56
56
  | "getReviewWorkSnapshot"
57
57
  | "acceptChange"
58
+ | "rejectChange"
58
59
  | "resolveComment"
59
60
  // Workflow (runtime.workflow + ai.inspect families)
60
61
  | "queryScopes"
@@ -131,6 +132,7 @@ export const RUNTIME_API_HANDLE_SHAPE_CHECK: Record<keyof RuntimeApiHandle, true
131
132
  findAllText: true,
132
133
  getReviewWorkSnapshot: true,
133
134
  acceptChange: true,
135
+ rejectChange: true,
134
136
  resolveComment: true,
135
137
  queryScopes: true,
136
138
  getWorkflowMarkupSnapshot: true,
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @endStateApi v3 — `ai.evaluate` family.
3
+ *
4
+ * evaluateAction — pre-flight verdict combining /06's static policy
5
+ * matrix with live runtime state. Returns whether an action can
6
+ * currently proceed, what blockers (if any) apply, and whether human
7
+ * approval is required per the policy tier.
8
+ *
9
+ * Composition rules:
10
+ *
11
+ * 1. If `policy.support` is `'blocked'` or `'unsupported'`, emit a
12
+ * `policy-refused:<support>` blocker. `allowed: false` regardless
13
+ * of live state — the policy override wins.
14
+ *
15
+ * 2. When `input.scopeId` is supplied, resolve it via the scope
16
+ * compiler. If it does not resolve, emit
17
+ * `scope-not-resolvable:<scopeId>` and flip `allowed: false`. This
18
+ * mirrors the refusal taxonomy that `ai.validateReplacementScope`
19
+ * surfaces.
20
+ *
21
+ * 3. `approvalRequired` is the policy tier's confirmation flag —
22
+ * `support === 'confirmation-required' ||
23
+ * requirements.userConfirmation`. Agents should surface this to a
24
+ * human before calling the mutating path, even when `allowed:
25
+ * true`.
26
+ *
27
+ * Read-family function — A4 audit emission does not apply.
28
+ * `validateReplacementScope` remains the authoritative pre-flight for
29
+ * replacement-class mutations (it runs the full `composeScopeValidation`
30
+ * pipeline); `evaluateAction` is a lighter, scope-optional probe an
31
+ * agent can call against any `AIAction`, including non-replacement
32
+ * operations (resolve_comment_thread, auto_accept_changes, etc.) that
33
+ * have no scope target.
34
+ */
35
+
36
+ import type { RuntimeApiHandle } from "../_runtime-handle.ts";
37
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
38
+ import {
39
+ getAIActionPolicy,
40
+ type AIAction,
41
+ type AIActionPolicy,
42
+ } from "../../../runtime/workflow/ai-action-policy.ts";
43
+ import { createScopeCompilerService } from "../../../runtime/scopes/index.ts";
44
+
45
+ export interface EvaluateActionInput {
46
+ readonly action: AIAction;
47
+ readonly scopeId?: string;
48
+ }
49
+
50
+ export interface EvaluateActionResult {
51
+ readonly action: AIAction;
52
+ readonly allowed: boolean;
53
+ readonly policy: AIActionPolicy;
54
+ readonly blockers: readonly string[];
55
+ readonly approvalRequired: boolean;
56
+ }
57
+
58
+ export const evaluateActionMetadata: ApiV3FnMetadata = {
59
+ name: "ai.evaluateAction",
60
+ status: "live-with-adapter",
61
+ sourceLayer: "workflow-review",
62
+ liveEvidence: {
63
+ runnerTest: "test/api/v3/ai/ai-evaluate-action.test.ts",
64
+ commit: "refactor-09-post-closure-evaluate-action",
65
+ },
66
+ uxIntent: { uiVisible: false, expectsUxResponse: "none" },
67
+ agentMetadata: {
68
+ readOrMutate: "read",
69
+ boundedScope: "scope",
70
+ auditCategory: "policy-evaluate",
71
+ contextPromptShape:
72
+ "Pre-flight verdict: can this action proceed against (optionally) this scope? Returns {allowed, policy, blockers, approvalRequired}. Composes getPolicy() with live scope resolvability.",
73
+ },
74
+ stateClass: "A-canonical",
75
+ persistsTo: "canonical",
76
+ rwdReference:
77
+ "§AI API § ai.evaluateAction. Composes /06's static policy matrix with live scope resolvability. Returns a single-verdict carrier agents branch on before calling the mutating path. Read-only; no audit emission.",
78
+ };
79
+
80
+ export function createEvaluateFamily(runtime: RuntimeApiHandle) {
81
+ const compiler = createScopeCompilerService(runtime);
82
+ return {
83
+ evaluateAction(input: EvaluateActionInput): EvaluateActionResult {
84
+ // @endStateApi — live-with-adapter. Composes /06's static policy
85
+ // matrix with live scope resolvability from the /08 compiler.
86
+ const policy = getAIActionPolicy(input.action);
87
+ const blockers: string[] = [];
88
+
89
+ if (policy.support === "blocked" || policy.support === "unsupported") {
90
+ blockers.push(`policy-refused:${policy.support}`);
91
+ }
92
+
93
+ if (input.scopeId !== undefined) {
94
+ const compiled = compiler.compileScopeById(input.scopeId);
95
+ if (!compiled) {
96
+ blockers.push(`scope-not-resolvable:${input.scopeId}`);
97
+ }
98
+ }
99
+
100
+ const approvalRequired =
101
+ policy.support === "confirmation-required" ||
102
+ policy.requirements.userConfirmation === true;
103
+
104
+ return {
105
+ action: input.action,
106
+ allowed: blockers.length === 0,
107
+ policy,
108
+ blockers: Object.freeze([...blockers]),
109
+ approvalRequired,
110
+ };
111
+ },
112
+ };
113
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @endStateApi v3 — `ai.getDocumentOutline`.
3
+ *
4
+ * Composes the layer-04/07 navigation snapshot with the layer-07 outline
5
+ * builder to return a heading tree an agent can surface for "jump to
6
+ * section" / "summarize this section" flows.
7
+ *
8
+ * Each entry carries `headingId`, `level`, `text`, `offset`, `pageIndex`,
9
+ * `sectionIndex`, and `parentHeadingIds` (top-level headings have an
10
+ * empty array). `activeHeadingId` points at the heading containing the
11
+ * current selection head, if any.
12
+ *
13
+ * Read-family; no audit emission.
14
+ */
15
+
16
+ import type { RuntimeApiHandle } from "../_runtime-handle.ts";
17
+ import type { ApiV3FnMetadata } from "../_layer-metadata.ts";
18
+ import type {
19
+ DocumentOutlineHeadingSnapshot,
20
+ DocumentOutlineSnapshot,
21
+ } from "../../public-types.ts";
22
+ import { createDocumentNavigationSnapshot } from "../../../runtime/document-navigation.ts";
23
+ import { createDocumentOutlineSnapshot } from "../../../runtime/document-outline.ts";
24
+ import {
25
+ computeBlockPositions,
26
+ createScopeCompilerService,
27
+ } from "../../../runtime/scopes/index.ts";
28
+
29
+ /**
30
+ * Gap B (coord-08 post-Slice-7 integration) — v3 extension of
31
+ * `DocumentOutlineHeadingSnapshot` that carries the L08 scope
32
+ * compiler's `scopeId` for each heading. Agents use it directly with
33
+ * `ai.applyReplacementScope` / `ai.attachExplanation` / etc. without a
34
+ * separate `ai.listScopes({kind:"heading"})` lookup + offset match.
35
+ *
36
+ * The field is populated when the compiler's heading enumeration has
37
+ * a matching entry by block-range start; omitted when no match is
38
+ * found (e.g. a heading outside the `main` story). Empty-docs /
39
+ * no-heading docs return `headings: []` — the enrichment is a no-op.
40
+ */
41
+ export type GetDocumentOutlineHeadingEntry = DocumentOutlineHeadingSnapshot & {
42
+ readonly scopeId?: string;
43
+ };
44
+
45
+ export type GetDocumentOutlineResult = Omit<
46
+ DocumentOutlineSnapshot,
47
+ "headings"
48
+ > & {
49
+ readonly headings: readonly GetDocumentOutlineHeadingEntry[];
50
+ };
51
+
52
+ export const getDocumentOutlineMetadata: ApiV3FnMetadata = {
53
+ name: "ai.getDocumentOutline",
54
+ status: "live-with-adapter",
55
+ sourceLayer: "workflow-review",
56
+ liveEvidence: {
57
+ runnerTest: "test/api/v3/ai/ai-document-read.test.ts",
58
+ commit: "refactor-09-post-closure-document-read",
59
+ },
60
+ uxIntent: { uiVisible: false, expectsUxResponse: "none" },
61
+ agentMetadata: {
62
+ readOrMutate: "read",
63
+ boundedScope: "document",
64
+ auditCategory: "document-outline-read",
65
+ contextPromptShape:
66
+ "Hierarchical heading outline — {headingId, level, text, offset, pageIndex, sectionIndex, parentHeadingIds} per entry, plus activeHeadingId when selection lives under a heading.",
67
+ },
68
+ stateClass: "A-canonical",
69
+ persistsTo: "canonical",
70
+ rwdReference:
71
+ "§AI API § ai.getDocumentOutline. Composes createDocumentNavigationSnapshot (L04/07) with createDocumentOutlineSnapshot (L07) to surface the heading tree. Read-only; no audit emission.",
72
+ };
73
+
74
+ export function createOutlineFamily(runtime: RuntimeApiHandle) {
75
+ const compiler = createScopeCompilerService(runtime);
76
+ return {
77
+ getDocumentOutline(): GetDocumentOutlineResult {
78
+ // @endStateApi — live-with-adapter. Composes L04/07 navigation
79
+ // snapshot with L07 outline builder to surface heading tree;
80
+ // enriches each heading entry with the L08 compiler's `scopeId`
81
+ // (Gap B, coord-08 post-Slice-7 integration).
82
+ const snapshot = runtime.getRenderSnapshot();
83
+ const document = runtime.getCanonicalDocument();
84
+ const selectionHead = snapshot.selection.head;
85
+ const navigation = createDocumentNavigationSnapshot(
86
+ document,
87
+ selectionHead,
88
+ snapshot.activeStory,
89
+ );
90
+ const outline = createDocumentOutlineSnapshot({
91
+ navigation,
92
+ activeStory: snapshot.activeStory,
93
+ selectionHead,
94
+ });
95
+
96
+ // Gap B enrichment — build an offset → scopeId map over the
97
+ // heading enumeration and join onto each outline entry. The
98
+ // offset-based join uses `computeBlockPositions` to translate
99
+ // the compiler's `blockIndex` (on the enumerated scope's
100
+ // `semanticPath`) back into a character offset equal to the
101
+ // outline's `offset` field.
102
+ const headingScopes = compiler
103
+ .compileAllScopes()
104
+ .filter((s) => s.kind === "heading");
105
+ const blockPositions = computeBlockPositions(document);
106
+ const blockOffsetByIndex = new Map<number, number>();
107
+ for (const entry of blockPositions) {
108
+ blockOffsetByIndex.set(entry.blockIndex, entry.from);
109
+ }
110
+ const scopeIdByOffset = new Map<number, string>();
111
+ for (const scope of headingScopes) {
112
+ // `semanticPath` is ["body", "heading", <level>, <blockIndex>]
113
+ // — the blockIndex is the last segment. Numeric parse + offset
114
+ // lookup gives us the heading paragraph's character offset.
115
+ const path = scope.handle.semanticPath;
116
+ const lastSegment = path[path.length - 1];
117
+ if (typeof lastSegment !== "string") continue;
118
+ const blockIndex = Number.parseInt(lastSegment, 10);
119
+ if (!Number.isFinite(blockIndex)) continue;
120
+ const offset = blockOffsetByIndex.get(blockIndex);
121
+ if (typeof offset === "number") {
122
+ scopeIdByOffset.set(offset, scope.handle.scopeId);
123
+ }
124
+ }
125
+
126
+ const enrichedHeadings: GetDocumentOutlineHeadingEntry[] =
127
+ outline.headings.map((heading) => {
128
+ const scopeId = scopeIdByOffset.get(heading.offset);
129
+ return scopeId ? { ...heading, scopeId } : heading;
130
+ });
131
+
132
+ return {
133
+ ...(outline.activeHeadingId
134
+ ? { activeHeadingId: outline.activeHeadingId }
135
+ : {}),
136
+ headings: enrichedHeadings,
137
+ };
138
+ },
139
+ };
140
+ }
@@ -121,6 +121,13 @@ export interface ApplyResult {
121
121
  readonly reason?: string;
122
122
  readonly blockers?: readonly string[];
123
123
  readonly auditHint?: string;
124
+ /**
125
+ * Gap A (post-Slice-7 integration) — revision IDs authored during
126
+ * the apply. Populated for suggest-mode (tracked insert + delete);
127
+ * empty for direct-edit. Agents chain into `ai.acceptRevision` /
128
+ * `ai.rejectRevision` with each id to land or discard the proposal.
129
+ */
130
+ readonly authoredRevisionIds: readonly string[];
124
131
  }
125
132
 
126
133
  export interface ApplyReplacementScopeInput {
@@ -331,6 +338,7 @@ export function createReplacementFamily(runtime: RuntimeApiHandle) {
331
338
  ? { blockers: Object.freeze([...result.validation.blockedReasons]) }
332
339
  : {}),
333
340
  ...(result.audit ? { auditHint: result.audit.actionId } : {}),
341
+ authoredRevisionIds: result.authoredRevisionIds,
334
342
  };
335
343
  },
336
344
  };