superdoc 2.18.0-next.9 → 2.18.1-next.1

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 (29) hide show
  1. package/dist/chunks/{create-super-doc-ui-qTSa0PbD.es.js → create-super-doc-ui-C_NS0Kbv.es.js} +1003 -911
  2. package/dist/chunks/{create-super-doc-ui-D9JWPXRh.cjs → create-super-doc-ui-DteEHfc7.cjs} +1046 -942
  3. package/dist/collaboration-upgrade-engine.cjs +1 -1
  4. package/dist/collaboration-upgrade-engine.es.js +1 -1
  5. package/dist/document-api/src/diff/diff.types.d.ts +1 -1
  6. package/dist/document-api/src/images/images.types.d.ts +2 -0
  7. package/dist/document-api/src/types/clipboard.d.ts +24 -1
  8. package/dist/layout-engine/contracts/src/index.d.ts +4 -0
  9. package/dist/public/ui-react.cjs +1 -1
  10. package/dist/public/ui-react.es.js +1 -1
  11. package/dist/public/ui-vue.cjs +1 -1
  12. package/dist/public/ui-vue.es.js +1 -1
  13. package/dist/public/ui.cjs +1 -1
  14. package/dist/public/ui.es.js +1 -1
  15. package/dist/style.css +22 -22
  16. package/dist/style.layered.css +22 -22
  17. package/dist/superdoc/src/core/SuperDoc.d.ts +10 -3
  18. package/dist/superdoc/src/core/types/index.d.ts +37 -0
  19. package/dist/superdoc/src/internal/toolbar/built-in/LinkInput.vue.d.ts +4 -4
  20. package/dist/superdoc/src/public/index.d.cts +10 -0
  21. package/dist/superdoc/src/public/index.d.ts +5 -0
  22. package/dist/superdoc/src/public/ui/create-super-doc-ui.d.ts +9 -1
  23. package/dist/superdoc/src/public/ui/types.d.ts +3 -5
  24. package/dist/superdoc.cjs +232 -104
  25. package/dist/superdoc.es.js +232 -104
  26. package/dist-cdn/style.layered.css +1 -1
  27. package/dist-cdn/superdoc.min.css +1 -1
  28. package/dist-cdn/superdoc.min.js +37 -37
  29. package/package.json +2 -2
@@ -510,363 +510,206 @@ var decodeLayoutStoryDataset = (raw) => {
510
510
  `${DOM_CLASS_NAMES.BLOCK_SDT}${DATA_ATTRS.SDT_ID}`;
511
511
  `${DATA_ATTRS.DRAGGABLE}`;
512
512
  //#endregion
513
- //#region ../layout-engine/contracts/src/direction-context.ts
513
+ //#region src/public/ui/equality.ts
514
514
  /**
515
- * Read a paragraph's inline base direction from its attributes.
515
+ * Shallow equality used by the controller to suppress redundant slice
516
+ * notifications. v2-native, dependency-free.
517
+ */
518
+ /**
519
+ * Returns `true` when `a` and `b` are the same reference, or are objects with
520
+ * the same own-enumerable keys and `Object.is`-equal values one level deep.
521
+ * Arrays compare by index. Primitives compare with `Object.is`.
522
+ */
523
+ function shallowEqual(a, b) {
524
+ if (Object.is(a, b)) return true;
525
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
526
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
527
+ const aKeys = Object.keys(a);
528
+ const bKeys = Object.keys(b);
529
+ if (aKeys.length !== bKeys.length) return false;
530
+ for (const key of aKeys) {
531
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
532
+ if (!Object.is(a[key], b[key])) return false;
533
+ }
534
+ return true;
535
+ }
536
+ //#endregion
537
+ //#region src/public/ui/format-painter-helpers.js
538
+ /**
539
+ * Maps SDRunProps field names to InlineRunPatch field names.
540
+ * SDRunProps (read-side) and InlineRunPatch (write-side) use different keys
541
+ * for the same properties; direct assignment between them is forbidden.
516
542
  *
517
- * Prefers the resolved {@link ParagraphDirectionContext} (SD-2776) when
518
- * present. Falls back to `paragraphProperties.rightToLeft` for PM-node /
519
- * editor paths that store direction on the raw OOXML properties rather
520
- * than the typed direction context.
543
+ * @param {Record<string, unknown>} props - SDRunProps object from doc.getNodeById
544
+ * @returns {Record<string, unknown>} InlineRunPatch ready for doc.format.apply
545
+ */
546
+ function sdRunPropsToInlineRunPatch(props) {
547
+ if (!props || typeof props !== "object") return {};
548
+ const patch = {};
549
+ if (props.fonts) patch.rFonts = {
550
+ ascii: props.fonts.ascii,
551
+ hAnsi: props.fonts.hAnsi,
552
+ eastAsia: props.fonts.eastAsia,
553
+ cs: props.fonts.cs,
554
+ asciiTheme: props.fonts.asciiTheme,
555
+ hAnsiTheme: props.fonts.hAnsiTheme,
556
+ eastAsiaTheme: props.fonts.eastAsiaTheme,
557
+ csTheme: props.fonts.csTheme,
558
+ hint: props.fonts.hint
559
+ };
560
+ if (props.fontFamily != null) patch.fontFamily = props.fontFamily;
561
+ if (props.lang) patch.lang = {
562
+ val: props.lang.val,
563
+ eastAsia: props.lang.eastAsia,
564
+ bidi: props.lang.bidi
565
+ };
566
+ if (props.fontSize != null) patch.fontSize = props.fontSize;
567
+ if (props.fontSizeCs != null) patch.fontSizeCs = props.fontSizeCs;
568
+ const color = projectColorRefToInlineColor(props.color);
569
+ if (color != null) patch.color = color;
570
+ if (props.highlight != null) patch.highlight = props.highlight;
571
+ const shading = projectShadingToInlinePatch(props.shading);
572
+ if (shading) patch.shading = shading;
573
+ if (props.cs != null) patch.cs = props.cs;
574
+ if (props.rtl != null) patch.rtl = props.rtl;
575
+ if (props.bold != null) patch.bold = props.bold;
576
+ if (props.boldCs != null) patch.bCs = props.boldCs;
577
+ if (props.italic != null) patch.italic = props.italic;
578
+ if (props.italicCs != null) patch.iCs = props.italicCs;
579
+ const underline = projectUnderlineToInlinePatch(props.underline);
580
+ if (underline !== void 0) patch.underline = underline;
581
+ if (props.strikethrough != null) patch.strike = props.strikethrough;
582
+ if (props.doubleStrikethrough != null) patch.dstrike = props.doubleStrikethrough;
583
+ if (props.caps != null) patch.caps = props.caps;
584
+ if (props.smallCaps != null) patch.smallCaps = props.smallCaps;
585
+ if (props.outline != null) patch.outline = props.outline;
586
+ if (props.shadow != null) patch.shadow = props.shadow;
587
+ if (props.emboss != null) patch.emboss = props.emboss;
588
+ if (props.imprint != null) patch.imprint = props.imprint;
589
+ if (props.verticalAlign != null) patch.vertAlign = props.verticalAlign;
590
+ if (props.characterSpacing != null) patch.letterSpacing = props.characterSpacing;
591
+ if (props.characterScale != null) patch.charScale = props.characterScale;
592
+ if (props.kern != null) patch.kerning = props.kern;
593
+ if (props.baselineShift != null) patch.position = props.baselineShift;
594
+ if (props.fitTextWidth != null) patch.fitText = { val: props.fitTextWidth };
595
+ if (props.vanish != null) patch.vanish = props.vanish;
596
+ if (props.webHidden != null) patch.webHidden = props.webHidden;
597
+ if (props.specVanish != null) patch.specVanish = props.specVanish;
598
+ const border = projectBorderToInlinePatch(props.border);
599
+ if (border) patch.border = border;
600
+ return patch;
601
+ }
602
+ function projectColorRefToInlineColor(color) {
603
+ if (!color) return void 0;
604
+ if (typeof color === "string") return color;
605
+ if (color.model === "rgb") return color.value;
606
+ if (color.model === "auto") return "auto";
607
+ }
608
+ function projectUnderlineToInlinePatch(underline) {
609
+ if (underline == null) return void 0;
610
+ const patch = {};
611
+ if (underline.style != null) patch.style = underline.style;
612
+ if (underline.color?.model === "rgb") patch.color = underline.color.value;
613
+ if (underline.color?.model === "theme") patch.themeColor = underline.color.theme;
614
+ return Object.keys(patch).length > 0 ? patch : true;
615
+ }
616
+ function projectShadingToInlinePatch(shading) {
617
+ if (!shading) return void 0;
618
+ const patch = {};
619
+ const fill = projectColorRefToInlineColor(shading.fill);
620
+ if (fill != null) patch.fill = fill;
621
+ const color = projectColorRefToInlineColor(shading.color);
622
+ if (color != null) patch.color = color;
623
+ if (shading.pattern != null) patch.val = shading.pattern;
624
+ return Object.keys(patch).length > 0 ? patch : void 0;
625
+ }
626
+ function projectBorderToInlinePatch(border) {
627
+ if (!border) return void 0;
628
+ const patch = {};
629
+ if (border.style != null) patch.val = border.style;
630
+ if (border.width != null) patch.sz = border.width;
631
+ if (border.space != null) patch.space = border.space;
632
+ const color = projectColorRefToInlineColor(border.color);
633
+ if (color != null) patch.color = color;
634
+ return Object.keys(patch).length > 0 ? patch : void 0;
635
+ }
636
+ /**
637
+ * Serializes a selection to a stable string key used to detect whether
638
+ * the target selection is the same as the source (skip apply in that case).
521
639
  *
522
- * Consumers should call this instead of inspecting attrs ad hoc so the
523
- * direction source check stays in one place.
640
+ * Falls back to the plain text target when the host has not populated an
641
+ * explicit `selectionTarget` yet.
642
+ *
643
+ * @param {{ selectionTarget?: unknown; target?: unknown } | null | undefined} selection
644
+ * @returns {string}
524
645
  */
525
- function getParagraphInlineDirection(attrs) {
526
- const fromContext = attrs?.directionContext?.inlineDirection;
527
- if (fromContext != null) return fromContext;
528
- const ppRtl = attrs?.paragraphProperties?.rightToLeft;
529
- if (ppRtl === true) return "rtl";
530
- if (ppRtl === false) return "ltr";
646
+ function selectionKey(selection) {
647
+ const target = selection?.selectionTarget ?? selection?.target;
648
+ if (!selection || !target) return "";
649
+ try {
650
+ return JSON.stringify(target);
651
+ } catch {
652
+ return String(target);
653
+ }
531
654
  }
532
655
  //#endregion
533
- //#region ../layout-engine/contracts/src/drawing-taxonomy.ts
656
+ //#region src/helpers/v2-review-mutation-impact.js
534
657
  /**
535
- * Frozen canonical diagnostic codes for drawing rendering.
658
+ * Classify the tracked-change identities carried by a v2 mutation event.
536
659
  *
537
- * New extractor/adapter diagnostics MUST use these exact strings. Family-
538
- * specific codes are required — do not collapse everything to
539
- * `render.unsupported-inline-ooxml` (plan §4).
660
+ * Receipt entities are the durable invalidation contract between the document
661
+ * kernel and derived review UI. Direct receipts carry entity refs; mutation
662
+ * plans carry the same identities through `trackedChanges` and per-step ids.
540
663
  */
541
- var DRAWING_DIAGNOSTIC_CODES = {
542
- /** External image relationship (`r:link` / `TargetMode="External"`). Not fetched. */
543
- externalImageDeferred: "render.media.external-image-deferred",
544
- /** Drawing references a relationship id that does not exist on the owner part. */
545
- missingRelationship: "render.drawing.missing-relationship",
546
- /** Relationship resolves but the target media part bytes are missing/empty. */
547
- missingMediaPart: "render.media.missing-part",
548
- /** Relationship exists but is not an image relationship type. */
549
- unsupportedRelationshipType: "render.drawing.unsupported-relationship-type",
550
- /** Media MIME is not in the supported allowlist. */
551
- unsupportedMime: "render.media.unsupported-mime",
552
- /** Media part exceeds the host byte-size policy before bytes are materialized. */
553
- imageTooLarge: "render.media.image-too-large",
554
- /** SVG content rejected by the SVG safety policy. */
555
- unsafeSvg: "render.media.unsafe-svg",
556
- /** Metafile / TIFF (EMF, WMF, TIFF) with no approved conversion path. */
557
- unsupportedFormat: "render.media.unsupported-format",
558
- /** OLE / ActiveX / embedded package object. */
559
- embeddedObjectNotSupported: "render.embedded-object-not-supported",
560
- /** SmartArt / diagram / unknown external graphic-data object. */
561
- unsupportedObject: "render.drawing.unsupported-object",
562
- /** VML structure outside any supported image-like subset. */
563
- vmlUnsupported: "render.drawing.vml-unsupported",
564
- /** VML image-like content that could not be promoted to a supported image. */
565
- vmlImageUnsupported: "render.drawing.vml-image-unsupported",
566
- /** Custom geometry command the extractor does not implement (e.g. `arcTo`). */
567
- unsupportedGeometryCommand: "render.drawing.unsupported-geometry-command",
568
- /** Anchor fields required for honest placement are unsupported. */
569
- anchorUnsupported: "render.drawing.anchor-unsupported",
570
- /** Wrap fields required for honest placement are unsupported. */
571
- wrapUnsupported: "render.drawing.wrap-unsupported",
572
- /** `mc:AlternateContent` had no supported choice and no usable fallback. */
573
- altContentNoSupportedChoice: "render.drawing.altcontent-no-supported-choice",
574
- /** A child of an otherwise-supported group is unsupported. */
575
- groupChildUnsupported: "render.drawing.group-child-unsupported"
576
- };
577
- DRAWING_DIAGNOSTIC_CODES.missingRelationship, DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType, DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType, DRAWING_DIAGNOSTIC_CODES.imageTooLarge, DRAWING_DIAGNOSTIC_CODES.missingMediaPart, DRAWING_DIAGNOSTIC_CODES.unsupportedObject, DRAWING_DIAGNOSTIC_CODES.vmlUnsupported;
664
+ function getV2TrackedChangeMutationImpact(event) {
665
+ if (event?.type !== "mutation:committed") return null;
666
+ const payload = event.origin === "history" ? event.result : event.receipt;
667
+ if (!payload || typeof payload !== "object") return null;
668
+ const upsertIds = /* @__PURE__ */ new Set();
669
+ const removedIds = /* @__PURE__ */ new Set();
670
+ /** @type {Array<{ from: string, to: string }>} */
671
+ const remappedPairs = [];
672
+ const collectEntity = (entry, into) => {
673
+ if (entry?.kind !== "entity" || entry.entityType !== "trackedChange") return;
674
+ if (typeof entry.entityId === "string" && entry.entityId.length > 0) into.add(entry.entityId);
675
+ };
676
+ const readEntityId = (entry) => {
677
+ if (entry?.kind !== "entity" || entry.entityType !== "trackedChange") return null;
678
+ return typeof entry.entityId === "string" && entry.entityId.length > 0 ? entry.entityId : null;
679
+ };
680
+ if (Array.isArray(payload.inserted)) payload.inserted.forEach((entry) => collectEntity(entry, upsertIds));
681
+ if (Array.isArray(payload.updated)) payload.updated.forEach((entry) => collectEntity(entry, upsertIds));
682
+ if (Array.isArray(payload.trackedChangeRefs)) payload.trackedChangeRefs.forEach((entry) => collectEntity(entry, upsertIds));
683
+ if (Array.isArray(payload.trackedChanges)) payload.trackedChanges.forEach((entry) => collectEntity(entry, upsertIds));
684
+ if (Array.isArray(payload.steps)) for (const step of payload.steps) {
685
+ if (!Array.isArray(step?.trackedChangeIds)) continue;
686
+ for (const id of step.trackedChangeIds) if (typeof id === "string" && id.length > 0) upsertIds.add(id);
687
+ }
688
+ if (Array.isArray(payload.removed)) payload.removed.forEach((entry) => collectEntity(entry, removedIds));
689
+ if (Array.isArray(payload.invalidatedRefs)) payload.invalidatedRefs.forEach((entry) => collectEntity(entry, removedIds));
690
+ if (Array.isArray(payload.remappedRefs)) for (const mapping of payload.remappedRefs) {
691
+ const fromId = readEntityId(mapping?.from);
692
+ const toId = readEntityId(mapping?.to);
693
+ if (fromId) removedIds.add(fromId);
694
+ if (toId) upsertIds.add(toId);
695
+ if (fromId && toId && fromId !== toId) remappedPairs.push({
696
+ from: fromId,
697
+ to: toId
698
+ });
699
+ }
700
+ for (const id of removedIds) upsertIds.delete(id);
701
+ const allResolved = readAllResolvedFact(event, payload);
702
+ if (upsertIds.size === 0 && removedIds.size === 0 && !allResolved) return null;
703
+ return {
704
+ upsertIds,
705
+ removedIds,
706
+ remappedPairs,
707
+ reconcileMode: "targeted",
708
+ ...allResolved ? { allResolved } : {}
709
+ };
710
+ }
578
711
  /**
579
- * The frozen drawing support taxonomy.
580
- *
581
- * Invariants (enforced by `drawing-taxonomy.test.ts`):
582
- * - every `supported` family has a real contract target (`!== 'none'`);
583
- * - every `DrawingBlock`-targeted family declares a `drawingKind`;
584
- * - every `fail-closed` family declares a canonical `diagnostic`;
585
- * - every canonical diagnostic code is referenced by at least one fail-closed
586
- * family;
587
- * - `deferred` families make no support claim (`contract: 'none'`, no
588
- * diagnostic).
589
- */
590
- var DRAWING_SUPPORT_TAXONOMY = {
591
- inlineBitmap: {
592
- family: "inlineBitmap",
593
- support: "supported",
594
- description: "wp:inline pic:pic with internal media and browser-safe MIME (PNG/JPEG; WebP/SVG once the host media resolver lands).",
595
- contract: "ImageRun"
596
- },
597
- anchoredBitmap: {
598
- family: "anchoredBitmap",
599
- support: "supported",
600
- description: "wp:anchor bitmap with anchor/wrap expressible by ImageAnchor/ImageWrap. Never silently inlined.",
601
- contract: "ImageBlock"
602
- },
603
- imageChildInGroup: {
604
- family: "imageChildInGroup",
605
- support: "supported",
606
- description: "Bitmap child of a supported DrawingML group/drawing wrapper. Supported only when the parent is.",
607
- contract: "ImageDrawing"
608
- },
609
- vectorShape: {
610
- family: "vectorShape",
611
- support: "supported",
612
- description: "Block/floating DrawingML preset/custom shape with the supported geometry/style subset.",
613
- contract: "DrawingBlock",
614
- drawingKind: "vectorShape"
615
- },
616
- shapeGroup: {
617
- family: "shapeGroup",
618
- support: "supported",
619
- description: "DrawingML group whose rendered children are supported vector/image children.",
620
- contract: "DrawingBlock",
621
- drawingKind: "shapeGroup"
622
- },
623
- chart: {
624
- family: "chart",
625
- support: "supported",
626
- description: "DrawingML chart projected into a ChartDrawing with parsed cached chart XML data. Complete Word chart fidelity remains deferred to chartFidelity.",
627
- contract: "DrawingBlock",
628
- drawingKind: "chart"
629
- },
630
- alternateContent: {
631
- family: "alternateContent",
632
- support: "supported",
633
- description: "mc:AlternateContent traversal/select policy. Selected Choice/Fallback maps to its family contract; unselected branches preserved for save/reopen.",
634
- contract: "selected-choice"
635
- },
636
- externalImage: {
637
- family: "externalImage",
638
- support: "fail-closed",
639
- description: "External image relationship (r:link / TargetMode=\"External\"). Not fetched.",
640
- contract: "none",
641
- diagnostic: DRAWING_DIAGNOSTIC_CODES.externalImageDeferred
642
- },
643
- missingRelationship: {
644
- family: "missingRelationship",
645
- support: "fail-closed",
646
- description: "Drawing references a relationship id absent from the owner part .rels.",
647
- contract: "none",
648
- diagnostic: DRAWING_DIAGNOSTIC_CODES.missingRelationship
649
- },
650
- missingMediaPart: {
651
- family: "missingMediaPart",
652
- support: "fail-closed",
653
- description: "Relationship resolves but the target media part bytes are missing or empty.",
654
- contract: "none",
655
- diagnostic: DRAWING_DIAGNOSTIC_CODES.missingMediaPart
656
- },
657
- wrongRelationshipType: {
658
- family: "wrongRelationshipType",
659
- support: "fail-closed",
660
- description: "Relationship exists but is not an image relationship type.",
661
- contract: "none",
662
- diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType
663
- },
664
- unsupportedMime: {
665
- family: "unsupportedMime",
666
- support: "fail-closed",
667
- description: "Media MIME outside the supported allowlist (incl. GIF/BMP/ICO).",
668
- contract: "none",
669
- diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedMime
670
- },
671
- oversizedMediaPart: {
672
- family: "oversizedMediaPart",
673
- support: "fail-closed",
674
- description: "Media part exceeds the host byte-size policy before bytes are materialized.",
675
- contract: "none",
676
- diagnostic: DRAWING_DIAGNOSTIC_CODES.imageTooLarge
677
- },
678
- unsafeSvg: {
679
- family: "unsafeSvg",
680
- support: "fail-closed",
681
- description: "SVG content rejected by the SVG safety policy.",
682
- contract: "none",
683
- diagnostic: DRAWING_DIAGNOSTIC_CODES.unsafeSvg
684
- },
685
- metafileOrTiff: {
686
- family: "metafileOrTiff",
687
- support: "fail-closed",
688
- description: "EMF/WMF/TIFF and other non-browser-native formats with no approved conversion path.",
689
- contract: "none",
690
- diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedFormat
691
- },
692
- embeddedObject: {
693
- family: "embeddedObject",
694
- support: "fail-closed",
695
- description: "OLE / ActiveX / embedded package object.",
696
- contract: "none",
697
- diagnostic: DRAWING_DIAGNOSTIC_CODES.embeddedObjectNotSupported
698
- },
699
- smartArtOrDiagram: {
700
- family: "smartArtOrDiagram",
701
- support: "fail-closed",
702
- description: "SmartArt / diagram / unknown external graphic-data object.",
703
- contract: "none",
704
- diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedObject
705
- },
706
- vml: {
707
- family: "vml",
708
- support: "fail-closed",
709
- description: "VML structure outside any supported image-like subset (default fail-closed policy).",
710
- contract: "none",
711
- diagnostic: DRAWING_DIAGNOSTIC_CODES.vmlUnsupported
712
- },
713
- vmlImageLike: {
714
- family: "vmlImageLike",
715
- support: "fail-closed",
716
- description: "Simple VML image references (v:imagedata) / watermark image metadata. Fail-closed unless a later change promotes a named subset with deterministic fixtures and exact oracles.",
717
- contract: "none",
718
- diagnostic: DRAWING_DIAGNOSTIC_CODES.vmlImageUnsupported
719
- },
720
- unsupportedGeometryCommand: {
721
- family: "unsupportedGeometryCommand",
722
- support: "fail-closed",
723
- description: "Custom geometry command the extractor does not implement, e.g. arcTo.",
724
- contract: "none",
725
- diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedGeometryCommand
726
- },
727
- unsupportedAnchorFields: {
728
- family: "unsupportedAnchorFields",
729
- support: "fail-closed",
730
- description: "Anchor fields required for honest placement are unsupported (char-relative, alignment modes, etc.).",
731
- contract: "none",
732
- diagnostic: DRAWING_DIAGNOSTIC_CODES.anchorUnsupported
733
- },
734
- unsupportedWrapFields: {
735
- family: "unsupportedWrapFields",
736
- support: "fail-closed",
737
- description: "Wrap fields required for honest placement are unsupported (arbitrary wrap polygons, etc.).",
738
- contract: "none",
739
- diagnostic: DRAWING_DIAGNOSTIC_CODES.wrapUnsupported
740
- },
741
- alternateContentNoSupportedChoice: {
742
- family: "alternateContentNoSupportedChoice",
743
- support: "fail-closed",
744
- description: "mc:AlternateContent with no supported choice and no usable fallback.",
745
- contract: "none",
746
- diagnostic: DRAWING_DIAGNOSTIC_CODES.altContentNoSupportedChoice
747
- },
748
- groupChildUnsupported: {
749
- family: "groupChildUnsupported",
750
- support: "fail-closed",
751
- description: "Unsupported child inside an otherwise-supported group. Supported siblings are not dropped.",
752
- contract: "none",
753
- diagnostic: DRAWING_DIAGNOSTIC_CODES.groupChildUnsupported
754
- },
755
- objectEditing: {
756
- family: "objectEditing",
757
- support: "deferred",
758
- description: "Editing/mutation of anchored/vector objects.",
759
- contract: "none"
760
- },
761
- customWrapPolygon: {
762
- family: "customWrapPolygon",
763
- support: "deferred",
764
- description: "Full Word-compatible text wrapping for arbitrary custom wrap polygons.",
765
- contract: "none"
766
- },
767
- chartFidelity: {
768
- family: "chartFidelity",
769
- support: "deferred",
770
- description: "Complete chart fidelity (a dedicated chart effort owns the parsed subset).",
771
- contract: "none"
772
- },
773
- vmlFidelity: {
774
- family: "vmlFidelity",
775
- support: "deferred",
776
- description: "Complete VML fidelity.",
777
- contract: "none"
778
- },
779
- decorativeAccessibilityUi: {
780
- family: "decorativeAccessibilityUi",
781
- support: "deferred",
782
- description: "Full accessibility UI for decorative image semantics beyond preserving source data.",
783
- contract: "none"
784
- }
785
- };
786
- Object.keys(DRAWING_SUPPORT_TAXONOMY);
787
- Object.freeze([
788
- "multiple-sections",
789
- "furniture-page-tokens",
790
- "non-balanceable-multi-column-sections",
791
- "body-anchored-objects",
792
- "non-flowing-page-relative-body-anchors",
793
- "footnotes",
794
- "page-references",
795
- "cross-references",
796
- "keep-constraints",
797
- "tables",
798
- "furniture-anchored-objects"
799
- ]);
800
- var INCREMENTAL_DEPENDENCY_CERTIFICATE_CLASSES = Object.freeze([
801
- "checkpoint-geometry",
802
- "section-numbering",
803
- "font-style-inputs",
804
- "notes",
805
- "furniture",
806
- "flowing-anchors",
807
- "non-flowing-page-anchors",
808
- "convergence",
809
- "source-topology"
810
- ]);
811
- new Set(INCREMENTAL_DEPENDENCY_CERTIFICATE_CLASSES);
812
- //#endregion
813
- //#region src/helpers/v2-review-mutation-impact.js
814
- /**
815
- * Classify the tracked-change identities carried by a v2 mutation event.
816
- *
817
- * Receipt entities are the durable invalidation contract between the document
818
- * kernel and derived review UI. Direct receipts carry entity refs; mutation
819
- * plans carry the same identities through `trackedChanges` and per-step ids.
820
- */
821
- function getV2TrackedChangeMutationImpact(event) {
822
- if (event?.type !== "mutation:committed") return null;
823
- const payload = event.origin === "history" ? event.result : event.receipt;
824
- if (!payload || typeof payload !== "object") return null;
825
- const upsertIds = /* @__PURE__ */ new Set();
826
- const removedIds = /* @__PURE__ */ new Set();
827
- /** @type {Array<{ from: string, to: string }>} */
828
- const remappedPairs = [];
829
- const collectEntity = (entry, into) => {
830
- if (entry?.kind !== "entity" || entry.entityType !== "trackedChange") return;
831
- if (typeof entry.entityId === "string" && entry.entityId.length > 0) into.add(entry.entityId);
832
- };
833
- const readEntityId = (entry) => {
834
- if (entry?.kind !== "entity" || entry.entityType !== "trackedChange") return null;
835
- return typeof entry.entityId === "string" && entry.entityId.length > 0 ? entry.entityId : null;
836
- };
837
- if (Array.isArray(payload.inserted)) payload.inserted.forEach((entry) => collectEntity(entry, upsertIds));
838
- if (Array.isArray(payload.updated)) payload.updated.forEach((entry) => collectEntity(entry, upsertIds));
839
- if (Array.isArray(payload.trackedChangeRefs)) payload.trackedChangeRefs.forEach((entry) => collectEntity(entry, upsertIds));
840
- if (Array.isArray(payload.trackedChanges)) payload.trackedChanges.forEach((entry) => collectEntity(entry, upsertIds));
841
- if (Array.isArray(payload.steps)) for (const step of payload.steps) {
842
- if (!Array.isArray(step?.trackedChangeIds)) continue;
843
- for (const id of step.trackedChangeIds) if (typeof id === "string" && id.length > 0) upsertIds.add(id);
844
- }
845
- if (Array.isArray(payload.removed)) payload.removed.forEach((entry) => collectEntity(entry, removedIds));
846
- if (Array.isArray(payload.invalidatedRefs)) payload.invalidatedRefs.forEach((entry) => collectEntity(entry, removedIds));
847
- if (Array.isArray(payload.remappedRefs)) for (const mapping of payload.remappedRefs) {
848
- const fromId = readEntityId(mapping?.from);
849
- const toId = readEntityId(mapping?.to);
850
- if (fromId) removedIds.add(fromId);
851
- if (toId) upsertIds.add(toId);
852
- if (fromId && toId && fromId !== toId) remappedPairs.push({
853
- from: fromId,
854
- to: toId
855
- });
856
- }
857
- for (const id of removedIds) upsertIds.delete(id);
858
- const allResolved = readAllResolvedFact(event, payload);
859
- if (upsertIds.size === 0 && removedIds.size === 0 && !allResolved) return null;
860
- return {
861
- upsertIds,
862
- removedIds,
863
- remappedPairs,
864
- reconcileMode: "targeted",
865
- ...allResolved ? { allResolved } : {}
866
- };
867
- }
868
- /**
869
- * Comment ids retired by a committed v2 mutation.
712
+ * Comment ids retired by a committed v2 mutation.
870
713
  *
871
714
  * AIDEV-NOTE: History commits put invalidated comment ids on `result`; cut and
872
715
  * other command receipts use `receipt`. Same origin split as
@@ -903,6 +746,56 @@ function readAllResolvedFact(event, receipt) {
903
746
  return fact;
904
747
  }
905
748
  //#endregion
749
+ //#region src/helpers/v2-typing-mutation-event.js
750
+ /**
751
+ * Command kinds whose committed mutations belong to the sustained typing
752
+ * cadence class (insert / replace / plain paste / Backspace / Delete).
753
+ *
754
+ * The SuperDoc shell debounces per-keystroke review-row hydration for these
755
+ * kinds instead of refreshing comments + tracked changes on every commit —
756
+ * hydrating immediately per keystroke was a measured worker-read storm during
757
+ * typing and backspace bursts. The `text.*` aliases cover integrations that
758
+ * forward Document API style command ids rather than editable-input kinds.
759
+ */
760
+ var V2_TYPING_COMMAND_KINDS = /* @__PURE__ */ new Set([
761
+ "insert-text",
762
+ "replace-text",
763
+ "plain-text-paste",
764
+ "delete-backward",
765
+ "delete-forward",
766
+ "text.insert",
767
+ "text.replace",
768
+ "text.pastePlain",
769
+ "text.deleteBackward",
770
+ "text.deleteForward",
771
+ "structural.enter",
772
+ "structural:enter-split-paragraph",
773
+ "structural:enter-insert-paragraph-before",
774
+ "structural:enter-insert-paragraph-after",
775
+ "structural:shift-enter-line-break",
776
+ "structural:backspace-boundary-merge-with-previous",
777
+ "structural:delete-boundary-merge-with-next",
778
+ "backspace:boundary-merge-with-previous",
779
+ "backspace:list-remove",
780
+ "backspace:list-outdent"
781
+ ]);
782
+ /**
783
+ * True when a forwarded v2 host event is a committed editable-input mutation
784
+ * of the sustained-typing class, i.e. review-row hydration for it should be
785
+ * debounced rather than fired immediately.
786
+ *
787
+ * Events without a forwarded `editableCommandKind` (programmatic Document API
788
+ * mutations, history commits, unclassified structural commands) return false
789
+ * so they keep hydrating immediately.
790
+ *
791
+ * @param {{ type?: string, origin?: string, editableCommandKind?: string } | null | undefined} event
792
+ * @returns {boolean}
793
+ */
794
+ var isV2EditableTextMutationEvent = (event) => {
795
+ if (event?.type !== "mutation:committed" || event.origin !== "command") return false;
796
+ return V2_TYPING_COMMAND_KINDS.has(event.editableCommandKind);
797
+ };
798
+ //#endregion
906
799
  //#region src/public/ui/reasons.ts
907
800
  /**
908
801
  * Stable public reason taxonomy for the v2-native `superdoc/ui` controller.
@@ -1433,600 +1326,714 @@ var COMMAND_CATALOG = [
1433
1326
  normalizePayload: normalizeColorPayload
1434
1327
  },
1435
1328
  {
1436
- id: "link",
1437
- family: "links",
1329
+ id: "link",
1330
+ family: "links",
1331
+ disposition: "routed",
1332
+ mutates: true,
1333
+ docRoute: "hyperlinks.wrap",
1334
+ activeMark: "link",
1335
+ link: { kind: "link" }
1336
+ },
1337
+ {
1338
+ id: "text-align",
1339
+ family: "paragraph",
1340
+ disposition: "routed",
1341
+ mutates: true,
1342
+ docRoute: "format.paragraph.setAlignment",
1343
+ blockParagraph: { kind: "alignment" },
1344
+ normalizePayload: normalizeAlignmentPayload
1345
+ },
1346
+ {
1347
+ id: "line-height",
1348
+ family: "paragraph",
1349
+ disposition: "routed",
1350
+ mutates: true,
1351
+ docRoute: "format.paragraph.setSpacing",
1352
+ blockParagraph: { kind: "spacing-line" },
1353
+ normalizePayload: normalizeLineHeightPayload
1354
+ },
1355
+ {
1356
+ id: "linked-style",
1357
+ family: "paragraph",
1358
+ disposition: "routed",
1359
+ mutates: true,
1360
+ docRoute: "styles.paragraph.setStyle",
1361
+ blockParagraph: { kind: "style" },
1362
+ normalizePayload: normalizeStyleIdPayload
1363
+ },
1364
+ {
1365
+ id: "bullet-list",
1366
+ family: "lists",
1367
+ disposition: "routed",
1368
+ mutates: true,
1369
+ docRoute: "lists.apply",
1370
+ list: {
1371
+ mode: "toggle-seed",
1372
+ seed: "bullet"
1373
+ }
1374
+ },
1375
+ {
1376
+ id: "numbered-list",
1377
+ family: "lists",
1378
+ disposition: "routed",
1379
+ mutates: true,
1380
+ docRoute: "lists.apply",
1381
+ list: {
1382
+ mode: "toggle-seed",
1383
+ seed: "ordered"
1384
+ }
1385
+ },
1386
+ {
1387
+ id: "indent-increase",
1388
+ family: "lists/indent",
1389
+ disposition: "routed",
1390
+ mutates: true,
1391
+ docRoute: "lists.indent",
1392
+ list: { mode: "indent" }
1393
+ },
1394
+ {
1395
+ id: "indent-decrease",
1396
+ family: "lists/indent",
1397
+ disposition: "routed",
1398
+ mutates: true,
1399
+ docRoute: "lists.outdent",
1400
+ list: { mode: "outdent" }
1401
+ },
1402
+ {
1403
+ id: "direction-ltr",
1404
+ family: "direction",
1405
+ disposition: "routed",
1406
+ mutates: true,
1407
+ docRoute: "format.paragraph.setDirection",
1408
+ blockParagraph: {
1409
+ kind: "direction",
1410
+ fixedValue: "ltr"
1411
+ }
1412
+ },
1413
+ {
1414
+ id: "direction-rtl",
1415
+ family: "direction",
1416
+ disposition: "routed",
1417
+ mutates: true,
1418
+ docRoute: "format.paragraph.setDirection",
1419
+ blockParagraph: {
1420
+ kind: "direction",
1421
+ fixedValue: "rtl"
1422
+ }
1423
+ },
1424
+ {
1425
+ id: "clear-formatting",
1426
+ family: "formatting",
1427
+ disposition: "routed",
1428
+ mutates: true,
1429
+ docRoute: "format.apply",
1430
+ inline: {
1431
+ key: "*",
1432
+ kind: "clear"
1433
+ }
1434
+ },
1435
+ {
1436
+ id: "image",
1437
+ family: "media",
1438
+ disposition: "routed",
1439
+ mutates: true,
1440
+ docRoute: "create.image",
1441
+ create: { kind: "image" }
1442
+ },
1443
+ {
1444
+ id: "table-of-contents-insert",
1445
+ family: "TOC",
1446
+ disposition: "routed",
1447
+ mutates: true,
1448
+ docRoute: "create.tableOfContents",
1449
+ create: { kind: "toc" }
1450
+ },
1451
+ {
1452
+ id: "table-insert",
1453
+ family: "tables",
1454
+ disposition: "routed",
1455
+ mutates: true,
1456
+ docRoute: "create.table",
1457
+ create: { kind: "table" }
1458
+ },
1459
+ {
1460
+ id: "table-add-row-before",
1461
+ family: "tables",
1462
+ disposition: "routed",
1463
+ mutates: true,
1464
+ docRoute: "tables.insertRow",
1465
+ table: {
1466
+ action: "insert-row-before",
1467
+ op: "insertRow"
1468
+ },
1469
+ reason: TABLE_CONTEXT
1470
+ },
1471
+ {
1472
+ id: "table-add-row-after",
1473
+ family: "tables",
1474
+ disposition: "routed",
1475
+ mutates: true,
1476
+ docRoute: "tables.insertRow",
1477
+ table: {
1478
+ action: "insert-row-after",
1479
+ op: "insertRow"
1480
+ },
1481
+ reason: TABLE_CONTEXT
1482
+ },
1483
+ {
1484
+ id: "table-delete-row",
1485
+ family: "tables",
1486
+ disposition: "routed",
1487
+ mutates: true,
1488
+ docRoute: "tables.deleteRow",
1489
+ table: {
1490
+ action: "delete-row",
1491
+ op: "deleteRow"
1492
+ },
1493
+ reason: TABLE_CONTEXT
1494
+ },
1495
+ {
1496
+ id: "table-add-column-before",
1497
+ family: "tables",
1498
+ disposition: "routed",
1499
+ mutates: true,
1500
+ docRoute: "tables.insertColumn",
1501
+ table: {
1502
+ action: "insert-column-before",
1503
+ op: "insertColumn"
1504
+ },
1505
+ reason: TABLE_CONTEXT
1506
+ },
1507
+ {
1508
+ id: "table-add-column-after",
1509
+ family: "tables",
1510
+ disposition: "routed",
1511
+ mutates: true,
1512
+ docRoute: "tables.insertColumn",
1513
+ table: {
1514
+ action: "insert-column-after",
1515
+ op: "insertColumn"
1516
+ },
1517
+ reason: TABLE_CONTEXT
1518
+ },
1519
+ {
1520
+ id: "table-delete-column",
1521
+ family: "tables",
1438
1522
  disposition: "routed",
1439
1523
  mutates: true,
1440
- docRoute: "hyperlinks.wrap",
1441
- activeMark: "link",
1442
- link: { kind: "link" }
1524
+ docRoute: "tables.deleteColumn",
1525
+ table: {
1526
+ action: "delete-column",
1527
+ op: "deleteColumn"
1528
+ },
1529
+ reason: TABLE_CONTEXT
1443
1530
  },
1444
1531
  {
1445
- id: "text-align",
1446
- family: "paragraph",
1532
+ id: "table-delete",
1533
+ family: "tables",
1447
1534
  disposition: "routed",
1448
1535
  mutates: true,
1449
- docRoute: "format.paragraph.setAlignment",
1450
- blockParagraph: { kind: "alignment" },
1451
- normalizePayload: normalizeAlignmentPayload
1536
+ docRoute: "tables.delete",
1537
+ table: {
1538
+ action: "delete-table",
1539
+ op: "delete"
1540
+ },
1541
+ reason: TABLE_CONTEXT
1452
1542
  },
1453
1543
  {
1454
- id: "line-height",
1455
- family: "paragraph",
1544
+ id: "table-merge-cells",
1545
+ family: "tables",
1456
1546
  disposition: "routed",
1457
1547
  mutates: true,
1458
- docRoute: "format.paragraph.setSpacing",
1459
- blockParagraph: { kind: "spacing-line" },
1460
- normalizePayload: normalizeLineHeightPayload
1548
+ docRoute: "tables.mergeCells",
1549
+ table: {
1550
+ action: "merge-cells",
1551
+ op: "mergeCells"
1552
+ },
1553
+ reason: TABLE_CONTEXT
1461
1554
  },
1462
1555
  {
1463
- id: "linked-style",
1464
- family: "paragraph",
1556
+ id: "table-split-cell",
1557
+ family: "tables",
1465
1558
  disposition: "routed",
1466
1559
  mutates: true,
1467
- docRoute: "styles.paragraph.setStyle",
1468
- blockParagraph: { kind: "style" },
1469
- normalizePayload: normalizeStyleIdPayload
1560
+ docRoute: "tables.splitCell",
1561
+ table: {
1562
+ action: "split-cell",
1563
+ op: "splitCell",
1564
+ requiresCell: true
1565
+ },
1566
+ reason: TABLE_CONTEXT
1470
1567
  },
1471
1568
  {
1472
- id: "bullet-list",
1473
- family: "lists",
1569
+ id: "table-remove-borders",
1570
+ family: "tables",
1474
1571
  disposition: "routed",
1475
1572
  mutates: true,
1476
- docRoute: "lists.apply",
1477
- list: {
1478
- mode: "toggle-seed",
1479
- seed: "bullet"
1480
- }
1573
+ docRoute: "tables.setBorders",
1574
+ table: {
1575
+ action: "remove-borders",
1576
+ op: "setBorders"
1577
+ },
1578
+ reason: TABLE_CONTEXT
1481
1579
  },
1482
1580
  {
1483
- id: "numbered-list",
1484
- family: "lists",
1581
+ id: "ruler",
1582
+ family: "UI chrome",
1485
1583
  disposition: "routed",
1486
- mutates: true,
1487
- docRoute: "lists.apply",
1488
- list: {
1489
- mode: "toggle-seed",
1490
- seed: "ordered"
1491
- }
1584
+ mutates: false,
1585
+ instanceRoute: "toggleRuler",
1586
+ valueFrom: "ruler"
1492
1587
  },
1493
1588
  {
1494
- id: "indent-increase",
1495
- family: "lists/indent",
1589
+ id: "formatting-marks",
1590
+ family: "UI chrome",
1496
1591
  disposition: "routed",
1497
- mutates: true,
1498
- docRoute: "lists.indent",
1499
- list: { mode: "indent" }
1592
+ mutates: false,
1593
+ instanceRoute: "toggleFormattingMarks",
1594
+ valueFrom: "formattingMarks"
1500
1595
  },
1501
1596
  {
1502
- id: "indent-decrease",
1503
- family: "lists/indent",
1597
+ id: "copy-format",
1598
+ family: "formatting",
1504
1599
  disposition: "routed",
1600
+ mutates: false
1601
+ },
1602
+ {
1603
+ id: "table-fix",
1604
+ family: "tables",
1605
+ disposition: "unsupported",
1505
1606
  mutates: true,
1506
- docRoute: "lists.outdent",
1507
- list: { mode: "outdent" }
1607
+ reason: UNSUPPORTED
1608
+ }
1609
+ ];
1610
+ var CATALOG_BY_ID = new Map(COMMAND_CATALOG.map((descriptor) => [descriptor.id, descriptor]));
1611
+ /** All built-in command ids known to the controller (routed + deferred + unsupported). */
1612
+ var ALL_BUILT_IN_COMMAND_IDS = COMMAND_CATALOG.map((d) => d.id);
1613
+ /** Resolve a command descriptor by id, or `null` when the id is unknown. */
1614
+ function getCommandDescriptor(id) {
1615
+ return CATALOG_BY_ID.get(id) ?? null;
1616
+ }
1617
+ //#endregion
1618
+ //#region src/public/ui/entity-at.ts
1619
+ function parseCommaSeparatedIds(value) {
1620
+ return value.split(",").map((id) => id.trim()).filter(Boolean);
1621
+ }
1622
+ /**
1623
+ * Collect every public-id candidate a painted run can carry for a tracked
1624
+ * change, deduped, in priority order: the canonical
1625
+ * `data-track-change-preferred-target-id` first (when the painter stamps one),
1626
+ * then the `data-track-change-ids` list, then the singular
1627
+ * `data-track-change-id`. The caller validates these against the live
1628
+ * tracked-changes slice and keeps whichever is the public id, so it does not
1629
+ * matter which attribute carries it — an imported/raw alias on one attribute
1630
+ * never hides the public id on another (a union, not a prefer-one-attribute).
1631
+ */
1632
+ function getTrackChangeIds(node) {
1633
+ const out = [];
1634
+ const add = (id) => {
1635
+ if (id && !out.includes(id)) out.push(id);
1636
+ };
1637
+ add(node.getAttribute("data-track-change-preferred-target-id"));
1638
+ const list = node.getAttribute("data-track-change-ids");
1639
+ if (list) for (const id of parseCommaSeparatedIds(list)) add(id);
1640
+ add(node.getAttribute("data-track-change-id"));
1641
+ return out;
1642
+ }
1643
+ /**
1644
+ * Read painted entities off `el` and every ancestor up to the document root, or
1645
+ * up to and INCLUDING `stopAt` when given. Innermost-first ordering: a tracked
1646
+ * change inside a comment highlight returns `[{ trackedChange }, { comment }]`,
1647
+ * matching what a switch on `hits[0]` expects when picking the most specific
1648
+ * entity.
1649
+ *
1650
+ * `stopAt` bounds the walk to the visible host: its own attributes are read, but
1651
+ * the walk never climbs into an app wrapper ABOVE it, so wrapper data-* cannot
1652
+ * leak into the hits. Omitting it keeps the original walk-to-document-root.
1653
+ *
1654
+ * Returns `[]` for null / non-Element starts. Uses duck-typed `getAttribute`
1655
+ * access so it works under any DOM implementation (happy-dom, jsdom, real
1656
+ * browser) without an `instanceof` check that could fail across realms.
1657
+ */
1658
+ function collectEntityHitsFromChain(start, stopAt) {
1659
+ if (!start || typeof start.getAttribute !== "function") return [];
1660
+ const hits = [];
1661
+ const seen = /* @__PURE__ */ new Set();
1662
+ let pendingStory = [];
1663
+ let nearestStoryKey = null;
1664
+ let nearestLayoutStory = null;
1665
+ let el = start;
1666
+ while (el) {
1667
+ const node = el;
1668
+ nearestStoryKey ??= node.getAttribute("data-story-key");
1669
+ const trackChangeIds = getTrackChangeIds(node);
1670
+ const storyKey = trackChangeIds.length > 0 ? node.getAttribute("data-story-key") : null;
1671
+ for (const trackChangeId of trackChangeIds) {
1672
+ const key = `trackedChange:${trackChangeId}`;
1673
+ if (!seen.has(key)) {
1674
+ seen.add(key);
1675
+ const hit = {
1676
+ type: "trackedChange",
1677
+ id: trackChangeId
1678
+ };
1679
+ if (storyKey) hit.storyKey = storyKey;
1680
+ hits.push(hit);
1681
+ pendingStory.push(hit);
1682
+ }
1683
+ }
1684
+ const layoutStory = node.getAttribute("data-layout-story");
1685
+ nearestLayoutStory ??= layoutStory;
1686
+ if (layoutStory && pendingStory.length > 0) {
1687
+ for (const hit of pendingStory) hit.story = layoutStory;
1688
+ pendingStory = [];
1689
+ }
1690
+ const citationId = node.getAttribute("data-citation-id");
1691
+ if (citationId) {
1692
+ const key = `citation:${citationId}`;
1693
+ if (!seen.has(key)) {
1694
+ seen.add(key);
1695
+ hits.push({
1696
+ type: "citation",
1697
+ id: citationId
1698
+ });
1699
+ }
1700
+ }
1701
+ const commentIds = node.getAttribute("data-comment-ids");
1702
+ if (commentIds) for (const id of parseCommaSeparatedIds(commentIds)) {
1703
+ const key = `comment:${id}`;
1704
+ if (!seen.has(key)) {
1705
+ seen.add(key);
1706
+ hits.push({
1707
+ type: "comment",
1708
+ id
1709
+ });
1710
+ }
1711
+ }
1712
+ const sdtType = node.getAttribute("data-sdt-type");
1713
+ const sdtId = node.getAttribute("data-sdt-id");
1714
+ if (sdtId && sdtType === "structuredContent") {
1715
+ const key = `contentControl:${sdtId}`;
1716
+ if (!seen.has(key)) {
1717
+ seen.add(key);
1718
+ const scopeAttr = node.getAttribute("data-sdt-scope");
1719
+ const tag = node.getAttribute("data-sdt-tag");
1720
+ const hit = {
1721
+ type: "contentControl",
1722
+ id: sdtId
1723
+ };
1724
+ if (nearestStoryKey) hit.storyKey = nearestStoryKey;
1725
+ if (nearestLayoutStory) hit.story = nearestLayoutStory;
1726
+ else pendingStory.push(hit);
1727
+ if (scopeAttr === "block" || scopeAttr === "inline") hit.scope = scopeAttr;
1728
+ if (tag) hit.tag = tag;
1729
+ hits.push(hit);
1730
+ }
1731
+ }
1732
+ if (stopAt && el === stopAt) break;
1733
+ el = el.parentElement;
1734
+ }
1735
+ return hits;
1736
+ }
1737
+ //#endregion
1738
+ //#region ../layout-engine/contracts/src/direction-context.ts
1739
+ /**
1740
+ * Read a paragraph's inline base direction from its attributes.
1741
+ *
1742
+ * Prefers the resolved {@link ParagraphDirectionContext} (SD-2776) when
1743
+ * present. Falls back to `paragraphProperties.rightToLeft` for PM-node /
1744
+ * editor paths that store direction on the raw OOXML properties rather
1745
+ * than the typed direction context.
1746
+ *
1747
+ * Consumers should call this instead of inspecting attrs ad hoc so the
1748
+ * direction source check stays in one place.
1749
+ */
1750
+ function getParagraphInlineDirection(attrs) {
1751
+ const fromContext = attrs?.directionContext?.inlineDirection;
1752
+ if (fromContext != null) return fromContext;
1753
+ const ppRtl = attrs?.paragraphProperties?.rightToLeft;
1754
+ if (ppRtl === true) return "rtl";
1755
+ if (ppRtl === false) return "ltr";
1756
+ }
1757
+ //#endregion
1758
+ //#region ../layout-engine/contracts/src/drawing-taxonomy.ts
1759
+ /**
1760
+ * Frozen canonical diagnostic codes for drawing rendering.
1761
+ *
1762
+ * New extractor/adapter diagnostics MUST use these exact strings. Family-
1763
+ * specific codes are required — do not collapse everything to
1764
+ * `render.unsupported-inline-ooxml` (plan §4).
1765
+ */
1766
+ var DRAWING_DIAGNOSTIC_CODES = {
1767
+ /** External image relationship (`r:link` / `TargetMode="External"`). Not fetched. */
1768
+ externalImageDeferred: "render.media.external-image-deferred",
1769
+ /** Drawing references a relationship id that does not exist on the owner part. */
1770
+ missingRelationship: "render.drawing.missing-relationship",
1771
+ /** Relationship resolves but the target media part bytes are missing/empty. */
1772
+ missingMediaPart: "render.media.missing-part",
1773
+ /** Relationship exists but is not an image relationship type. */
1774
+ unsupportedRelationshipType: "render.drawing.unsupported-relationship-type",
1775
+ /** Media MIME is not in the supported allowlist. */
1776
+ unsupportedMime: "render.media.unsupported-mime",
1777
+ /** Media part exceeds the host byte-size policy before bytes are materialized. */
1778
+ imageTooLarge: "render.media.image-too-large",
1779
+ /** SVG content rejected by the SVG safety policy. */
1780
+ unsafeSvg: "render.media.unsafe-svg",
1781
+ /** Metafile / TIFF (EMF, WMF, TIFF) with no approved conversion path. */
1782
+ unsupportedFormat: "render.media.unsupported-format",
1783
+ /** OLE / ActiveX / embedded package object. */
1784
+ embeddedObjectNotSupported: "render.embedded-object-not-supported",
1785
+ /** SmartArt / diagram / unknown external graphic-data object. */
1786
+ unsupportedObject: "render.drawing.unsupported-object",
1787
+ /** VML structure outside any supported image-like subset. */
1788
+ vmlUnsupported: "render.drawing.vml-unsupported",
1789
+ /** VML image-like content that could not be promoted to a supported image. */
1790
+ vmlImageUnsupported: "render.drawing.vml-image-unsupported",
1791
+ /** Custom geometry command the extractor does not implement (e.g. `arcTo`). */
1792
+ unsupportedGeometryCommand: "render.drawing.unsupported-geometry-command",
1793
+ /** Anchor fields required for honest placement are unsupported. */
1794
+ anchorUnsupported: "render.drawing.anchor-unsupported",
1795
+ /** Wrap fields required for honest placement are unsupported. */
1796
+ wrapUnsupported: "render.drawing.wrap-unsupported",
1797
+ /** `mc:AlternateContent` had no supported choice and no usable fallback. */
1798
+ altContentNoSupportedChoice: "render.drawing.altcontent-no-supported-choice",
1799
+ /** A child of an otherwise-supported group is unsupported. */
1800
+ groupChildUnsupported: "render.drawing.group-child-unsupported"
1801
+ };
1802
+ DRAWING_DIAGNOSTIC_CODES.missingRelationship, DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType, DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType, DRAWING_DIAGNOSTIC_CODES.imageTooLarge, DRAWING_DIAGNOSTIC_CODES.missingMediaPart, DRAWING_DIAGNOSTIC_CODES.unsupportedObject, DRAWING_DIAGNOSTIC_CODES.vmlUnsupported;
1803
+ /**
1804
+ * The frozen drawing support taxonomy.
1805
+ *
1806
+ * Invariants (enforced by `drawing-taxonomy.test.ts`):
1807
+ * - every `supported` family has a real contract target (`!== 'none'`);
1808
+ * - every `DrawingBlock`-targeted family declares a `drawingKind`;
1809
+ * - every `fail-closed` family declares a canonical `diagnostic`;
1810
+ * - every canonical diagnostic code is referenced by at least one fail-closed
1811
+ * family;
1812
+ * - `deferred` families make no support claim (`contract: 'none'`, no
1813
+ * diagnostic).
1814
+ */
1815
+ var DRAWING_SUPPORT_TAXONOMY = {
1816
+ inlineBitmap: {
1817
+ family: "inlineBitmap",
1818
+ support: "supported",
1819
+ description: "wp:inline pic:pic with internal media and browser-safe MIME (PNG/JPEG; WebP/SVG once the host media resolver lands).",
1820
+ contract: "ImageRun"
1508
1821
  },
1509
- {
1510
- id: "direction-ltr",
1511
- family: "direction",
1512
- disposition: "routed",
1513
- mutates: true,
1514
- docRoute: "format.paragraph.setDirection",
1515
- blockParagraph: {
1516
- kind: "direction",
1517
- fixedValue: "ltr"
1518
- }
1822
+ anchoredBitmap: {
1823
+ family: "anchoredBitmap",
1824
+ support: "supported",
1825
+ description: "wp:anchor bitmap with anchor/wrap expressible by ImageAnchor/ImageWrap. Never silently inlined.",
1826
+ contract: "ImageBlock"
1519
1827
  },
1520
- {
1521
- id: "direction-rtl",
1522
- family: "direction",
1523
- disposition: "routed",
1524
- mutates: true,
1525
- docRoute: "format.paragraph.setDirection",
1526
- blockParagraph: {
1527
- kind: "direction",
1528
- fixedValue: "rtl"
1529
- }
1828
+ imageChildInGroup: {
1829
+ family: "imageChildInGroup",
1830
+ support: "supported",
1831
+ description: "Bitmap child of a supported DrawingML group/drawing wrapper. Supported only when the parent is.",
1832
+ contract: "ImageDrawing"
1530
1833
  },
1531
- {
1532
- id: "clear-formatting",
1533
- family: "formatting",
1534
- disposition: "routed",
1535
- mutates: true,
1536
- docRoute: "format.apply",
1537
- inline: {
1538
- key: "*",
1539
- kind: "clear"
1540
- }
1834
+ vectorShape: {
1835
+ family: "vectorShape",
1836
+ support: "supported",
1837
+ description: "Block/floating DrawingML preset/custom shape with the supported geometry/style subset.",
1838
+ contract: "DrawingBlock",
1839
+ drawingKind: "vectorShape"
1541
1840
  },
1542
- {
1543
- id: "image",
1544
- family: "media",
1545
- disposition: "routed",
1546
- mutates: true,
1547
- docRoute: "create.image",
1548
- create: { kind: "image" }
1841
+ shapeGroup: {
1842
+ family: "shapeGroup",
1843
+ support: "supported",
1844
+ description: "DrawingML group whose rendered children are supported vector/image children.",
1845
+ contract: "DrawingBlock",
1846
+ drawingKind: "shapeGroup"
1549
1847
  },
1550
- {
1551
- id: "table-of-contents-insert",
1552
- family: "TOC",
1553
- disposition: "routed",
1554
- mutates: true,
1555
- docRoute: "create.tableOfContents",
1556
- create: { kind: "toc" }
1848
+ chart: {
1849
+ family: "chart",
1850
+ support: "supported",
1851
+ description: "DrawingML chart projected into a ChartDrawing with parsed cached chart XML data. Complete Word chart fidelity remains deferred to chartFidelity.",
1852
+ contract: "DrawingBlock",
1853
+ drawingKind: "chart"
1557
1854
  },
1558
- {
1559
- id: "table-insert",
1560
- family: "tables",
1561
- disposition: "routed",
1562
- mutates: true,
1563
- docRoute: "create.table",
1564
- create: { kind: "table" }
1855
+ alternateContent: {
1856
+ family: "alternateContent",
1857
+ support: "supported",
1858
+ description: "mc:AlternateContent traversal/select policy. Selected Choice/Fallback maps to its family contract; unselected branches preserved for save/reopen.",
1859
+ contract: "selected-choice"
1565
1860
  },
1566
- {
1567
- id: "table-add-row-before",
1568
- family: "tables",
1569
- disposition: "routed",
1570
- mutates: true,
1571
- docRoute: "tables.insertRow",
1572
- table: {
1573
- action: "insert-row-before",
1574
- op: "insertRow"
1575
- },
1576
- reason: TABLE_CONTEXT
1861
+ externalImage: {
1862
+ family: "externalImage",
1863
+ support: "fail-closed",
1864
+ description: "External image relationship (r:link / TargetMode=\"External\"). Not fetched.",
1865
+ contract: "none",
1866
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.externalImageDeferred
1577
1867
  },
1578
- {
1579
- id: "table-add-row-after",
1580
- family: "tables",
1581
- disposition: "routed",
1582
- mutates: true,
1583
- docRoute: "tables.insertRow",
1584
- table: {
1585
- action: "insert-row-after",
1586
- op: "insertRow"
1587
- },
1588
- reason: TABLE_CONTEXT
1868
+ missingRelationship: {
1869
+ family: "missingRelationship",
1870
+ support: "fail-closed",
1871
+ description: "Drawing references a relationship id absent from the owner part .rels.",
1872
+ contract: "none",
1873
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.missingRelationship
1589
1874
  },
1590
- {
1591
- id: "table-delete-row",
1592
- family: "tables",
1593
- disposition: "routed",
1594
- mutates: true,
1595
- docRoute: "tables.deleteRow",
1596
- table: {
1597
- action: "delete-row",
1598
- op: "deleteRow"
1599
- },
1600
- reason: TABLE_CONTEXT
1875
+ missingMediaPart: {
1876
+ family: "missingMediaPart",
1877
+ support: "fail-closed",
1878
+ description: "Relationship resolves but the target media part bytes are missing or empty.",
1879
+ contract: "none",
1880
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.missingMediaPart
1601
1881
  },
1602
- {
1603
- id: "table-add-column-before",
1604
- family: "tables",
1605
- disposition: "routed",
1606
- mutates: true,
1607
- docRoute: "tables.insertColumn",
1608
- table: {
1609
- action: "insert-column-before",
1610
- op: "insertColumn"
1611
- },
1612
- reason: TABLE_CONTEXT
1882
+ wrongRelationshipType: {
1883
+ family: "wrongRelationshipType",
1884
+ support: "fail-closed",
1885
+ description: "Relationship exists but is not an image relationship type.",
1886
+ contract: "none",
1887
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType
1613
1888
  },
1614
- {
1615
- id: "table-add-column-after",
1616
- family: "tables",
1617
- disposition: "routed",
1618
- mutates: true,
1619
- docRoute: "tables.insertColumn",
1620
- table: {
1621
- action: "insert-column-after",
1622
- op: "insertColumn"
1623
- },
1624
- reason: TABLE_CONTEXT
1889
+ unsupportedMime: {
1890
+ family: "unsupportedMime",
1891
+ support: "fail-closed",
1892
+ description: "Media MIME outside the supported allowlist (incl. GIF/BMP/ICO).",
1893
+ contract: "none",
1894
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedMime
1625
1895
  },
1626
- {
1627
- id: "table-delete-column",
1628
- family: "tables",
1629
- disposition: "routed",
1630
- mutates: true,
1631
- docRoute: "tables.deleteColumn",
1632
- table: {
1633
- action: "delete-column",
1634
- op: "deleteColumn"
1635
- },
1636
- reason: TABLE_CONTEXT
1896
+ oversizedMediaPart: {
1897
+ family: "oversizedMediaPart",
1898
+ support: "fail-closed",
1899
+ description: "Media part exceeds the host byte-size policy before bytes are materialized.",
1900
+ contract: "none",
1901
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.imageTooLarge
1637
1902
  },
1638
- {
1639
- id: "table-delete",
1640
- family: "tables",
1641
- disposition: "routed",
1642
- mutates: true,
1643
- docRoute: "tables.delete",
1644
- table: {
1645
- action: "delete-table",
1646
- op: "delete"
1647
- },
1648
- reason: TABLE_CONTEXT
1903
+ unsafeSvg: {
1904
+ family: "unsafeSvg",
1905
+ support: "fail-closed",
1906
+ description: "SVG content rejected by the SVG safety policy.",
1907
+ contract: "none",
1908
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsafeSvg
1649
1909
  },
1650
- {
1651
- id: "table-merge-cells",
1652
- family: "tables",
1653
- disposition: "routed",
1654
- mutates: true,
1655
- docRoute: "tables.mergeCells",
1656
- table: {
1657
- action: "merge-cells",
1658
- op: "mergeCells"
1659
- },
1660
- reason: TABLE_CONTEXT
1910
+ metafileOrTiff: {
1911
+ family: "metafileOrTiff",
1912
+ support: "fail-closed",
1913
+ description: "EMF/WMF/TIFF and other non-browser-native formats with no approved conversion path.",
1914
+ contract: "none",
1915
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedFormat
1661
1916
  },
1662
- {
1663
- id: "table-split-cell",
1664
- family: "tables",
1665
- disposition: "routed",
1666
- mutates: true,
1667
- docRoute: "tables.splitCell",
1668
- table: {
1669
- action: "split-cell",
1670
- op: "splitCell",
1671
- requiresCell: true
1672
- },
1673
- reason: TABLE_CONTEXT
1917
+ embeddedObject: {
1918
+ family: "embeddedObject",
1919
+ support: "fail-closed",
1920
+ description: "OLE / ActiveX / embedded package object.",
1921
+ contract: "none",
1922
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.embeddedObjectNotSupported
1674
1923
  },
1675
- {
1676
- id: "table-remove-borders",
1677
- family: "tables",
1678
- disposition: "routed",
1679
- mutates: true,
1680
- docRoute: "tables.setBorders",
1681
- table: {
1682
- action: "remove-borders",
1683
- op: "setBorders"
1684
- },
1685
- reason: TABLE_CONTEXT
1924
+ smartArtOrDiagram: {
1925
+ family: "smartArtOrDiagram",
1926
+ support: "fail-closed",
1927
+ description: "SmartArt / diagram / unknown external graphic-data object.",
1928
+ contract: "none",
1929
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedObject
1686
1930
  },
1687
- {
1688
- id: "ruler",
1689
- family: "UI chrome",
1690
- disposition: "routed",
1691
- mutates: false,
1692
- instanceRoute: "toggleRuler",
1693
- valueFrom: "ruler"
1931
+ vml: {
1932
+ family: "vml",
1933
+ support: "fail-closed",
1934
+ description: "VML structure outside any supported image-like subset (default fail-closed policy).",
1935
+ contract: "none",
1936
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.vmlUnsupported
1694
1937
  },
1695
- {
1696
- id: "formatting-marks",
1697
- family: "UI chrome",
1698
- disposition: "routed",
1699
- mutates: false,
1700
- instanceRoute: "toggleFormattingMarks",
1701
- valueFrom: "formattingMarks"
1938
+ vmlImageLike: {
1939
+ family: "vmlImageLike",
1940
+ support: "fail-closed",
1941
+ description: "Simple VML image references (v:imagedata) / watermark image metadata. Fail-closed unless a later change promotes a named subset with deterministic fixtures and exact oracles.",
1942
+ contract: "none",
1943
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.vmlImageUnsupported
1702
1944
  },
1703
- {
1704
- id: "copy-format",
1705
- family: "formatting",
1706
- disposition: "routed",
1707
- mutates: false
1945
+ unsupportedGeometryCommand: {
1946
+ family: "unsupportedGeometryCommand",
1947
+ support: "fail-closed",
1948
+ description: "Custom geometry command the extractor does not implement, e.g. arcTo.",
1949
+ contract: "none",
1950
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedGeometryCommand
1708
1951
  },
1709
- {
1710
- id: "table-fix",
1711
- family: "tables",
1712
- disposition: "unsupported",
1713
- mutates: true,
1714
- reason: UNSUPPORTED
1715
- }
1716
- ];
1717
- var CATALOG_BY_ID = new Map(COMMAND_CATALOG.map((descriptor) => [descriptor.id, descriptor]));
1718
- /** All built-in command ids known to the controller (routed + deferred + unsupported). */
1719
- var ALL_BUILT_IN_COMMAND_IDS = COMMAND_CATALOG.map((d) => d.id);
1720
- /** Resolve a command descriptor by id, or `null` when the id is unknown. */
1721
- function getCommandDescriptor(id) {
1722
- return CATALOG_BY_ID.get(id) ?? null;
1723
- }
1724
- //#endregion
1725
- //#region src/public/ui/equality.ts
1726
- /**
1727
- * Shallow equality used by the controller to suppress redundant slice
1728
- * notifications. v2-native, dependency-free.
1729
- */
1730
- /**
1731
- * Returns `true` when `a` and `b` are the same reference, or are objects with
1732
- * the same own-enumerable keys and `Object.is`-equal values one level deep.
1733
- * Arrays compare by index. Primitives compare with `Object.is`.
1734
- */
1735
- function shallowEqual(a, b) {
1736
- if (Object.is(a, b)) return true;
1737
- if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
1738
- if (Array.isArray(a) !== Array.isArray(b)) return false;
1739
- const aKeys = Object.keys(a);
1740
- const bKeys = Object.keys(b);
1741
- if (aKeys.length !== bKeys.length) return false;
1742
- for (const key of aKeys) {
1743
- if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
1744
- if (!Object.is(a[key], b[key])) return false;
1745
- }
1746
- return true;
1747
- }
1748
- //#endregion
1749
- //#region src/public/ui/format-painter-helpers.js
1750
- /**
1751
- * Maps SDRunProps field names to InlineRunPatch field names.
1752
- * SDRunProps (read-side) and InlineRunPatch (write-side) use different keys
1753
- * for the same properties; direct assignment between them is forbidden.
1754
- *
1755
- * @param {Record<string, unknown>} props - SDRunProps object from doc.getNodeById
1756
- * @returns {Record<string, unknown>} InlineRunPatch ready for doc.format.apply
1757
- */
1758
- function sdRunPropsToInlineRunPatch(props) {
1759
- if (!props || typeof props !== "object") return {};
1760
- const patch = {};
1761
- if (props.fonts) patch.rFonts = {
1762
- ascii: props.fonts.ascii,
1763
- hAnsi: props.fonts.hAnsi,
1764
- eastAsia: props.fonts.eastAsia,
1765
- cs: props.fonts.cs,
1766
- asciiTheme: props.fonts.asciiTheme,
1767
- hAnsiTheme: props.fonts.hAnsiTheme,
1768
- eastAsiaTheme: props.fonts.eastAsiaTheme,
1769
- csTheme: props.fonts.csTheme,
1770
- hint: props.fonts.hint
1771
- };
1772
- if (props.fontFamily != null) patch.fontFamily = props.fontFamily;
1773
- if (props.lang) patch.lang = {
1774
- val: props.lang.val,
1775
- eastAsia: props.lang.eastAsia,
1776
- bidi: props.lang.bidi
1777
- };
1778
- if (props.fontSize != null) patch.fontSize = props.fontSize;
1779
- if (props.fontSizeCs != null) patch.fontSizeCs = props.fontSizeCs;
1780
- const color = projectColorRefToInlineColor(props.color);
1781
- if (color != null) patch.color = color;
1782
- if (props.highlight != null) patch.highlight = props.highlight;
1783
- const shading = projectShadingToInlinePatch(props.shading);
1784
- if (shading) patch.shading = shading;
1785
- if (props.cs != null) patch.cs = props.cs;
1786
- if (props.rtl != null) patch.rtl = props.rtl;
1787
- if (props.bold != null) patch.bold = props.bold;
1788
- if (props.boldCs != null) patch.bCs = props.boldCs;
1789
- if (props.italic != null) patch.italic = props.italic;
1790
- if (props.italicCs != null) patch.iCs = props.italicCs;
1791
- const underline = projectUnderlineToInlinePatch(props.underline);
1792
- if (underline !== void 0) patch.underline = underline;
1793
- if (props.strikethrough != null) patch.strike = props.strikethrough;
1794
- if (props.doubleStrikethrough != null) patch.dstrike = props.doubleStrikethrough;
1795
- if (props.caps != null) patch.caps = props.caps;
1796
- if (props.smallCaps != null) patch.smallCaps = props.smallCaps;
1797
- if (props.outline != null) patch.outline = props.outline;
1798
- if (props.shadow != null) patch.shadow = props.shadow;
1799
- if (props.emboss != null) patch.emboss = props.emboss;
1800
- if (props.imprint != null) patch.imprint = props.imprint;
1801
- if (props.verticalAlign != null) patch.vertAlign = props.verticalAlign;
1802
- if (props.characterSpacing != null) patch.letterSpacing = props.characterSpacing;
1803
- if (props.characterScale != null) patch.charScale = props.characterScale;
1804
- if (props.kern != null) patch.kerning = props.kern;
1805
- if (props.baselineShift != null) patch.position = props.baselineShift;
1806
- if (props.fitTextWidth != null) patch.fitText = { val: props.fitTextWidth };
1807
- if (props.vanish != null) patch.vanish = props.vanish;
1808
- if (props.webHidden != null) patch.webHidden = props.webHidden;
1809
- if (props.specVanish != null) patch.specVanish = props.specVanish;
1810
- const border = projectBorderToInlinePatch(props.border);
1811
- if (border) patch.border = border;
1812
- return patch;
1813
- }
1814
- function projectColorRefToInlineColor(color) {
1815
- if (!color) return void 0;
1816
- if (typeof color === "string") return color;
1817
- if (color.model === "rgb") return color.value;
1818
- if (color.model === "auto") return "auto";
1819
- }
1820
- function projectUnderlineToInlinePatch(underline) {
1821
- if (underline == null) return void 0;
1822
- const patch = {};
1823
- if (underline.style != null) patch.style = underline.style;
1824
- if (underline.color?.model === "rgb") patch.color = underline.color.value;
1825
- if (underline.color?.model === "theme") patch.themeColor = underline.color.theme;
1826
- return Object.keys(patch).length > 0 ? patch : true;
1827
- }
1828
- function projectShadingToInlinePatch(shading) {
1829
- if (!shading) return void 0;
1830
- const patch = {};
1831
- const fill = projectColorRefToInlineColor(shading.fill);
1832
- if (fill != null) patch.fill = fill;
1833
- const color = projectColorRefToInlineColor(shading.color);
1834
- if (color != null) patch.color = color;
1835
- if (shading.pattern != null) patch.val = shading.pattern;
1836
- return Object.keys(patch).length > 0 ? patch : void 0;
1837
- }
1838
- function projectBorderToInlinePatch(border) {
1839
- if (!border) return void 0;
1840
- const patch = {};
1841
- if (border.style != null) patch.val = border.style;
1842
- if (border.width != null) patch.sz = border.width;
1843
- if (border.space != null) patch.space = border.space;
1844
- const color = projectColorRefToInlineColor(border.color);
1845
- if (color != null) patch.color = color;
1846
- return Object.keys(patch).length > 0 ? patch : void 0;
1847
- }
1848
- /**
1849
- * Serializes a selection to a stable string key used to detect whether
1850
- * the target selection is the same as the source (skip apply in that case).
1851
- *
1852
- * Falls back to the plain text target when the host has not populated an
1853
- * explicit `selectionTarget` yet.
1854
- *
1855
- * @param {{ selectionTarget?: unknown; target?: unknown } | null | undefined} selection
1856
- * @returns {string}
1857
- */
1858
- function selectionKey(selection) {
1859
- const target = selection?.selectionTarget ?? selection?.target;
1860
- if (!selection || !target) return "";
1861
- try {
1862
- return JSON.stringify(target);
1863
- } catch {
1864
- return String(target);
1952
+ unsupportedAnchorFields: {
1953
+ family: "unsupportedAnchorFields",
1954
+ support: "fail-closed",
1955
+ description: "Anchor fields required for honest placement are unsupported (char-relative, alignment modes, etc.).",
1956
+ contract: "none",
1957
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.anchorUnsupported
1958
+ },
1959
+ unsupportedWrapFields: {
1960
+ family: "unsupportedWrapFields",
1961
+ support: "fail-closed",
1962
+ description: "Wrap fields required for honest placement are unsupported (arbitrary wrap polygons, etc.).",
1963
+ contract: "none",
1964
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.wrapUnsupported
1965
+ },
1966
+ alternateContentNoSupportedChoice: {
1967
+ family: "alternateContentNoSupportedChoice",
1968
+ support: "fail-closed",
1969
+ description: "mc:AlternateContent with no supported choice and no usable fallback.",
1970
+ contract: "none",
1971
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.altContentNoSupportedChoice
1972
+ },
1973
+ groupChildUnsupported: {
1974
+ family: "groupChildUnsupported",
1975
+ support: "fail-closed",
1976
+ description: "Unsupported child inside an otherwise-supported group. Supported siblings are not dropped.",
1977
+ contract: "none",
1978
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.groupChildUnsupported
1979
+ },
1980
+ objectEditing: {
1981
+ family: "objectEditing",
1982
+ support: "deferred",
1983
+ description: "Editing/mutation of anchored/vector objects.",
1984
+ contract: "none"
1985
+ },
1986
+ customWrapPolygon: {
1987
+ family: "customWrapPolygon",
1988
+ support: "deferred",
1989
+ description: "Full Word-compatible text wrapping for arbitrary custom wrap polygons.",
1990
+ contract: "none"
1991
+ },
1992
+ chartFidelity: {
1993
+ family: "chartFidelity",
1994
+ support: "deferred",
1995
+ description: "Complete chart fidelity (a dedicated chart effort owns the parsed subset).",
1996
+ contract: "none"
1997
+ },
1998
+ vmlFidelity: {
1999
+ family: "vmlFidelity",
2000
+ support: "deferred",
2001
+ description: "Complete VML fidelity.",
2002
+ contract: "none"
2003
+ },
2004
+ decorativeAccessibilityUi: {
2005
+ family: "decorativeAccessibilityUi",
2006
+ support: "deferred",
2007
+ description: "Full accessibility UI for decorative image semantics beyond preserving source data.",
2008
+ contract: "none"
1865
2009
  }
1866
- }
1867
- //#endregion
1868
- //#region src/helpers/v2-typing-mutation-event.js
1869
- /**
1870
- * Command kinds whose committed mutations belong to the sustained typing
1871
- * cadence class (insert / replace / plain paste / Backspace / Delete).
1872
- *
1873
- * The SuperDoc shell debounces per-keystroke review-row hydration for these
1874
- * kinds instead of refreshing comments + tracked changes on every commit —
1875
- * hydrating immediately per keystroke was a measured worker-read storm during
1876
- * typing and backspace bursts. The `text.*` aliases cover integrations that
1877
- * forward Document API style command ids rather than editable-input kinds.
1878
- */
1879
- var V2_TYPING_COMMAND_KINDS = /* @__PURE__ */ new Set([
1880
- "insert-text",
1881
- "replace-text",
1882
- "plain-text-paste",
1883
- "delete-backward",
1884
- "delete-forward",
1885
- "text.insert",
1886
- "text.replace",
1887
- "text.pastePlain",
1888
- "text.deleteBackward",
1889
- "text.deleteForward",
1890
- "structural.enter",
1891
- "structural:enter-split-paragraph",
1892
- "structural:enter-insert-paragraph-before",
1893
- "structural:enter-insert-paragraph-after",
1894
- "structural:shift-enter-line-break",
1895
- "structural:backspace-boundary-merge-with-previous",
1896
- "structural:delete-boundary-merge-with-next",
1897
- "backspace:boundary-merge-with-previous",
1898
- "backspace:list-remove",
1899
- "backspace:list-outdent"
1900
- ]);
1901
- /**
1902
- * True when a forwarded v2 host event is a committed editable-input mutation
1903
- * of the sustained-typing class, i.e. review-row hydration for it should be
1904
- * debounced rather than fired immediately.
1905
- *
1906
- * Events without a forwarded `editableCommandKind` (programmatic Document API
1907
- * mutations, history commits, unclassified structural commands) return false
1908
- * so they keep hydrating immediately.
1909
- *
1910
- * @param {{ type?: string, origin?: string, editableCommandKind?: string } | null | undefined} event
1911
- * @returns {boolean}
1912
- */
1913
- var isV2EditableTextMutationEvent = (event) => {
1914
- if (event?.type !== "mutation:committed" || event.origin !== "command") return false;
1915
- return V2_TYPING_COMMAND_KINDS.has(event.editableCommandKind);
1916
2010
  };
1917
- //#endregion
1918
- //#region src/public/ui/entity-at.ts
1919
- function parseCommaSeparatedIds(value) {
1920
- return value.split(",").map((id) => id.trim()).filter(Boolean);
1921
- }
1922
- /**
1923
- * Collect every public-id candidate a painted run can carry for a tracked
1924
- * change, deduped, in priority order: the canonical
1925
- * `data-track-change-preferred-target-id` first (when the painter stamps one),
1926
- * then the `data-track-change-ids` list, then the singular
1927
- * `data-track-change-id`. The caller validates these against the live
1928
- * tracked-changes slice and keeps whichever is the public id, so it does not
1929
- * matter which attribute carries it — an imported/raw alias on one attribute
1930
- * never hides the public id on another (a union, not a prefer-one-attribute).
1931
- */
1932
- function getTrackChangeIds(node) {
1933
- const out = [];
1934
- const add = (id) => {
1935
- if (id && !out.includes(id)) out.push(id);
1936
- };
1937
- add(node.getAttribute("data-track-change-preferred-target-id"));
1938
- const list = node.getAttribute("data-track-change-ids");
1939
- if (list) for (const id of parseCommaSeparatedIds(list)) add(id);
1940
- add(node.getAttribute("data-track-change-id"));
1941
- return out;
1942
- }
1943
- /**
1944
- * Read painted entities off `el` and every ancestor up to the document root, or
1945
- * up to and INCLUDING `stopAt` when given. Innermost-first ordering: a tracked
1946
- * change inside a comment highlight returns `[{ trackedChange }, { comment }]`,
1947
- * matching what a switch on `hits[0]` expects when picking the most specific
1948
- * entity.
1949
- *
1950
- * `stopAt` bounds the walk to the visible host: its own attributes are read, but
1951
- * the walk never climbs into an app wrapper ABOVE it, so wrapper data-* cannot
1952
- * leak into the hits. Omitting it keeps the original walk-to-document-root.
1953
- *
1954
- * Returns `[]` for null / non-Element starts. Uses duck-typed `getAttribute`
1955
- * access so it works under any DOM implementation (happy-dom, jsdom, real
1956
- * browser) without an `instanceof` check that could fail across realms.
1957
- */
1958
- function collectEntityHitsFromChain(start, stopAt) {
1959
- if (!start || typeof start.getAttribute !== "function") return [];
1960
- const hits = [];
1961
- const seen = /* @__PURE__ */ new Set();
1962
- let pendingStory = [];
1963
- let el = start;
1964
- while (el) {
1965
- const node = el;
1966
- const trackChangeIds = getTrackChangeIds(node);
1967
- const storyKey = trackChangeIds.length > 0 ? node.getAttribute("data-story-key") : null;
1968
- for (const trackChangeId of trackChangeIds) {
1969
- const key = `trackedChange:${trackChangeId}`;
1970
- if (!seen.has(key)) {
1971
- seen.add(key);
1972
- const hit = {
1973
- type: "trackedChange",
1974
- id: trackChangeId
1975
- };
1976
- if (storyKey) hit.storyKey = storyKey;
1977
- hits.push(hit);
1978
- pendingStory.push(hit);
1979
- }
1980
- }
1981
- const layoutStory = node.getAttribute("data-layout-story");
1982
- if (layoutStory && pendingStory.length > 0) {
1983
- for (const hit of pendingStory) hit.story = layoutStory;
1984
- pendingStory = [];
1985
- }
1986
- const citationId = node.getAttribute("data-citation-id");
1987
- if (citationId) {
1988
- const key = `citation:${citationId}`;
1989
- if (!seen.has(key)) {
1990
- seen.add(key);
1991
- hits.push({
1992
- type: "citation",
1993
- id: citationId
1994
- });
1995
- }
1996
- }
1997
- const commentIds = node.getAttribute("data-comment-ids");
1998
- if (commentIds) for (const id of parseCommaSeparatedIds(commentIds)) {
1999
- const key = `comment:${id}`;
2000
- if (!seen.has(key)) {
2001
- seen.add(key);
2002
- hits.push({
2003
- type: "comment",
2004
- id
2005
- });
2006
- }
2007
- }
2008
- const sdtType = node.getAttribute("data-sdt-type");
2009
- const sdtId = node.getAttribute("data-sdt-id");
2010
- if (sdtId && sdtType === "structuredContent") {
2011
- const key = `contentControl:${sdtId}`;
2012
- if (!seen.has(key)) {
2013
- seen.add(key);
2014
- const scopeAttr = node.getAttribute("data-sdt-scope");
2015
- const tag = node.getAttribute("data-sdt-tag");
2016
- const hit = {
2017
- type: "contentControl",
2018
- id: sdtId
2019
- };
2020
- if (scopeAttr === "block" || scopeAttr === "inline") hit.scope = scopeAttr;
2021
- if (tag) hit.tag = tag;
2022
- hits.push(hit);
2023
- }
2024
- }
2025
- if (stopAt && el === stopAt) break;
2026
- el = el.parentElement;
2027
- }
2028
- return hits;
2029
- }
2011
+ Object.keys(DRAWING_SUPPORT_TAXONOMY);
2012
+ Object.freeze([
2013
+ "multiple-sections",
2014
+ "furniture-page-tokens",
2015
+ "non-balanceable-multi-column-sections",
2016
+ "body-anchored-objects",
2017
+ "non-flowing-page-relative-body-anchors",
2018
+ "footnotes",
2019
+ "page-references",
2020
+ "cross-references",
2021
+ "keep-constraints",
2022
+ "tables",
2023
+ "furniture-anchored-objects"
2024
+ ]);
2025
+ var INCREMENTAL_DEPENDENCY_CERTIFICATE_CLASSES = Object.freeze([
2026
+ "checkpoint-geometry",
2027
+ "section-numbering",
2028
+ "font-style-inputs",
2029
+ "notes",
2030
+ "furniture",
2031
+ "flowing-anchors",
2032
+ "non-flowing-page-anchors",
2033
+ "convergence",
2034
+ "source-topology"
2035
+ ]);
2036
+ new Set(INCREMENTAL_DEPENDENCY_CERTIFICATE_CLASSES);
2030
2037
  //#endregion
2031
2038
  //#region ../document-api/src/format/inline-run-patch.ts
2032
2039
  var schemaBooleanOrNull = () => ({ oneOf: [{ type: "boolean" }, { type: "null" }] });
@@ -2295,6 +2302,19 @@ INLINE_PROPERTY_REGISTRY.filter((entry) => entry.storage === "mark").map((entry)
2295
2302
  * `false` when the surface is unavailable (view mode, a pre-ready editor, or a
2296
2303
  * host that has not exposed a Document API facade) rather than throwing.
2297
2304
  */
2305
+ var firstPartyCommandMutations = /* @__PURE__ */ new WeakMap();
2306
+ var firstPartyCommandExecutors = /* @__PURE__ */ new WeakMap();
2307
+ function registerFirstPartyCommandMutation(superdoc, host, mutation) {
2308
+ let hosts = firstPartyCommandMutations.get(superdoc);
2309
+ if (!hosts) {
2310
+ hosts = /* @__PURE__ */ new WeakMap();
2311
+ firstPartyCommandMutations.set(superdoc, hosts);
2312
+ }
2313
+ hosts.set(host, mutation);
2314
+ }
2315
+ function executeFirstPartyCommandAsync(ui, id, payload) {
2316
+ return firstPartyCommandExecutors.get(ui)?.(id, payload) ?? ui.commands.executeAsync(id, payload);
2317
+ }
2298
2318
  function uiBenchNowMs() {
2299
2319
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
2300
2320
  }
@@ -2686,6 +2706,11 @@ function layoutStoryDatasetToStoryLocator(raw) {
2686
2706
  storyType: "headerFooterPart",
2687
2707
  refId: decoded.id
2688
2708
  } : void 0;
2709
+ case "textbox": return decoded.id ? {
2710
+ kind: "story",
2711
+ storyType: "textbox",
2712
+ textboxId: decoded.id
2713
+ } : void 0;
2689
2714
  default: return;
2690
2715
  }
2691
2716
  }
@@ -3915,6 +3940,7 @@ function createSuperDocUI(options) {
3915
3940
  keyboardSelecting: false
3916
3941
  };
3917
3942
  let painterCaptureEpoch = 0;
3943
+ let painterCommandDoc = null;
3918
3944
  const painterModeListeners = /* @__PURE__ */ new Set();
3919
3945
  let frozenProjectedValues = {};
3920
3946
  let frozenProjectedValuesKey = "";
@@ -3964,8 +3990,10 @@ function createSuperDocUI(options) {
3964
3990
  },
3965
3991
  contextAt
3966
3992
  };
3993
+ let firstPartyCommandDoc;
3967
3994
  /** Read the live browser Document API facade (or null). */
3968
3995
  const getDoc = () => {
3996
+ if (firstPartyCommandDoc !== void 0) return firstPartyCommandDoc;
3969
3997
  const doc = getEditor()?.doc;
3970
3998
  return doc && typeof doc === "object" ? doc : null;
3971
3999
  };
@@ -3996,6 +4024,25 @@ function createSuperDocUI(options) {
3996
4024
  const host = getEditor()?.host;
3997
4025
  return host && typeof host === "object" ? host : null;
3998
4026
  };
4027
+ const createFirstPartyCommandDoc = () => {
4028
+ const editor = getEditor();
4029
+ const doc = editor?.doc;
4030
+ if (editor?.editorVersion !== 2) return doc;
4031
+ const host = getHost();
4032
+ const mutation = host ? firstPartyCommandMutations.get(superdoc)?.get(host) : null;
4033
+ if (!doc || !host || !mutation) return null;
4034
+ const wrap = (source, prefix) => new Proxy(source, { get(target, key, receiver) {
4035
+ const value = Reflect.get(target, key, receiver);
4036
+ if (typeof key !== "string") return value;
4037
+ const memberPath = prefix ? `${prefix}.${key}` : key;
4038
+ if (typeof value === "function" && mutation.supports(memberPath)) return (...args) => {
4039
+ if (getEditor() !== editor || getHost() !== host || editor.doc !== doc) return false;
4040
+ return mutation.run(memberPath, args);
4041
+ };
4042
+ return value && (typeof value === "object" || typeof value === "function") ? wrap(value, memberPath) : value;
4043
+ } });
4044
+ return wrap(doc, "");
4045
+ };
3999
4046
  const resolveActiveHeaderFooterSlot = (story) => {
4000
4047
  if (story.storyType !== "headerFooterPart" || typeof story.refId !== "string") return null;
4001
4048
  const editor = getEditor();
@@ -7994,7 +8041,10 @@ function createSuperDocUI(options) {
7994
8041
  function executeListToggleCommand(kind, payload) {
7995
8042
  const lists = getEditCommands()?.lists;
7996
8043
  const apply = lists?.apply;
7997
- if (typeof apply !== "function") return false;
8044
+ const host = getHost();
8045
+ const firstPartyMutation = firstPartyCommandDoc !== void 0 && getEditor()?.editorVersion === 2 && host ? firstPartyCommandMutations.get(superdoc)?.get(host) : null;
8046
+ if (firstPartyCommandDoc !== void 0 && getEditor()?.editorVersion === 2 && !firstPartyMutation) return false;
8047
+ if (!firstPartyMutation && typeof apply !== "function") return false;
7998
8048
  const { behavior, preset, continuity } = typeof payload === "string" ? presetFromToolbarStyleKey(payload) : payload && typeof payload === "object" ? payload : {};
7999
8049
  const input = {
8000
8050
  kind,
@@ -8003,7 +8053,7 @@ function createSuperDocUI(options) {
8003
8053
  if (preset !== void 0) input.preset = preset;
8004
8054
  if (continuity !== void 0) input.continuity = continuity;
8005
8055
  try {
8006
- return settleCommandExecution(apply.call(lists, input));
8056
+ return settleCommandExecution(firstPartyMutation ? firstPartyMutation.runListApply(input) : apply.call(lists, input));
8007
8057
  } catch {
8008
8058
  return false;
8009
8059
  }
@@ -8625,6 +8675,9 @@ function createSuperDocUI(options) {
8625
8675
  const doc = getDoc();
8626
8676
  const linksApi = doc?.hyperlinks;
8627
8677
  if (!linksApi) return false;
8678
+ const hyperlinkHost = getHost();
8679
+ const hyperlinkHandles = typeof hyperlinkHost?.getHandles === "function" ? hyperlinkHost.getHandles() : null;
8680
+ const trustedLinksApi = firstPartyCommandDoc ? (hyperlinkHandles?.editing)?.hyperlinks : linksApi;
8628
8681
  const record = readLinkPayloadRecord(payload);
8629
8682
  const href = readLinkPayloadHref(payload);
8630
8683
  const payloadTarget = readLinkPayloadTarget(payload);
@@ -8648,7 +8701,7 @@ function createSuperDocUI(options) {
8648
8701
  }
8649
8702
  }
8650
8703
  if (existing && href === null) {
8651
- const remove = linksApi.remove;
8704
+ const remove = trustedLinksApi?.remove;
8652
8705
  if (typeof remove !== "function") return false;
8653
8706
  try {
8654
8707
  return callEditorMutation("hyperlinks.remove", remove, {
@@ -8660,7 +8713,7 @@ function createSuperDocUI(options) {
8660
8713
  }
8661
8714
  }
8662
8715
  if ((existing || requestedHyperlinkTarget) && typeof href === "string" && href.trim() !== "") {
8663
- const patch = linksApi.patch;
8716
+ const patch = trustedLinksApi?.patch;
8664
8717
  const updateTarget = linksApi.updateTarget;
8665
8718
  if (!(!hasRequestedHyperlinkTarget && existing && typeof patch === "function") && !(hasRequestedHyperlinkTarget && typeof updateTarget === "function")) return false;
8666
8719
  const shouldReplaceText = typeof text === "string" && text !== (hasRequestedHyperlinkTarget ? currentText : existing?.text ?? currentText);
@@ -8750,7 +8803,7 @@ function createSuperDocUI(options) {
8750
8803
  }
8751
8804
  if (typeof href === "string" && href.trim() !== "") {
8752
8805
  const normalizedHref = href.trim();
8753
- const wrap = linksApi.wrap;
8806
+ const wrap = trustedLinksApi?.wrap;
8754
8807
  const textTargets = textAddressesFromTarget(payloadTarget);
8755
8808
  const wrapTargets = textTargets.length > 0 ? textTargets : selectionTextAddresses(state.selection);
8756
8809
  if (wrapTargets.length > 0) {
@@ -8764,7 +8817,7 @@ function createSuperDocUI(options) {
8764
8817
  return false;
8765
8818
  }
8766
8819
  }
8767
- const insert = linksApi.insert;
8820
+ const insert = trustedLinksApi?.insert;
8768
8821
  const text = readLinkPayloadText(payload, normalizedHref);
8769
8822
  if (typeof insert !== "function" || text.trim() === "") return false;
8770
8823
  const target = collapsedTextAddressFromTarget(payloadTarget) ?? collapsedTextAddressFromSelection(state.selection);
@@ -8835,8 +8888,13 @@ function createSuperDocUI(options) {
8835
8888
  if (typeof record.alt === "string") input.alt = record.alt;
8836
8889
  if (typeof record.title === "string") input.title = record.title;
8837
8890
  if (record.size && typeof record.size === "object") input.size = record.size;
8891
+ const imageHost = getHost();
8892
+ const imageHandles = typeof imageHost?.getHandles === "function" ? imageHost.getHandles() : null;
8893
+ const trustedImageCommands = firstPartyCommandDoc ? (imageHandles?.editing)?.images : null;
8894
+ const create = firstPartyCommandDoc ? trustedImageCommands?.create : op;
8895
+ if (typeof create !== "function") return false;
8838
8896
  try {
8839
- return callEditorMutation(descriptor.docRoute, op, input);
8897
+ return callEditorMutation(descriptor.docRoute, create, input);
8840
8898
  } catch {
8841
8899
  return false;
8842
8900
  }
@@ -8850,6 +8908,13 @@ function createSuperDocUI(options) {
8850
8908
  return false;
8851
8909
  }
8852
8910
  };
8911
+ const tableInsertionCommands = () => {
8912
+ if (firstPartyCommandDoc === null) return null;
8913
+ const host = getHost();
8914
+ if (!host) return null;
8915
+ if (firstPartyCommandDoc !== void 0) return ((typeof host.getHandles === "function" ? host.getHandles() : null)?.editing)?.tables ?? null;
8916
+ return typeof host.getCodeTableEditingCommands === "function" ? host.getCodeTableEditingCommands() : null;
8917
+ };
8853
8918
  /**
8854
8919
  * Route a table cell-context command (`tables.*`) against the live table
8855
8920
  * context resolved from the shared facade. Fails closed (`false`) when the
@@ -8866,16 +8931,14 @@ function createSuperDocUI(options) {
8866
8931
  if (!input) return false;
8867
8932
  try {
8868
8933
  if (spec.action === "insert-row-before" || spec.action === "insert-row-after") {
8869
- const host = getHost();
8870
- const tableCommands = ((typeof host?.getHandles === "function" ? host.getHandles() : null)?.editing)?.tables;
8934
+ const tableCommands = tableInsertionCommands();
8871
8935
  if (typeof tableCommands?.insertRow !== "function") return false;
8872
8936
  const options = editorMutationOptionsForRoute(descriptor.docRoute);
8873
8937
  if (options && options.success === false) return options;
8874
8938
  return options ? tableCommands.insertRow(input, options) : tableCommands.insertRow(input);
8875
8939
  }
8876
8940
  if (spec.action === "insert-column-before" || spec.action === "insert-column-after") {
8877
- const host = getHost();
8878
- const tableCommands = ((typeof host?.getHandles === "function" ? host.getHandles() : null)?.editing)?.tables;
8941
+ const tableCommands = tableInsertionCommands();
8879
8942
  if (typeof tableCommands?.insertColumn !== "function") return false;
8880
8943
  const options = editorMutationOptionsForRoute(descriptor.docRoute);
8881
8944
  if (options && options.success === false) return options;
@@ -8974,9 +9037,10 @@ function createSuperDocUI(options) {
8974
9037
  let releaseScheduledMutation = null;
8975
9038
  try {
8976
9039
  const commandEditor = getEditor();
9040
+ const commandDocSource = commandEditor?.doc;
8977
9041
  const commandMode = readDocumentMode();
8978
9042
  const mutate = () => {
8979
- if (getEditor() !== commandEditor || getDoc() !== doc || readDocumentMode() !== commandMode) return false;
9043
+ if (getEditor() !== commandEditor || commandEditor?.doc !== commandDocSource || readDocumentMode() !== commandMode) return false;
8980
9044
  return callInlineFormatMutation(route, op, input);
8981
9045
  };
8982
9046
  const selectionSignature = selectionInlineValueSignature(state.selection) ?? selectionKey(state.selection);
@@ -9026,12 +9090,14 @@ function createSuperDocUI(options) {
9026
9090
  return false;
9027
9091
  }
9028
9092
  };
9029
- const executeCommand = (id, payload, context) => {
9093
+ const runCommand = (id, payload, context, firstParty) => {
9030
9094
  const actionId = beginInteraction(superdoc, "command:started", () => ({
9031
9095
  commandId: id,
9032
9096
  selection: state.selection
9033
9097
  }));
9034
9098
  let result;
9099
+ const previousCommandDoc = firstPartyCommandDoc;
9100
+ firstPartyCommandDoc = firstParty && !customCommands.has(id) ? createFirstPartyCommandDoc() : void 0;
9035
9101
  try {
9036
9102
  result = executeCommandInternal(id, payload, context);
9037
9103
  } catch (error) {
@@ -9041,6 +9107,8 @@ function createSuperDocUI(options) {
9041
9107
  outcome: "threw"
9042
9108
  }));
9043
9109
  throw error;
9110
+ } finally {
9111
+ firstPartyCommandDoc = previousCommandDoc;
9044
9112
  }
9045
9113
  const settled = (value) => recordInteraction(superdoc, "command:settled", () => ({
9046
9114
  actionId,
@@ -9054,8 +9122,10 @@ function createSuperDocUI(options) {
9054
9122
  } catch {}
9055
9123
  return result;
9056
9124
  };
9125
+ const executeCommand = (id, payload, context) => runCommand(id, payload, context, false);
9057
9126
  const exitFormatPainter = () => {
9058
9127
  painterCaptureEpoch += 1;
9128
+ painterCommandDoc = null;
9059
9129
  painter = {
9060
9130
  mode: "idle",
9061
9131
  snapshot: null,
@@ -9068,6 +9138,7 @@ function createSuperDocUI(options) {
9068
9138
  recompute();
9069
9139
  };
9070
9140
  const executeCopyFormat = async () => {
9141
+ const commandDoc = firstPartyCommandDoc ?? null;
9071
9142
  const visibleActiveMarksAtClick = [
9072
9143
  "bold",
9073
9144
  "italic",
@@ -9110,6 +9181,7 @@ function createSuperDocUI(options) {
9110
9181
  sourceSelectionKey: selectionKey(captureSlice),
9111
9182
  lastClickAt: now
9112
9183
  };
9184
+ painterCommandDoc = commandDoc;
9113
9185
  for (const cb of painterModeListeners) cb(painter.mode);
9114
9186
  recompute();
9115
9187
  return true;
@@ -9293,7 +9365,7 @@ function createSuperDocUI(options) {
9293
9365
  if (painter.mode === "armed") exitFormatPainter();
9294
9366
  return;
9295
9367
  }
9296
- const doc = getDoc();
9368
+ const doc = painterCommandDoc ?? getDoc();
9297
9369
  if (!doc) return;
9298
9370
  const targetSelection = selectionSliceFromInfo(selection, "ready");
9299
9371
  const { lockModesById } = computeActiveContentControlLockModes(doc.contentControls, targetSelection);
@@ -9398,18 +9470,20 @@ function createSuperDocUI(options) {
9398
9470
  if (painter.mode === "armed") exitFormatPainter();
9399
9471
  recompute();
9400
9472
  };
9401
- const executeCommandAsync = (id, payload, context) => {
9473
+ const runCommandAsync = (id, payload, context, firstParty) => {
9402
9474
  const prepared = prepareCommandSelectionAsync(id, payload);
9403
9475
  const settlement = prepared ? prepared.then(() => {
9404
- executeCommand(id, payload, context);
9476
+ runCommand(id, payload, context, firstParty);
9405
9477
  return lastCommandSettlement;
9406
9478
  }) : (() => {
9407
- executeCommand(id, payload, context);
9479
+ runCommand(id, payload, context, firstParty);
9408
9480
  return lastCommandSettlement;
9409
9481
  })();
9410
9482
  if (getCommandDescriptor(id)?.inline?.kind === "toggle") lastInlineToggleCommandSettlement = settlement;
9411
9483
  return settlement;
9412
9484
  };
9485
+ const executeCommandAsync = (id, payload, context) => runCommandAsync(id, payload, context, false);
9486
+ const executeFirstPartyCommandAsync = (id, payload) => runCommandAsync(id, payload, void 0, true);
9413
9487
  const makeCommandHandle = (id) => ({
9414
9488
  id,
9415
9489
  getState: () => state.toolbar.commands[id] ?? computeCommandState(id, getDoc(), state.selection, projectSelectionInlineValues(state.selection)),
@@ -10246,6 +10320,14 @@ function createSuperDocUI(options) {
10246
10320
  kind,
10247
10321
  scope: isAllTarget ? "all" : "id"
10248
10322
  }, tcApi)) return false;
10323
+ if (firstPartyCommandDoc) {
10324
+ const host = getHost();
10325
+ const decide = (safeCall(typeof host?.getHandles === "function" ? () => host.getHandles() : void 0, null)?.trackChanges)?.decide;
10326
+ return typeof decide === "function" ? decide({
10327
+ decision: kind,
10328
+ target
10329
+ }) : false;
10330
+ }
10249
10331
  const legacyName = isAllTarget ? `${kind}All` : kind;
10250
10332
  const op = !isRangeTarget && !isMultiIdTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
10251
10333
  const decide = tcApi && typeof tcApi.decide === "function" ? tcApi.decide : null;
@@ -10549,10 +10631,18 @@ function createSuperDocUI(options) {
10549
10631
  const hits = [];
10550
10632
  for (const hit of rawHits) {
10551
10633
  if (hit.type !== "trackedChange") {
10552
- const key = `${hit.type}:${hit.id}`;
10634
+ const layoutStory = hit.type === "contentControl" ? layoutStoryDatasetToStoryLocator(hit.story) : void 0;
10635
+ const story = hit.type === "contentControl" ? isTextboxStory(layoutStory) ? layoutStory : storyKeyToStoryLocator(hit.storyKey) ?? layoutStory : void 0;
10636
+ const key = `${hit.type}:${hit.id}:${JSON.stringify(story ?? null)}`;
10553
10637
  if (seen.has(key)) continue;
10554
10638
  seen.add(key);
10555
- hits.push(hit);
10639
+ if (hit.type === "contentControl") {
10640
+ const { storyKey: _storyKey, story: _paintedStory, ...publicHit } = hit;
10641
+ hits.push(story ? {
10642
+ ...publicHit,
10643
+ story
10644
+ } : publicHit);
10645
+ } else hits.push(hit);
10556
10646
  continue;
10557
10647
  }
10558
10648
  const story = storyKeyToStoryLocator(hit.storyKey) ?? layoutStoryDatasetToStoryLocator(hit.story);
@@ -11522,6 +11612,7 @@ function createSuperDocUI(options) {
11522
11612
  listeners.clear();
11523
11613
  searchListeners.clear();
11524
11614
  painterCaptureEpoch += 1;
11615
+ painterCommandDoc = null;
11525
11616
  painterModeListeners.clear();
11526
11617
  searchRequestGeneration += 1;
11527
11618
  customCommands.clear();
@@ -11591,6 +11682,7 @@ function createSuperDocUI(options) {
11591
11682
  destroy
11592
11683
  };
11593
11684
  controllerRef = controller;
11685
+ firstPartyCommandExecutors.set(controller, executeFirstPartyCommandAsync);
11594
11686
  return controller;
11595
11687
  }
11596
11688
  /**
@@ -11756,4 +11848,4 @@ function commandIsActive(descriptor, selection) {
11756
11848
  return descriptor.activeMark ? selection.activeMarks.includes(descriptor.activeMark) : false;
11757
11849
  }
11758
11850
  //#endregion
11759
- export { getV2TrackedChangeMutationImpact as a, createInteractionHistory as c, applyV2CommentInvalidationFromMutation as i, recordInteraction as l, shallowEqual as n, DOM_CLASS_NAMES as o, BUILT_IN_COMMAND_IDS as r, closeInteractionHistory as s, createSuperDocUI as t };
11851
+ export { applyV2CommentInvalidationFromMutation as a, DOM_CLASS_NAMES as c, recordInteraction as d, BUILT_IN_COMMAND_IDS as i, closeInteractionHistory as l, executeFirstPartyCommandAsync as n, getV2TrackedChangeMutationImpact as o, registerFirstPartyCommandMutation as r, shallowEqual as s, createSuperDocUI as t, createInteractionHistory as u };