@beyondwork/docx-react-component 1.0.106 → 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.
Files changed (190) hide show
  1. package/package.json +19 -5
  2. package/src/api/geometry-overlay-rects.ts +5 -0
  3. package/src/api/package-version.ts +1 -1
  4. package/src/api/page-anchor-id.ts +5 -0
  5. package/src/api/public-types.ts +16 -9
  6. package/src/api/table-node-specs.ts +6 -0
  7. package/src/api/v3/_create.ts +2 -1
  8. package/src/api/v3/_page-anchor-id.ts +52 -0
  9. package/src/api/v3/_runtime-handle.ts +92 -1
  10. package/src/api/v3/ai/_audit-time.ts +5 -0
  11. package/src/api/v3/ai/_pe2-evidence.ts +38 -0
  12. package/src/api/v3/ai/attach.ts +7 -2
  13. package/src/api/v3/ai/replacement.ts +101 -18
  14. package/src/api/v3/ai/resolve.ts +2 -2
  15. package/src/api/v3/ai/review.ts +177 -3
  16. package/src/api/v3/index.ts +1 -0
  17. package/src/api/v3/runtime/collab.ts +462 -0
  18. package/src/api/v3/runtime/document.ts +503 -20
  19. package/src/api/v3/runtime/geometry.ts +97 -0
  20. package/src/api/v3/runtime/layout.ts +744 -0
  21. package/src/api/v3/runtime/perf-probe.ts +14 -0
  22. package/src/api/v3/runtime/viewport.ts +9 -8
  23. package/src/api/v3/ui/_types.ts +149 -55
  24. package/src/api/v3/ui/chrome-preset-model.ts +5 -5
  25. package/src/api/v3/ui/debug.ts +115 -2
  26. package/src/api/v3/ui/index.ts +13 -0
  27. package/src/api/v3/ui/overlays.ts +0 -8
  28. package/src/api/v3/ui/surface.ts +56 -0
  29. package/src/api/v3/ui/viewport.ts +22 -9
  30. package/src/core/commands/image-commands.ts +1 -0
  31. package/src/core/commands/index.ts +6 -0
  32. package/src/core/schema/text-schema.ts +43 -5
  33. package/src/core/selection/mapping.ts +8 -1
  34. package/src/core/selection/review-anchors.ts +5 -1
  35. package/src/core/state/text-transaction.ts +8 -2
  36. package/src/io/export/serialize-revisions.ts +149 -1
  37. package/src/io/normalize/normalize-text.ts +6 -0
  38. package/src/io/ooxml/parse-bookmark-references.ts +55 -0
  39. package/src/io/ooxml/parse-fields.ts +24 -2
  40. package/src/io/ooxml/parse-headers-footers.ts +38 -5
  41. package/src/io/ooxml/parse-main-document.ts +153 -9
  42. package/src/io/ooxml/parse-numbering.ts +20 -0
  43. package/src/io/ooxml/parse-revisions.ts +19 -8
  44. package/src/io/opc/package-reader.ts +98 -8
  45. package/src/model/anchor.ts +4 -3
  46. package/src/model/canonical-document.ts +220 -2
  47. package/src/model/canonical-hash.ts +221 -0
  48. package/src/model/canonical-layout-inputs.ts +245 -6
  49. package/src/model/layout/index.ts +1 -0
  50. package/src/model/layout/page-graph-types.ts +118 -1
  51. package/src/model/review/revision-types.ts +14 -3
  52. package/src/preservation/store.ts +20 -4
  53. package/src/review/README.md +1 -1
  54. package/src/review/store/revision-actions.ts +14 -2
  55. package/src/runtime/collab/event-types.ts +67 -1
  56. package/src/runtime/collab/runtime-collab-sync.ts +177 -5
  57. package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
  58. package/src/runtime/document-heading-outline.ts +147 -0
  59. package/src/runtime/document-navigation.ts +8 -243
  60. package/src/runtime/document-runtime.ts +240 -97
  61. package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
  62. package/src/runtime/formatting/layout-inputs.ts +38 -5
  63. package/src/runtime/formatting/numbering/geometry.ts +28 -2
  64. package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
  65. package/src/runtime/geometry/caret-geometry.ts +5 -6
  66. package/src/runtime/geometry/geometry-facet.ts +60 -10
  67. package/src/runtime/geometry/geometry-index.ts +591 -20
  68. package/src/runtime/geometry/geometry-types.ts +59 -0
  69. package/src/runtime/geometry/hit-test.ts +11 -1
  70. package/src/runtime/geometry/overlay-rects.ts +5 -3
  71. package/src/runtime/geometry/project-anchors.ts +1 -1
  72. package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
  73. package/src/runtime/layout/index.ts +6 -0
  74. package/src/runtime/layout/layout-engine-instance.ts +6 -1
  75. package/src/runtime/layout/layout-engine-version.ts +181 -16
  76. package/src/runtime/layout/layout-facet-types.ts +6 -0
  77. package/src/runtime/layout/page-graph.ts +21 -4
  78. package/src/runtime/layout/paginated-layout-engine.ts +139 -15
  79. package/src/runtime/layout/project-block-fragments.ts +265 -7
  80. package/src/runtime/layout/public-facet.ts +78 -24
  81. package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
  82. package/src/runtime/layout/table-row-split.ts +92 -35
  83. package/src/runtime/prerender/cache-envelope.ts +2 -2
  84. package/src/runtime/prerender/cache-key.ts +5 -4
  85. package/src/runtime/prerender/customxml-cache.ts +0 -1
  86. package/src/runtime/render/render-kernel.ts +1 -1
  87. package/src/runtime/revision-runtime.ts +112 -10
  88. package/src/runtime/scopes/_scope-dependencies.ts +1 -0
  89. package/src/runtime/scopes/action-validation.ts +22 -2
  90. package/src/runtime/scopes/capabilities.ts +316 -0
  91. package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
  92. package/src/runtime/scopes/compiler-service.ts +108 -4
  93. package/src/runtime/scopes/content-control-evidence.ts +79 -0
  94. package/src/runtime/scopes/create-issue.ts +5 -5
  95. package/src/runtime/scopes/evidence.ts +91 -0
  96. package/src/runtime/scopes/formatting/apply.ts +2 -0
  97. package/src/runtime/scopes/geometry-evidence.ts +130 -0
  98. package/src/runtime/scopes/index.ts +54 -0
  99. package/src/runtime/scopes/issue-lifecycle.ts +224 -0
  100. package/src/runtime/scopes/layout-evidence.ts +374 -0
  101. package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
  102. package/src/runtime/scopes/preservation-boundary.ts +7 -1
  103. package/src/runtime/scopes/replacement/apply.ts +97 -34
  104. package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
  105. package/src/runtime/scopes/semantic-scope-types.ts +242 -3
  106. package/src/runtime/scopes/visualization.ts +28 -0
  107. package/src/runtime/surface-projection.ts +44 -5
  108. package/src/runtime/telemetry/perf-probe.ts +216 -0
  109. package/src/runtime/virtualized-rendering.ts +36 -1
  110. package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
  111. package/src/runtime/workflow/coordinator.ts +39 -11
  112. package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
  113. package/src/runtime/workflow/index.ts +3 -0
  114. package/src/runtime/workflow/overlay-lane-types.ts +58 -0
  115. package/src/runtime/workflow/overlay-lanes.ts +168 -10
  116. package/src/runtime/workflow/overlay-store.ts +2 -2
  117. package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
  118. package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
  119. package/src/session/_sync-legacy.ts +17 -27
  120. package/src/session/import/loader.ts +6 -4
  121. package/src/session/import/source-package-evidence.ts +186 -2
  122. package/src/session/index.ts +5 -6
  123. package/src/session/session.ts +30 -56
  124. package/src/session/types.ts +8 -13
  125. package/src/shell/session-bootstrap.ts +155 -81
  126. package/src/ui/WordReviewEditor.tsx +520 -12
  127. package/src/ui/editor-shell-view.tsx +14 -4
  128. package/src/ui/editor-surface-controller.tsx +5 -3
  129. package/src/ui/headless/selection-tool-resolver.ts +1 -2
  130. package/src/ui/presence-overlay-lane.ts +0 -1
  131. package/src/ui/ui-controller-factory.ts +7 -0
  132. package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
  133. package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
  134. package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
  135. package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
  136. package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
  137. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
  138. package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
  139. package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
  140. package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
  141. package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
  142. package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
  143. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
  144. package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
  145. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
  146. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
  147. package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
  148. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
  149. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
  150. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
  151. package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
  152. package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
  153. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
  154. package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
  155. package/src/ui-tailwind/debug/README.md +4 -1
  156. package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
  157. package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
  158. package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
  159. package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
  160. package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
  161. package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
  162. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
  163. package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
  164. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
  165. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
  166. package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
  167. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
  168. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
  169. package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
  170. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
  171. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
  172. package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
  173. package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
  174. package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
  175. package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
  176. package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
  177. package/src/ui-tailwind/review-workspace/types.ts +26 -12
  178. package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
  179. package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
  180. package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
  181. package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
  182. package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
  183. package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
  184. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
  185. package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
  186. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
  187. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
  188. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
  189. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
  190. package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
@@ -187,6 +187,13 @@ import {
187
187
  createWorkflowCoordinator,
188
188
  type WorkflowCoordinator,
189
189
  } from "./workflow/coordinator.ts";
190
+ import {
191
+ areWorkflowReviewOverlayLaneSnapshotsEqual,
192
+ shouldRefreshWorkflowReviewOverlayLane,
193
+ type WorkflowReviewOverlayLaneListener,
194
+ type WorkflowReviewOverlayLaneKind,
195
+ type WorkflowReviewOverlayLaneSnapshot,
196
+ } from "./workflow/overlay-lanes.ts";
190
197
  import {
191
198
  insertScopeMarkers,
192
199
  removeScopeMarkers,
@@ -307,7 +314,7 @@ import { rebuildFieldRegistry } from "./formatting/field/registry.ts";
307
314
  import {
308
315
  incrementInvalidationCounter,
309
316
  recordPerfSample,
310
- } from "../ui-tailwind/editor-surface/perf-probe.ts";
317
+ } from "./telemetry/perf-probe.ts";
311
318
  import {
312
319
  createLoadScheduler,
313
320
  type LoadScheduler,
@@ -689,6 +696,19 @@ export interface DocumentRuntime {
689
696
  * kernel is available) primary anchor rects.
690
697
  */
691
698
  getAllScopeCardModels(): readonly import("../api/public-types.ts").ScopeCardModel[];
699
+ /**
700
+ * Durable review/workflow overlay lane projection. Presence remains
701
+ * awareness-owned and returns an explicit unavailable snapshot here.
702
+ */
703
+ getReviewOverlayLane(kind: WorkflowReviewOverlayLaneKind): WorkflowReviewOverlayLaneSnapshot;
704
+ /**
705
+ * Subscribe to durable review/workflow lane changes. The presence lane is
706
+ * awareness-owned and must be subscribed through the awareness/UI source.
707
+ */
708
+ subscribeReviewOverlayLane(
709
+ kind: WorkflowReviewOverlayLaneKind,
710
+ listener: WorkflowReviewOverlayLaneListener,
711
+ ): Unsubscribe;
692
712
  };
693
713
  /**
694
714
  * Runtime-owned geometry facet (Layer 05 · Geometry Projection).
@@ -756,6 +776,8 @@ export interface DocumentRuntime {
756
776
  getFootnoteResolver(): FootnoteResolver | undefined;
757
777
  updateFields(options?: UpdateFieldsOptions): UpdateFieldsResult;
758
778
  updateTableOfContents(options?: TocRefreshOptions): TocRefreshResult;
779
+ /** Runtime wall-clock timestamp source used for audit/event emission. */
780
+ now(): string;
759
781
  getSessionState(): EditorSessionState;
760
782
  getPersistedSnapshot(): PersistedEditorSnapshot;
761
783
  getCompatibilityReport(): CompatibilityReport;
@@ -774,7 +796,10 @@ export interface DocumentRuntime {
774
796
  getWorkflowMarkupSnapshot(): WorkflowMarkupSnapshot;
775
797
  setWorkflowMetadataDefinitions(definitions: WorkflowMetadataDefinition[]): void;
776
798
  clearWorkflowMetadataDefinitions(): void;
777
- setWorkflowMetadataEntries(entries: WorkflowMetadataEntry[]): void;
799
+ setWorkflowMetadataEntries(
800
+ entries: WorkflowMetadataEntry[],
801
+ origin?: WorkflowEventOrigin,
802
+ ): void;
778
803
  clearWorkflowMetadataEntries(): void;
779
804
  getWorkflowMetadataSnapshot(): WorkflowMetadataSnapshot;
780
805
  /** W10 — class-A overlay-visibility policy (per-kind read). */
@@ -1015,8 +1040,8 @@ export interface CreateDocumentRuntimeOptions {
1015
1040
  * L7 Phase 2.5 — prerender cache seed. When set, the internal
1016
1041
  * LayoutEngineInstance is seeded with this graph so the first
1017
1042
  * `getPageGraph()` call returns it directly, skipping `fullRebuild`.
1018
- * The seeded graph must have been produced with the current
1019
- * `LAYOUT_ENGINE_VERSION` (callers rely on the cache-key scheme in
1043
+ * The seeded graph must have been produced with the current layout engine
1044
+ * and laycache schema versions (callers rely on the cache-key scheme in
1020
1045
  * `src/runtime/prerender/cache-key.ts` to guarantee this).
1021
1046
  *
1022
1047
  * See CLAUDE.md (lane status table) §Phase 2.5 §3.7.
@@ -1150,6 +1175,46 @@ function computeReviewStateHash(
1150
1175
  return `${commentParts.join(",")}|${revisionParts.join(",")}`;
1151
1176
  }
1152
1177
 
1178
+ function normalizeCompatibilityTextNode(text: string): string {
1179
+ return [
1180
+ text.length > 0 ? "nonempty" : "empty",
1181
+ /^\s/u.test(text) ? "leading-space" : "",
1182
+ /\s$/u.test(text) ? "trailing-space" : "",
1183
+ text.includes(" ") ? "double-space" : "",
1184
+ ].filter(Boolean).join(":");
1185
+ }
1186
+
1187
+ function normalizeCompatibilityCacheValue(key: string, value: unknown): unknown {
1188
+ if (key === "updatedAt" || key === "generatedAt") {
1189
+ return undefined;
1190
+ }
1191
+ if (
1192
+ value &&
1193
+ typeof value === "object" &&
1194
+ !Array.isArray(value) &&
1195
+ (value as { type?: unknown }).type === "text" &&
1196
+ typeof (value as { text?: unknown }).text === "string"
1197
+ ) {
1198
+ return {
1199
+ ...value,
1200
+ text: normalizeCompatibilityTextNode((value as { text: string }).text),
1201
+ };
1202
+ }
1203
+ return value;
1204
+ }
1205
+
1206
+ function computeCompatibilityCacheKey(state: EditorState): string {
1207
+ return JSON.stringify(
1208
+ {
1209
+ document: state.document,
1210
+ warnings: state.warnings,
1211
+ fatalError: state.fatalError,
1212
+ baseCompatibility: state.compatibility,
1213
+ },
1214
+ normalizeCompatibilityCacheValue,
1215
+ );
1216
+ }
1217
+
1153
1218
  export function createDocumentRuntime(
1154
1219
  options: CreateDocumentRuntimeOptions,
1155
1220
  ): DocumentRuntime {
@@ -1315,7 +1380,7 @@ export function createDocumentRuntime(
1315
1380
  // runtime's current document identity so `keyEqual` passes without
1316
1381
  // triggering a rebuild. The cache-key scheme in
1317
1382
  // src/runtime/prerender/cache-key.ts guarantees the seed is produced
1318
- // by the same LAYOUT_ENGINE_VERSION.
1383
+ // by the same layout engine and laycache schema versions.
1319
1384
  layoutEngine.seedCachedGraph(options.seedLayoutCache, state.document);
1320
1385
  perfCounters.increment("loadSession.layoutCache.seeded");
1321
1386
  }
@@ -1326,6 +1391,15 @@ export function createDocumentRuntime(
1326
1391
  // `renderKernelRef` is a forward reference so the facet can reach the
1327
1392
  // kernel after it is created below (kernel creation needs the facet).
1328
1393
  let renderKernelRef: RenderKernel | null = null;
1394
+ // `runtimeRef` is a forward reference so the facet can call the runtime's
1395
+ // viewport methods without duplicating their implementation.
1396
+ let runtimeRef: DocumentRuntime | null = null;
1397
+ const getRuntimeForLayoutFacet = (): DocumentRuntime => {
1398
+ if (!runtimeRef) {
1399
+ throw new Error("Document runtime viewport methods are not initialized");
1400
+ }
1401
+ return runtimeRef;
1402
+ };
1329
1403
  const layoutFacet: WordReviewEditorLayoutFacet = createLayoutFacet({
1330
1404
  engine: layoutEngine,
1331
1405
  getQueryInput: () => ({
@@ -1344,24 +1418,15 @@ export function createDocumentRuntime(
1344
1418
  // input via the getQueryInput + engine accessors above.
1345
1419
  // L7 Phase 2 — delegate viewport culling through the facet so the
1346
1420
  // workspace (which only holds a `layoutFacet` ref) can drive culling
1347
- // without a separate `DocumentRuntime` prop. The lambdas forward to
1348
- // the runtime's own `setVisibleBlockRange` / `requestViewportRefresh`
1349
- // implementations which are defined below. Capturing by closure works
1350
- // because the lambdas are only invoked after the full runtime object is
1351
- // built and the methods below have been assigned.
1352
- // TODO(L7 Phase 2 post-merge): this lambda duplicates the setVisibleBlockRange
1353
- // logic implemented on `runtime` at line ~2874. Consolidate after Phase 2 ships
1354
- // so the facet forwards to the runtime method. Forward-reference pattern matches
1355
- // `renderKernelRef` — see that example. See spec-review notes on commit 0b03bfa7.
1421
+ // without a separate `DocumentRuntime` prop.
1356
1422
  setVisibleBlockRange: (range) => {
1357
- applyViewportRanges([range]);
1423
+ getRuntimeForLayoutFacet().setVisibleBlockRange(range);
1358
1424
  },
1359
1425
  setVisibleBlockRanges: (ranges) => {
1360
- applyViewportRanges(ranges);
1426
+ getRuntimeForLayoutFacet().setVisibleBlockRanges(ranges);
1361
1427
  },
1362
1428
  requestViewportRefresh: () => {
1363
- perfCounters.increment("runtime.viewport.refreshes");
1364
- maybeRefreshSurfaceForViewport();
1429
+ getRuntimeForLayoutFacet().requestViewportRefresh();
1365
1430
  },
1366
1431
  });
1367
1432
  // D1 — wire the layout-guard warning emitter so `return []` guard sites
@@ -1416,7 +1481,14 @@ export function createDocumentRuntime(
1416
1481
 
1417
1482
  let cachedSurface:
1418
1483
  | {
1419
- revisionToken: string;
1484
+ content: CanonicalDocumentEnvelope["content"];
1485
+ subParts: CanonicalDocumentEnvelope["subParts"];
1486
+ styles: CanonicalDocumentEnvelope["styles"];
1487
+ numbering: CanonicalDocumentEnvelope["numbering"];
1488
+ media: CanonicalDocumentEnvelope["media"];
1489
+ preservation: CanonicalDocumentEnvelope["preservation"];
1490
+ review: CanonicalDocumentEnvelope["review"];
1491
+ effectiveMarkupModeProvider: typeof effectiveMarkupModeProvider;
1420
1492
  activeStoryKey: string;
1421
1493
  /**
1422
1494
  * Serialized viewport ranges at build time. A surface snapshot is
@@ -1424,9 +1496,9 @@ export function createDocumentRuntime(
1424
1496
  * runtime's current `viewportRangesKey` matches — otherwise the
1425
1497
  * cached snapshot realizes a different set of blocks as real vs
1426
1498
  * placeholder-culled than the current runtime state demands.
1427
- * Pre-refactor/11b this was a (revisionToken, activeStoryKey) key
1428
- * only, which was silently stale-prone when callers applied new
1429
- * viewport ranges without also calling `requestViewportRefresh()`.
1499
+ * Do not key this cache on `revisionToken`: commits can advance the
1500
+ * token without changing the structural inputs surface projection
1501
+ * consumes.
1430
1502
  */
1431
1503
  viewportRangesKey: string;
1432
1504
  snapshot: RuntimeRenderSnapshot["surface"];
@@ -1435,10 +1507,7 @@ export function createDocumentRuntime(
1435
1507
  let cachedCompatibility:
1436
1508
  | {
1437
1509
  revisionToken: string;
1438
- /** Block count at the time of the last full rebuild. O(1) proxy for structural stability. */
1439
- blockCount: number;
1440
- /** Warning count at time of rebuild — warnings array gets remapped on every edit. */
1441
- warningCount: number;
1510
+ structuralKey: string;
1442
1511
  warnings: EditorState["warnings"];
1443
1512
  fatalError: EditorState["fatalError"];
1444
1513
  report: RuntimeRenderSnapshot["compatibility"];
@@ -1454,7 +1523,7 @@ export function createDocumentRuntime(
1454
1523
  let cachedTrackedChanges:
1455
1524
  | {
1456
1525
  revisions: CanonicalDocumentEnvelope["review"]["revisions"];
1457
- revisionToken: string;
1526
+ comments: CanonicalDocumentEnvelope["review"]["comments"];
1458
1527
  snapshot: TrackedChangesSnapshot;
1459
1528
  }
1460
1529
  | undefined;
@@ -1534,19 +1603,22 @@ export function createDocumentRuntime(
1534
1603
  | undefined;
1535
1604
  let cachedNavigation:
1536
1605
  | {
1537
- revisionToken: string;
1606
+ document: CanonicalDocumentEnvelope;
1538
1607
  activeStoryKey: string;
1608
+ workspaceMode: WorkspaceMode;
1609
+ zoomLevel: ZoomLevel;
1539
1610
  selectionHead: number;
1540
1611
  snapshot: DocumentNavigationSnapshot;
1541
1612
  }
1542
1613
  | undefined;
1543
1614
  let cachedViewStateSnapshot:
1544
1615
  | {
1545
- revisionToken: string;
1546
1616
  activeStoryKey: string;
1547
1617
  selection: EditorState["selection"];
1548
1618
  viewStateRef: ViewState;
1619
+ surface: RuntimeRenderSnapshot["surface"];
1549
1620
  pageLayout: PageLayoutSnapshot | null | undefined;
1621
+ numbering: CanonicalDocumentEnvelope["numbering"];
1550
1622
  snapshot: EditorViewStateSnapshot;
1551
1623
  }
1552
1624
  | undefined;
@@ -1605,7 +1677,14 @@ export function createDocumentRuntime(
1605
1677
  : surfaceViewportRangesKey;
1606
1678
  if (
1607
1679
  cachedSurface &&
1608
- cachedSurface.revisionToken === state.revisionToken &&
1680
+ cachedSurface.content === document.content &&
1681
+ cachedSurface.subParts === document.subParts &&
1682
+ cachedSurface.styles === document.styles &&
1683
+ cachedSurface.numbering === document.numbering &&
1684
+ cachedSurface.media === document.media &&
1685
+ cachedSurface.preservation === document.preservation &&
1686
+ cachedSurface.review === document.review &&
1687
+ cachedSurface.effectiveMarkupModeProvider === effectiveMarkupModeProvider &&
1609
1688
  cachedSurface.activeStoryKey === activeStoryKey &&
1610
1689
  cachedSurface.viewportRangesKey === surfaceCacheKey
1611
1690
  ) {
@@ -1644,7 +1723,14 @@ export function createDocumentRuntime(
1644
1723
  ? snapshot
1645
1724
  : enrichCulledPlaceholdersWithHeights(snapshot);
1646
1725
  cachedSurface = {
1647
- revisionToken: state.revisionToken,
1726
+ content: document.content,
1727
+ subParts: document.subParts,
1728
+ styles: document.styles,
1729
+ numbering: document.numbering,
1730
+ media: document.media,
1731
+ preservation: document.preservation,
1732
+ review: document.review,
1733
+ effectiveMarkupModeProvider,
1648
1734
  activeStoryKey,
1649
1735
  viewportRangesKey: surfaceCacheKey,
1650
1736
  snapshot: enrichedSnapshot,
@@ -1652,7 +1738,7 @@ export function createDocumentRuntime(
1652
1738
  // Keep the scroll-path fingerprint in lockstep so a subsequent
1653
1739
  // `maybeRefreshSurfaceForViewport` sees the freshly-built snapshot
1654
1740
  // and short-circuits instead of paying a redundant projection.
1655
- cachedSurfaceFingerprint = `${state.revisionToken}|${activeStoryKey}|${surfaceCacheKey}|${String(state.selection.anchor)}:${String(state.selection.head)}`;
1741
+ cachedSurfaceFingerprint = `${activeStoryKey}|${surfaceCacheKey}|${String(state.selection.anchor)}:${String(state.selection.head)}`;
1656
1742
  return enrichedSnapshot;
1657
1743
  }
1658
1744
 
@@ -1761,8 +1847,7 @@ export function createDocumentRuntime(
1761
1847
  function getCachedCompatibilityReport(
1762
1848
  nextState: EditorState,
1763
1849
  ): RuntimeRenderSnapshot["compatibility"] {
1764
- const blockCount = nextState.document.content.children.length;
1765
- const warningCount = nextState.warnings?.length ?? 0;
1850
+ let structuralKey: string | undefined;
1766
1851
  if (cachedCompatibility) {
1767
1852
  // Fast path 1: same revisionToken (selection move, surface-only refresh).
1768
1853
  if (
@@ -1772,27 +1857,21 @@ export function createDocumentRuntime(
1772
1857
  ) {
1773
1858
  return cachedCompatibility.report;
1774
1859
  }
1775
- // Fast path 2: revisionToken changed but block structure and warning
1776
- // count are stable (text-only edit). buildCompatibilityReport reads
1777
- // block types, review counts, preservation, and warnings — NOT run text.
1778
- // Block count + warning count + fatalError identity is an O(1) proxy:
1779
- // if these are stable, the compatibility report output is unchanged.
1780
- // `warnings` array gets remapped to a new reference on every commit via
1781
- // remapReviewStateAfterContentChange, so we can't use reference equality.
1860
+ structuralKey = computeCompatibilityCacheKey(nextState);
1782
1861
  if (
1783
- cachedCompatibility.blockCount === blockCount &&
1784
- cachedCompatibility.warningCount === warningCount &&
1785
- cachedCompatibility.fatalError === nextState.fatalError
1862
+ cachedCompatibility.structuralKey === structuralKey
1786
1863
  ) {
1787
1864
  cachedCompatibility = {
1788
1865
  ...cachedCompatibility,
1789
1866
  revisionToken: nextState.revisionToken,
1790
1867
  warnings: nextState.warnings,
1868
+ fatalError: nextState.fatalError,
1791
1869
  };
1792
1870
  return cachedCompatibility.report;
1793
1871
  }
1794
1872
  }
1795
1873
 
1874
+ structuralKey ??= computeCompatibilityCacheKey(nextState);
1796
1875
  const derived = createDerivedCompatibility(nextState);
1797
1876
  recordPerfSample("snapshot.compatibility");
1798
1877
  incrementInvalidationCounter("runtime.snapshot.compatibilityMisses");
@@ -1807,8 +1886,7 @@ export function createDocumentRuntime(
1807
1886
  };
1808
1887
  cachedCompatibility = {
1809
1888
  revisionToken: nextState.revisionToken,
1810
- blockCount,
1811
- warningCount,
1889
+ structuralKey,
1812
1890
  warnings: nextState.warnings,
1813
1891
  fatalError: nextState.fatalError,
1814
1892
  report,
@@ -1841,7 +1919,7 @@ export function createDocumentRuntime(
1841
1919
  if (
1842
1920
  cachedTrackedChanges &&
1843
1921
  cachedTrackedChanges.revisions === nextState.document.review.revisions &&
1844
- cachedTrackedChanges.revisionToken === nextState.revisionToken
1922
+ cachedTrackedChanges.comments === nextState.document.review.comments
1845
1923
  ) {
1846
1924
  return cachedTrackedChanges.snapshot;
1847
1925
  }
@@ -1849,7 +1927,7 @@ export function createDocumentRuntime(
1849
1927
  const snapshot = toPublicTrackedChangesSnapshot(nextState);
1850
1928
  cachedTrackedChanges = {
1851
1929
  revisions: nextState.document.review.revisions,
1852
- revisionToken: nextState.revisionToken,
1930
+ comments: nextState.document.review.comments,
1853
1931
  snapshot,
1854
1932
  };
1855
1933
  return snapshot;
@@ -1899,8 +1977,10 @@ export function createDocumentRuntime(
1899
1977
  );
1900
1978
  if (
1901
1979
  cachedNavigation &&
1902
- cachedNavigation.revisionToken === nextState.revisionToken &&
1903
- cachedNavigation.activeStoryKey === activeStoryKey
1980
+ cachedNavigation.document === nextState.document &&
1981
+ cachedNavigation.activeStoryKey === activeStoryKey &&
1982
+ cachedNavigation.workspaceMode === viewState.workspaceMode &&
1983
+ cachedNavigation.zoomLevel === viewState.zoomLevel
1904
1984
  ) {
1905
1985
  if (cachedNavigation.selectionHead === nextState.selection.head) {
1906
1986
  return cachedNavigation.snapshot;
@@ -1912,16 +1992,20 @@ export function createDocumentRuntime(
1912
1992
  snapshot.activeSectionIndex === cachedNavigation.snapshot.activeSectionIndex
1913
1993
  ) {
1914
1994
  cachedNavigation = {
1915
- revisionToken: nextState.revisionToken,
1995
+ document: nextState.document,
1916
1996
  activeStoryKey,
1997
+ workspaceMode: viewState.workspaceMode,
1998
+ zoomLevel: viewState.zoomLevel,
1917
1999
  selectionHead: nextState.selection.head,
1918
2000
  snapshot: cachedNavigation.snapshot,
1919
2001
  };
1920
2002
  return cachedNavigation.snapshot;
1921
2003
  }
1922
2004
  cachedNavigation = {
1923
- revisionToken: nextState.revisionToken,
2005
+ document: nextState.document,
1924
2006
  activeStoryKey,
2007
+ workspaceMode: viewState.workspaceMode,
2008
+ zoomLevel: viewState.zoomLevel,
1925
2009
  selectionHead: nextState.selection.head,
1926
2010
  snapshot,
1927
2011
  };
@@ -1932,8 +2016,10 @@ export function createDocumentRuntime(
1932
2016
  recordPerfSample("snapshot.navigation");
1933
2017
  incrementInvalidationCounter("runtime.snapshot.navigationMisses");
1934
2018
  cachedNavigation = {
1935
- revisionToken: nextState.revisionToken,
2019
+ document: nextState.document,
1936
2020
  activeStoryKey,
2021
+ workspaceMode: viewState.workspaceMode,
2022
+ zoomLevel: viewState.zoomLevel,
1937
2023
  selectionHead: nextState.selection.head,
1938
2024
  snapshot,
1939
2025
  };
@@ -2115,6 +2201,10 @@ export function createDocumentRuntime(
2115
2201
  }
2116
2202
 
2117
2203
  if (block.kind === "sdt_block") {
2204
+ const lockedSdtMatch = matchWorkflowLockedSurfaceBlock(block, range);
2205
+ if (lockedSdtMatch) {
2206
+ return lockedSdtMatch;
2207
+ }
2118
2208
  const match = findSurfaceOpaqueTargetIntersectingRange(block.children, range);
2119
2209
  if (match) {
2120
2210
  return match;
@@ -2146,6 +2236,49 @@ export function createDocumentRuntime(
2146
2236
  return null;
2147
2237
  }
2148
2238
 
2239
+ function matchWorkflowLockedSurfaceBlock(
2240
+ block: Extract<SurfaceBlockSnapshot, { kind: "sdt_block" }>,
2241
+ range: { from: number; to: number },
2242
+ ): {
2243
+ from: number;
2244
+ to: number;
2245
+ label: string;
2246
+ blockedReasonCode: "workflow_preserve_only" | "workflow_blocked_import";
2247
+ } | null {
2248
+ if (!block.blockedReasonCode) {
2249
+ return null;
2250
+ }
2251
+
2252
+ const blockRange = {
2253
+ from: block.from,
2254
+ to: block.to > block.from ? block.to : block.from + 1,
2255
+ };
2256
+ const targetRange = normalizeCaretRangeForSurfaceIntersection(range);
2257
+ if (!rangesIntersect(blockRange, targetRange)) {
2258
+ return null;
2259
+ }
2260
+
2261
+ return {
2262
+ from: blockRange.from,
2263
+ to: blockRange.to,
2264
+ label: block.label ?? block.alias ?? "Content control",
2265
+ blockedReasonCode: block.blockedReasonCode,
2266
+ };
2267
+ }
2268
+
2269
+ function normalizeCaretRangeForSurfaceIntersection(range: {
2270
+ from: number;
2271
+ to: number;
2272
+ }): { from: number; to: number } {
2273
+ if (range.to > range.from) {
2274
+ return range;
2275
+ }
2276
+ return {
2277
+ from: range.from,
2278
+ to: range.from + 1,
2279
+ };
2280
+ }
2281
+
2149
2282
  function matchOpaqueInlineSegment(
2150
2283
  segment: SurfaceInlineSegment,
2151
2284
  range: { from: number; to: number },
@@ -2632,13 +2765,20 @@ export function createDocumentRuntime(
2632
2765
  : {}),
2633
2766
  },
2634
2767
  );
2635
- cachedSurfaceFingerprint = `${state.revisionToken}|${storyTargetKey(activeStory)}|${viewportRangesKey}|${String(state.selection.anchor)}:${String(state.selection.head)}`;
2768
+ cachedSurfaceFingerprint = `${storyTargetKey(activeStory)}|${viewportRangesKey}|${String(state.selection.anchor)}:${String(state.selection.head)}`;
2636
2769
  // Refresh the cache with the just-built snapshot so a subsequent
2637
2770
  // refreshRenderSnapshot (same revisionToken + activeStoryKey) hits cache.
2638
2771
  // Without this repopulate, the next non-mutation refresh wastes a second
2639
2772
  // createEditorSurfaceSnapshot call on an identical rebuild.
2640
2773
  cachedSurface = {
2641
- revisionToken: state.revisionToken,
2774
+ content: state.document.content,
2775
+ subParts: state.document.subParts,
2776
+ styles: state.document.styles,
2777
+ numbering: state.document.numbering,
2778
+ media: state.document.media,
2779
+ preservation: state.document.preservation,
2780
+ review: state.document.review,
2781
+ effectiveMarkupModeProvider,
2642
2782
  activeStoryKey: storyTargetKey(activeStory),
2643
2783
  viewportRangesKey,
2644
2784
  snapshot: newSurface,
@@ -2660,12 +2800,12 @@ export function createDocumentRuntime(
2660
2800
  * a full projection + listener fan-out on every tick.
2661
2801
  *
2662
2802
  * Safe because every external input that can change the projection
2663
- * output without ticking revisionToken / selection / viewportRanges /
2664
- * activeStory is wired to call `refreshSurfaceOnly` directly (see
2665
- * `setEffectiveMarkupModeProvider`, `invalidateForMarkupModeChange`).
2803
+ * output without changing the tracked selection / viewportRanges /
2804
+ * activeStory tuple is wired to call `refreshSurfaceOnly` directly
2805
+ * (see `setEffectiveMarkupModeProvider`, `invalidateForMarkupModeChange`).
2666
2806
  */
2667
2807
  function maybeRefreshSurfaceForViewport(): void {
2668
- const fingerprint = `${state.revisionToken}|${storyTargetKey(activeStory)}|${viewportRangesKey}|${String(state.selection.anchor)}:${String(state.selection.head)}`;
2808
+ const fingerprint = `${storyTargetKey(activeStory)}|${viewportRangesKey}|${String(state.selection.anchor)}:${String(state.selection.head)}`;
2669
2809
  if (cachedSurfaceFingerprint === fingerprint && cachedSurface) {
2670
2810
  return;
2671
2811
  }
@@ -2722,19 +2862,20 @@ export function createDocumentRuntime(
2722
2862
 
2723
2863
  function getCachedViewStateSnapshot(): EditorViewStateSnapshot {
2724
2864
  const activeStoryKey = storyTargetKey(activeStory);
2865
+ const surface = cachedRenderSnapshot.surface;
2725
2866
  const pageLayout = cachedRenderSnapshot.pageLayout;
2726
2867
  if (
2727
2868
  cachedViewStateSnapshot &&
2728
- cachedViewStateSnapshot.revisionToken === state.revisionToken &&
2729
2869
  cachedViewStateSnapshot.activeStoryKey === activeStoryKey &&
2730
2870
  cachedViewStateSnapshot.selection === state.selection &&
2731
2871
  cachedViewStateSnapshot.viewStateRef === viewState &&
2732
- cachedViewStateSnapshot.pageLayout === pageLayout
2872
+ cachedViewStateSnapshot.surface === surface &&
2873
+ cachedViewStateSnapshot.pageLayout === pageLayout &&
2874
+ cachedViewStateSnapshot.numbering === state.document.numbering
2733
2875
  ) {
2734
2876
  return cachedViewStateSnapshot.snapshot;
2735
2877
  }
2736
2878
 
2737
- const surface = cachedRenderSnapshot.surface;
2738
2879
  const mainSurface =
2739
2880
  activeStory.kind === "main"
2740
2881
  ? surface
@@ -2749,11 +2890,12 @@ export function createDocumentRuntime(
2749
2890
  state.document.numbering,
2750
2891
  );
2751
2892
  cachedViewStateSnapshot = {
2752
- revisionToken: state.revisionToken,
2753
2893
  activeStoryKey,
2754
2894
  selection: state.selection,
2755
2895
  viewStateRef: viewState,
2896
+ surface,
2756
2897
  pageLayout,
2898
+ numbering: state.document.numbering,
2757
2899
  snapshot,
2758
2900
  };
2759
2901
  return snapshot;
@@ -3207,7 +3349,9 @@ export function createDocumentRuntime(
3207
3349
  try {
3208
3350
  const timestamp = clock();
3209
3351
  const selection = target ? createSelectionFromPublicAnchor(target) : state.selection;
3352
+ const effectiveDocumentMode = workflowCoordinator.getEffectiveDocumentMode(selection);
3210
3353
  if (
3354
+ effectiveDocumentMode !== "suggesting" &&
3211
3355
  shouldPreserveEquivalentReplacement(formatting) &&
3212
3356
  replacementTextMatchesCurrentRange(state.document, activeStory, selection, text)
3213
3357
  ) {
@@ -4384,6 +4528,27 @@ export function createDocumentRuntime(
4384
4528
  getAllScopeCardModels() {
4385
4529
  return workflowCoordinator.getAllScopeCardModels();
4386
4530
  },
4531
+ getReviewOverlayLane(kind: WorkflowReviewOverlayLaneKind) {
4532
+ return workflowCoordinator.getReviewOverlayLane(kind);
4533
+ },
4534
+ subscribeReviewOverlayLane(
4535
+ kind: WorkflowReviewOverlayLaneKind,
4536
+ listener: WorkflowReviewOverlayLaneListener,
4537
+ ) {
4538
+ if (kind === "presence") {
4539
+ throw new Error(
4540
+ "runtime.workflow.subscribeReviewOverlayLane: presence is awareness-owned; use the awareness-backed presence lane source",
4541
+ );
4542
+ }
4543
+ let lastSnapshot = workflowCoordinator.getReviewOverlayLane(kind);
4544
+ return runtime.subscribeToEvents((event) => {
4545
+ if (!shouldRefreshWorkflowReviewOverlayLane(kind, event)) return;
4546
+ const nextSnapshot = workflowCoordinator.getReviewOverlayLane(kind);
4547
+ if (areWorkflowReviewOverlayLaneSnapshotsEqual(lastSnapshot, nextSnapshot)) return;
4548
+ lastSnapshot = nextSnapshot;
4549
+ listener(nextSnapshot);
4550
+ });
4551
+ },
4387
4552
  },
4388
4553
  geometry: geometryFacet,
4389
4554
  getLayoutFacet() {
@@ -4579,9 +4744,18 @@ export function createDocumentRuntime(
4579
4744
  protectionSelection: refreshed.protectionSelection,
4580
4745
  origin: createOrigin("api", clock()),
4581
4746
  });
4747
+ // TOC refresh is an explicit structural API operation, not a typing
4748
+ // hot path. Re-realize the full surface so refreshed generated rows
4749
+ // remain inspectable instead of collapsing to selection-corridor
4750
+ // viewport placeholders immediately after the replacement.
4751
+ applyViewportRanges(null);
4752
+ cachedRenderSnapshot = refreshRenderSnapshot();
4582
4753
  }
4583
4754
  return refreshed.result;
4584
4755
  },
4756
+ now() {
4757
+ return clock();
4758
+ },
4585
4759
  getSessionState() {
4586
4760
  const compatibility = createDerivedCompatibility(state);
4587
4761
  const normalizedWorkflowOverlay = workflowCoordinator.getWorkflowOverlay();
@@ -4737,8 +4911,8 @@ export function createDocumentRuntime(
4737
4911
  clearWorkflowMetadataDefinitions() {
4738
4912
  workflowCoordinator.clearWorkflowMetadataDefinitions();
4739
4913
  },
4740
- setWorkflowMetadataEntries(entries) {
4741
- workflowCoordinator.setWorkflowMetadataEntries(entries);
4914
+ setWorkflowMetadataEntries(entries, origin) {
4915
+ workflowCoordinator.setWorkflowMetadataEntries(entries, origin);
4742
4916
  },
4743
4917
  clearWorkflowMetadataEntries() {
4744
4918
  workflowCoordinator.clearWorkflowMetadataEntries();
@@ -5025,6 +5199,7 @@ export function createDocumentRuntime(
5025
5199
  maybeRefreshSurfaceForViewport();
5026
5200
  },
5027
5201
  };
5202
+ runtimeRef = runtime;
5028
5203
 
5029
5204
  // Late-bind the workflow coordinator's dispatch accessor now that
5030
5205
  // the runtime object exists. addScope / removeScope / setWorkflowOverlay /
@@ -5556,30 +5731,6 @@ export function createDocumentRuntime(
5556
5731
  };
5557
5732
  const opId = (command.origin as { opId?: string } | undefined)?.opId;
5558
5733
  const selection = textOptions.selection ?? state.selection;
5559
- if (
5560
- activeStory.kind !== "main" &&
5561
- workflowCoordinator.getEffectiveDocumentMode(selection) === "suggesting" &&
5562
- command.type === "paragraph.split"
5563
- ) {
5564
- const message = `"${command.type}" is not supported in suggesting mode for this story.`;
5565
- emit({
5566
- type: "command_blocked",
5567
- documentId: state.documentId,
5568
- command: textOptions.blockedCommandName ?? command.type,
5569
- reasons: [{
5570
- code: "suggesting_unsupported",
5571
- message,
5572
- storyTarget: activeStory,
5573
- }],
5574
- });
5575
- return completeDispatch({
5576
- kind: "rejected",
5577
- refreshClass: "blocked",
5578
- opId,
5579
- newRevisionToken: "",
5580
- blockedReasons: [{ code: "suggesting_unsupported", message }],
5581
- });
5582
- }
5583
5734
  if (!textOptions.skipWorkflowGuard) {
5584
5735
  const blockedReasons = workflowCoordinator.evaluateBlockedReasons(selection, command.type);
5585
5736
  if (blockedReasons.length > 0) {
@@ -7145,14 +7296,6 @@ const NON_MUTATION_COMMANDS = new Set([
7145
7296
  /** Mutation commands that are not yet supported in suggesting mode. */
7146
7297
  const SUGGESTING_UNSUPPORTED_COMMANDS = new Set<string>([]);
7147
7298
 
7148
- const SUGGESTING_SECONDARY_STORY_UNSUPPORTED_COMMANDS = new Set<string>([
7149
- "text.insert",
7150
- "text.delete-backward",
7151
- "text.delete-forward",
7152
- "text.insert-tab",
7153
- "text.insert-hard-break",
7154
- ]);
7155
-
7156
7299
  function isMutationCommand(command: EditorCommand): boolean {
7157
7300
  return !NON_MUTATION_COMMANDS.has(command.type);
7158
7301
  }
@@ -47,6 +47,17 @@ export function dispatchTextCommand(
47
47
  }]);
48
48
  return;
49
49
  }
50
+ if (
51
+ effectiveSelectionMode === "suggest" &&
52
+ command.type === "split-paragraph" &&
53
+ context.activeStory.kind !== "main"
54
+ ) {
55
+ runtime.emitBlockedCommand(getMountedTextCommandName(command), [{
56
+ code: "suggesting_unsupported",
57
+ message: "Structural suggestions are only supported in the main story.",
58
+ }]);
59
+ return;
60
+ }
50
61
 
51
62
  if (listAwareResult) {
52
63
  deps.dispatchStoryMutationResult(runtime, context, listAwareResult, context.timestamp);