superdoc 2.0.0-next.32 → 2.0.0-next.34

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.
@@ -1,3 +1,308 @@
1
+ function getParagraphInlineDirection(attrs) {
2
+ const fromContext = attrs?.directionContext?.inlineDirection;
3
+ if (fromContext != null) return fromContext;
4
+ const ppRtl = attrs?.paragraphProperties?.rightToLeft;
5
+ if (ppRtl === true) return "rtl";
6
+ if (ppRtl === false) return "ltr";
7
+ }
8
+ var FALLBACK_PALETTE = [
9
+ "#1f6feb",
10
+ "#d1242f",
11
+ "#8250df",
12
+ "#bf3989",
13
+ "#1a7f37",
14
+ "#9a6700",
15
+ "#bc4c00",
16
+ "#0969da",
17
+ "#cf222e",
18
+ "#6639ba",
19
+ "#116329",
20
+ "#7d4e00"
21
+ ];
22
+ const authorIdentityKey = (author) => {
23
+ if (!author) return "";
24
+ return `${typeof author.name === "string" ? author.name : ""} ${typeof author.email === "string" ? author.email : ""}`;
25
+ };
26
+ var hashString = (value) => {
27
+ let hash = 2166136261;
28
+ for (let i = 0; i < value.length; i += 1) {
29
+ hash ^= value.charCodeAt(i);
30
+ hash = Math.imul(hash, 16777619);
31
+ }
32
+ return hash >>> 0;
33
+ };
34
+ const fallbackAuthorColor = (author) => {
35
+ return FALLBACK_PALETTE[hashString(authorIdentityKey(author)) % FALLBACK_PALETTE.length];
36
+ };
37
+ var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
38
+ const composeAuthorColorResolver = (config) => {
39
+ if (!config || config.enabled === false) return void 0;
40
+ const overrides = config.overrides && typeof config.overrides === "object" ? config.overrides : void 0;
41
+ const resolve = typeof config.resolve === "function" ? config.resolve : void 0;
42
+ return (author) => {
43
+ const safeAuthor = author ?? {};
44
+ if (overrides) {
45
+ if (isNonEmptyString(safeAuthor.email) && isNonEmptyString(overrides[safeAuthor.email])) return overrides[safeAuthor.email];
46
+ if (isNonEmptyString(safeAuthor.name) && isNonEmptyString(overrides[safeAuthor.name])) return overrides[safeAuthor.name];
47
+ }
48
+ if (resolve) try {
49
+ const resolved = resolve(safeAuthor);
50
+ if (isNonEmptyString(resolved)) return resolved;
51
+ } catch {}
52
+ return fallbackAuthorColor(safeAuthor);
53
+ };
54
+ };
55
+ const TRACKED_CHANGE_CONFIGURABLE_SEMANTIC_COLOR_KEYS = [
56
+ "insertion",
57
+ "deletion",
58
+ "move",
59
+ "move-from",
60
+ "move-to",
61
+ "table-cell-insertion",
62
+ "table-cell-deletion",
63
+ "cell-merge",
64
+ "cell-split",
65
+ "image-insertion",
66
+ "image-deletion",
67
+ "image-property-change"
68
+ ];
69
+ var CONFIGURABLE_SEMANTIC_COLOR_KEY_SET = new Set(TRACKED_CHANGE_CONFIGURABLE_SEMANTIC_COLOR_KEYS);
70
+ const DRAWING_DIAGNOSTIC_CODES = {
71
+ externalImageDeferred: "render.media.external-image-deferred",
72
+ missingRelationship: "render.drawing.missing-relationship",
73
+ missingMediaPart: "render.media.missing-part",
74
+ unsupportedRelationshipType: "render.drawing.unsupported-relationship-type",
75
+ unsupportedMime: "render.media.unsupported-mime",
76
+ imageTooLarge: "render.media.image-too-large",
77
+ unsafeSvg: "render.media.unsafe-svg",
78
+ unsupportedFormat: "render.media.unsupported-format",
79
+ embeddedObjectNotSupported: "render.embedded-object-not-supported",
80
+ unsupportedObject: "render.drawing.unsupported-object",
81
+ vmlUnsupported: "render.drawing.vml-unsupported",
82
+ vmlImageUnsupported: "render.drawing.vml-image-unsupported",
83
+ unsupportedGeometryCommand: "render.drawing.unsupported-geometry-command",
84
+ anchorUnsupported: "render.drawing.anchor-unsupported",
85
+ wrapUnsupported: "render.drawing.wrap-unsupported",
86
+ altContentNoSupportedChoice: "render.drawing.altcontent-no-supported-choice",
87
+ groupChildUnsupported: "render.drawing.group-child-unsupported"
88
+ };
89
+ const DRAWING_DIAGNOSTIC_CODE_ALIASES = {
90
+ "render.media.missing-relationship": DRAWING_DIAGNOSTIC_CODES.missingRelationship,
91
+ "render.media.wrong-relationship-type": DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType,
92
+ "render.media.unsupported-target": DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType,
93
+ "render.media.invalid-image-size": DRAWING_DIAGNOSTIC_CODES.imageTooLarge,
94
+ "render.media-resolver-unavailable": DRAWING_DIAGNOSTIC_CODES.missingMediaPart,
95
+ "render.chart-not-supported": DRAWING_DIAGNOSTIC_CODES.unsupportedObject,
96
+ "render.textbox.vml-unsupported": DRAWING_DIAGNOSTIC_CODES.vmlUnsupported
97
+ };
98
+ const DRAWING_SUPPORT_TAXONOMY = {
99
+ inlineBitmap: {
100
+ family: "inlineBitmap",
101
+ support: "supported",
102
+ description: "wp:inline pic:pic with internal media and browser-safe MIME (PNG/JPEG; WebP/SVG once the host media resolver lands).",
103
+ contract: "ImageRun"
104
+ },
105
+ anchoredBitmap: {
106
+ family: "anchoredBitmap",
107
+ support: "supported",
108
+ description: "wp:anchor bitmap with anchor/wrap expressible by ImageAnchor/ImageWrap. Never silently inlined.",
109
+ contract: "ImageBlock"
110
+ },
111
+ imageChildInGroup: {
112
+ family: "imageChildInGroup",
113
+ support: "supported",
114
+ description: "Bitmap child of a supported DrawingML group/drawing wrapper. Supported only when the parent is.",
115
+ contract: "ImageDrawing"
116
+ },
117
+ vectorShape: {
118
+ family: "vectorShape",
119
+ support: "supported",
120
+ description: "Block/floating DrawingML preset/custom shape with the supported geometry/style subset.",
121
+ contract: "DrawingBlock",
122
+ drawingKind: "vectorShape"
123
+ },
124
+ shapeGroup: {
125
+ family: "shapeGroup",
126
+ support: "supported",
127
+ description: "DrawingML group whose rendered children are supported vector/image children.",
128
+ contract: "DrawingBlock",
129
+ drawingKind: "shapeGroup"
130
+ },
131
+ chart: {
132
+ family: "chart",
133
+ support: "supported",
134
+ description: "DrawingML chart projected into a ChartDrawing with parsed cached chart XML data. Complete Word chart fidelity remains deferred to chartFidelity.",
135
+ contract: "DrawingBlock",
136
+ drawingKind: "chart"
137
+ },
138
+ alternateContent: {
139
+ family: "alternateContent",
140
+ support: "supported",
141
+ description: "mc:AlternateContent traversal/select policy. Selected Choice/Fallback maps to its family contract; unselected branches preserved for save/reopen.",
142
+ contract: "selected-choice"
143
+ },
144
+ externalImage: {
145
+ family: "externalImage",
146
+ support: "fail-closed",
147
+ description: "External image relationship (r:link / TargetMode=\"External\"). Not fetched.",
148
+ contract: "none",
149
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.externalImageDeferred
150
+ },
151
+ missingRelationship: {
152
+ family: "missingRelationship",
153
+ support: "fail-closed",
154
+ description: "Drawing references a relationship id absent from the owner part .rels.",
155
+ contract: "none",
156
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.missingRelationship
157
+ },
158
+ missingMediaPart: {
159
+ family: "missingMediaPart",
160
+ support: "fail-closed",
161
+ description: "Relationship resolves but the target media part bytes are missing or empty.",
162
+ contract: "none",
163
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.missingMediaPart
164
+ },
165
+ wrongRelationshipType: {
166
+ family: "wrongRelationshipType",
167
+ support: "fail-closed",
168
+ description: "Relationship exists but is not an image relationship type.",
169
+ contract: "none",
170
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedRelationshipType
171
+ },
172
+ unsupportedMime: {
173
+ family: "unsupportedMime",
174
+ support: "fail-closed",
175
+ description: "Media MIME outside the supported allowlist (incl. GIF/BMP/ICO).",
176
+ contract: "none",
177
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedMime
178
+ },
179
+ oversizedMediaPart: {
180
+ family: "oversizedMediaPart",
181
+ support: "fail-closed",
182
+ description: "Media part exceeds the host byte-size policy before bytes are materialized.",
183
+ contract: "none",
184
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.imageTooLarge
185
+ },
186
+ unsafeSvg: {
187
+ family: "unsafeSvg",
188
+ support: "fail-closed",
189
+ description: "SVG content rejected by the SVG safety policy.",
190
+ contract: "none",
191
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsafeSvg
192
+ },
193
+ metafileOrTiff: {
194
+ family: "metafileOrTiff",
195
+ support: "fail-closed",
196
+ description: "EMF/WMF/TIFF and other non-browser-native formats with no approved conversion path.",
197
+ contract: "none",
198
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedFormat
199
+ },
200
+ embeddedObject: {
201
+ family: "embeddedObject",
202
+ support: "fail-closed",
203
+ description: "OLE / ActiveX / embedded package object.",
204
+ contract: "none",
205
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.embeddedObjectNotSupported
206
+ },
207
+ smartArtOrDiagram: {
208
+ family: "smartArtOrDiagram",
209
+ support: "fail-closed",
210
+ description: "SmartArt / diagram / unknown external graphic-data object.",
211
+ contract: "none",
212
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedObject
213
+ },
214
+ vml: {
215
+ family: "vml",
216
+ support: "fail-closed",
217
+ description: "VML structure outside any supported image-like subset (default fail-closed policy).",
218
+ contract: "none",
219
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.vmlUnsupported
220
+ },
221
+ vmlImageLike: {
222
+ family: "vmlImageLike",
223
+ support: "fail-closed",
224
+ 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.",
225
+ contract: "none",
226
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.vmlImageUnsupported
227
+ },
228
+ unsupportedGeometryCommand: {
229
+ family: "unsupportedGeometryCommand",
230
+ support: "fail-closed",
231
+ description: "Custom geometry command the extractor does not implement, e.g. arcTo.",
232
+ contract: "none",
233
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.unsupportedGeometryCommand
234
+ },
235
+ unsupportedAnchorFields: {
236
+ family: "unsupportedAnchorFields",
237
+ support: "fail-closed",
238
+ description: "Anchor fields required for honest placement are unsupported (char-relative, alignment modes, etc.).",
239
+ contract: "none",
240
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.anchorUnsupported
241
+ },
242
+ unsupportedWrapFields: {
243
+ family: "unsupportedWrapFields",
244
+ support: "fail-closed",
245
+ description: "Wrap fields required for honest placement are unsupported (arbitrary wrap polygons, etc.).",
246
+ contract: "none",
247
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.wrapUnsupported
248
+ },
249
+ alternateContentNoSupportedChoice: {
250
+ family: "alternateContentNoSupportedChoice",
251
+ support: "fail-closed",
252
+ description: "mc:AlternateContent with no supported choice and no usable fallback.",
253
+ contract: "none",
254
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.altContentNoSupportedChoice
255
+ },
256
+ groupChildUnsupported: {
257
+ family: "groupChildUnsupported",
258
+ support: "fail-closed",
259
+ description: "Unsupported child inside an otherwise-supported group. Supported siblings are not dropped.",
260
+ contract: "none",
261
+ diagnostic: DRAWING_DIAGNOSTIC_CODES.groupChildUnsupported
262
+ },
263
+ objectEditing: {
264
+ family: "objectEditing",
265
+ support: "deferred",
266
+ description: "Editing/mutation of anchored/vector objects.",
267
+ contract: "none"
268
+ },
269
+ customWrapPolygon: {
270
+ family: "customWrapPolygon",
271
+ support: "deferred",
272
+ description: "Full Word-compatible text wrapping for arbitrary custom wrap polygons.",
273
+ contract: "none"
274
+ },
275
+ chartFidelity: {
276
+ family: "chartFidelity",
277
+ support: "deferred",
278
+ description: "Complete chart fidelity (a dedicated chart effort owns the parsed subset).",
279
+ contract: "none"
280
+ },
281
+ vmlFidelity: {
282
+ family: "vmlFidelity",
283
+ support: "deferred",
284
+ description: "Complete VML fidelity.",
285
+ contract: "none"
286
+ },
287
+ decorativeAccessibilityUi: {
288
+ family: "decorativeAccessibilityUi",
289
+ support: "deferred",
290
+ description: "Full accessibility UI for decorative image semantics beyond preserving source data.",
291
+ contract: "none"
292
+ }
293
+ };
294
+ const DRAWING_FAMILIES = Object.keys(DRAWING_SUPPORT_TAXONOMY);
295
+ const PAGE_CHECKPOINT_DEPENDENCY_CLASSES = Object.freeze([
296
+ "multiple-sections",
297
+ "furniture-page-tokens",
298
+ "non-balanceable-multi-column-sections",
299
+ "body-anchored-objects",
300
+ "footnotes",
301
+ "page-references",
302
+ "keep-constraints",
303
+ "tables",
304
+ "furniture-anchored-objects"
305
+ ]);
1
306
  function getV2TrackedChangeMutationImpact(event) {
2
307
  if (event?.type !== "mutation:committed") return null;
3
308
  const payload = event.origin === "history" ? event.result : event.receipt;
@@ -1262,6 +1567,11 @@ const HEAVY_DOC_READ_POLICY = [
1262
1567
  note: "reserved: full sections list"
1263
1568
  }
1264
1569
  ];
1570
+ var COMMENTS_CATALOG_PART_URIS = new Set([
1571
+ "/word/comments.xml",
1572
+ "/word/commentsExtended.xml",
1573
+ "/word/commentsIds.xml"
1574
+ ]);
1265
1575
  function isHeavyDocReadKey(key) {
1266
1576
  return HEAVY_DOC_READ_POLICY.some((entry) => entry.match === "exact" ? entry.key === key : key.startsWith(entry.key));
1267
1577
  }
@@ -2331,6 +2641,8 @@ function createSuperDocUI(options) {
2331
2641
  const superdoc = options.superdoc;
2332
2642
  const optimisticInlineValues = /* @__PURE__ */ new Map();
2333
2643
  const optimisticInlineToggles = /* @__PURE__ */ new Map();
2644
+ let optimisticParagraphAlignment = null;
2645
+ let optimisticParagraphAlignmentGeneration = 0;
2334
2646
  const pendingInlineToggleMutations = [];
2335
2647
  let lastOptimisticInlineSelectionSignature = null;
2336
2648
  let optimisticInlineToggleGeneration = 0;
@@ -2977,6 +3289,11 @@ function createSuperDocUI(options) {
2977
3289
  demandedHeavyReads.set(key, token);
2978
3290
  scheduleAsyncRefresh();
2979
3291
  };
3292
+ const commentsCatalogMayHaveRows = () => {
3293
+ const entry = asyncReads.get("comments");
3294
+ if (!entry?.hasSettled) return entry?.inflightToken != null;
3295
+ return Array.isArray(entry.value) && entry.value.length > 0;
3296
+ };
2980
3297
  const heavyReadDemandActive = (key, token) => demandedHeavyReads.get(key) === token;
2981
3298
  const ensureContentControlsCatalog = (_reason) => {
2982
3299
  demandHeavyDocRead("contentControls");
@@ -2993,20 +3310,28 @@ function createSuperDocUI(options) {
2993
3310
  let lastTypingMutationAtMs = 0;
2994
3311
  let heavyReadsHeldUntilIdle = false;
2995
3312
  let heavyReadsTypingHoldSinceMs = 0;
3313
+ let heavyReadCeilingReleaseActive = false;
2996
3314
  let heavyReadCompletionRecomputeTimer = null;
2997
3315
  const scheduleHeavyReadCompletionRecompute = () => {
2998
3316
  if (disposed || heavyReadCompletionRecomputeTimer) return;
2999
- const startedAtMs = Date.now();
3317
+ const startedAtMs = heavyReadsTypingHoldSinceMs || Date.now();
3000
3318
  const attempt = () => {
3001
3319
  heavyReadCompletionRecomputeTimer = null;
3002
3320
  if (disposed) return;
3003
3321
  const idleForMs = Date.now() - lastEditableMutationAtMs;
3004
- if ((foregroundMutationActive() || idleForMs < HEAVY_READ_IDLE_MS) && Date.now() - startedAtMs < HEAVY_READ_IDLE_CEILING_MS) {
3322
+ const busy = foregroundMutationActive() || idleForMs < HEAVY_READ_IDLE_MS;
3323
+ if (busy && Date.now() - startedAtMs < HEAVY_READ_IDLE_CEILING_MS) {
3005
3324
  heavyReadCompletionRecomputeTimer = setTimeout(attempt, HEAVY_READ_IDLE_POLL_MS);
3006
3325
  return;
3007
3326
  }
3008
3327
  heavyReadsHeldUntilIdle = false;
3009
- recompute();
3328
+ heavyReadCeilingReleaseActive = busy;
3329
+ try {
3330
+ recompute();
3331
+ } finally {
3332
+ heavyReadCeilingReleaseActive = false;
3333
+ heavyReadsTypingHoldSinceMs = 0;
3334
+ }
3010
3335
  };
3011
3336
  heavyReadCompletionRecomputeTimer = setTimeout(attempt, 0);
3012
3337
  };
@@ -3113,6 +3438,49 @@ function createSuperDocUI(options) {
3113
3438
  invalidateDocumentContent();
3114
3439
  recompute();
3115
3440
  };
3441
+ let pendingPostPaintContentRefresh = null;
3442
+ let postPaintContentRefreshRunning = false;
3443
+ let postPaintContentRefreshDrainToken = 0;
3444
+ const resetPostPaintContentRefresh = () => {
3445
+ pendingPostPaintContentRefresh = null;
3446
+ postPaintContentRefreshRunning = false;
3447
+ postPaintContentRefreshDrainToken += 1;
3448
+ };
3449
+ const schedulePostPaintContentRefresh = () => {
3450
+ const editor = getEditor();
3451
+ const readiness = editor?.documentMutationReadiness;
3452
+ if (!editor || typeof readiness?.whenPainted !== "function") return;
3453
+ pendingPostPaintContentRefresh = {
3454
+ editor,
3455
+ readiness,
3456
+ afterEpoch: typeof readiness.getRenderEpoch === "function" ? safeCall(() => readiness.getRenderEpoch.call(readiness), null) : null
3457
+ };
3458
+ if (postPaintContentRefreshRunning) return;
3459
+ postPaintContentRefreshRunning = true;
3460
+ const drainToken = postPaintContentRefreshDrainToken;
3461
+ const drain = async () => {
3462
+ while (!disposed && drainToken === postPaintContentRefreshDrainToken) {
3463
+ const request = pendingPostPaintContentRefresh;
3464
+ pendingPostPaintContentRefresh = null;
3465
+ if (!request) {
3466
+ postPaintContentRefreshRunning = false;
3467
+ return;
3468
+ }
3469
+ let paintCompleted = false;
3470
+ try {
3471
+ await request.readiness.whenPainted.call(request.readiness, typeof request.afterEpoch === "number" ? { afterEpoch: request.afterEpoch } : void 0);
3472
+ paintCompleted = true;
3473
+ } catch {}
3474
+ if (disposed || drainToken !== postPaintContentRefreshDrainToken) return;
3475
+ if (getEditor() !== request.editor) {
3476
+ resetPostPaintContentRefresh();
3477
+ return;
3478
+ }
3479
+ if (paintCompleted) invalidateDocumentContentAndRecompute();
3480
+ }
3481
+ };
3482
+ drain();
3483
+ };
3116
3484
  const scheduleTypingDocumentContentInvalidation = () => {
3117
3485
  if (typingContentInvalidationTimer) clearTimeout(typingContentInvalidationTimer);
3118
3486
  typingContentInvalidationTimer = setTimeout(() => {
@@ -3200,7 +3568,7 @@ function createSuperDocUI(options) {
3200
3568
  status: "pending"
3201
3569
  };
3202
3570
  }
3203
- if (heavyReadsHeldUntilIdle) {
3571
+ if (heavyReadsHeldUntilIdle && !heavyReadCeilingReleaseActive) {
3204
3572
  scheduleHeavyReadCompletionRecompute();
3205
3573
  if (entry?.hasSettled) return {
3206
3574
  value: entry.value,
@@ -3211,21 +3579,23 @@ function createSuperDocUI(options) {
3211
3579
  status: "pending"
3212
3580
  };
3213
3581
  }
3214
- const nowMs = Date.now();
3215
- if (nowMs - lastTypingMutationAtMs < HEAVY_READ_IDLE_MS && (!heavyReadsTypingHoldSinceMs || nowMs - heavyReadsTypingHoldSinceMs < HEAVY_READ_IDLE_CEILING_MS)) {
3216
- if (!heavyReadsTypingHoldSinceMs) heavyReadsTypingHoldSinceMs = nowMs;
3217
- heavyReadsHeldUntilIdle = true;
3218
- scheduleHeavyReadCompletionRecompute();
3219
- if (entry?.hasSettled) return {
3220
- value: entry.value,
3221
- status: "stale"
3222
- };
3223
- return {
3224
- value: null,
3225
- status: "pending"
3226
- };
3582
+ if (!heavyReadCeilingReleaseActive) {
3583
+ const nowMs = Date.now();
3584
+ if (nowMs - lastTypingMutationAtMs < HEAVY_READ_IDLE_MS && (!heavyReadsTypingHoldSinceMs || nowMs - heavyReadsTypingHoldSinceMs < HEAVY_READ_IDLE_CEILING_MS)) {
3585
+ if (!heavyReadsTypingHoldSinceMs) heavyReadsTypingHoldSinceMs = nowMs;
3586
+ heavyReadsHeldUntilIdle = true;
3587
+ scheduleHeavyReadCompletionRecompute();
3588
+ if (entry?.hasSettled) return {
3589
+ value: entry.value,
3590
+ status: "stale"
3591
+ };
3592
+ return {
3593
+ value: null,
3594
+ status: "pending"
3595
+ };
3596
+ }
3597
+ heavyReadsTypingHoldSinceMs = 0;
3227
3598
  }
3228
- heavyReadsTypingHoldSinceMs = 0;
3229
3599
  }
3230
3600
  if (foregroundMutationActive()) {
3231
3601
  scheduleForegroundAsyncRetry();
@@ -3297,6 +3667,7 @@ function createSuperDocUI(options) {
3297
3667
  const syncCoordinatorEditor = () => {
3298
3668
  const editor = getEditor();
3299
3669
  if (editor === lastCoordinatorEditor) return;
3670
+ resetPostPaintContentRefresh();
3300
3671
  reviewMutationReconciler.reset();
3301
3672
  reviewMutationToken = {};
3302
3673
  lastCoordinatorEditor = editor;
@@ -3310,6 +3681,7 @@ function createSuperDocUI(options) {
3310
3681
  pendingEffectiveInlineRead = null;
3311
3682
  lastEffectiveInlineReadKey = null;
3312
3683
  heldSettledInlineValues = null;
3684
+ optimisticParagraphAlignment = null;
3313
3685
  pendingSelectionSeedValidationToken = null;
3314
3686
  coldAsyncReadDeferrals.clear();
3315
3687
  demandedHeavyReads.clear();
@@ -4215,6 +4587,7 @@ function createSuperDocUI(options) {
4215
4587
  styleName: active.styleName
4216
4588
  };
4217
4589
  }
4590
+ if (descriptor.id === "text-align") return readToolbarParagraphAlignment(doc, selection$1);
4218
4591
  if (descriptor.id === "link") return readActiveLinkHref(doc, selection$1) ?? void 0;
4219
4592
  };
4220
4593
  const readListStateSnapshotForBlock = (doc, blockId, story) => {
@@ -4266,22 +4639,160 @@ function createSuperDocUI(options) {
4266
4639
  const readNodeById = (doc, blockId, story) => {
4267
4640
  if (!doc) return {
4268
4641
  value: null,
4269
- status: "ready"
4642
+ status: "ready",
4643
+ refreshing: false
4270
4644
  };
4271
4645
  const storySignature = storyLocatorSignature(story);
4272
4646
  const isBodyStory = storySignature === "story:body";
4273
4647
  if (isBodyStory && typeof doc.getNodeById !== "function") return {
4274
4648
  value: null,
4275
- status: "ready"
4649
+ status: "ready",
4650
+ refreshing: false
4276
4651
  };
4277
4652
  if (!isBodyStory && typeof doc.getNode !== "function") return {
4278
4653
  value: null,
4279
- status: "ready"
4654
+ status: "ready",
4655
+ refreshing: false
4280
4656
  };
4281
- return readAsync(`node:${storySignature}:${blockId}`, contentToken(), () => isBodyStory ? doc.getNodeById({
4657
+ const key = `node:${storySignature}:${blockId}`;
4658
+ const token = contentToken();
4659
+ const read = readAsync(key, token, () => isBodyStory ? doc.getNodeById({
4282
4660
  nodeId: blockId,
4283
4661
  nodeType: "paragraph"
4284
4662
  }) : doc.getNode(paragraphTarget(blockId, story)), (raw) => raw && typeof raw === "object" ? raw : null);
4663
+ const entry = asyncReads.get(key);
4664
+ return {
4665
+ ...read,
4666
+ refreshing: read.status === "stale" && entry?.inflightToken === token && entry.failedToken !== token
4667
+ };
4668
+ };
4669
+ const normalizeParagraphAlignment = (value) => {
4670
+ return value === "left" || value === "center" || value === "right" || value === "justify" ? value : void 0;
4671
+ };
4672
+ const isProjectionResolvedParagraphAlignment = (value) => {
4673
+ return value === "start" || value === "end" || value === "distributed" || value === "numTab" || value === "lowKashida" || value === "mediumKashida" || value === "highKashida" || value === "thaiDistribute";
4674
+ };
4675
+ const readEffectiveParagraphAlignments = (selection$1, blockIds) => {
4676
+ const host = getHost();
4677
+ const readByIds = host?.readMountedProjectionBlocksByIds;
4678
+ if (typeof readByIds !== "function") return null;
4679
+ const story = selectionStoryLocator(selection$1);
4680
+ const blocks = safeCall(() => story ? readByIds.call(host, [...blockIds], story) : readByIds.call(host, [...blockIds]), null);
4681
+ if (!Array.isArray(blocks)) return /* @__PURE__ */ new Map();
4682
+ const projectionBlocks = collectProjectionTextBlocks(blocks);
4683
+ const alignments = /* @__PURE__ */ new Map();
4684
+ for (const blockId of blockIds) {
4685
+ const block = projectionBlocks.find((candidate) => projectionBlockMatchesId(candidate, blockId));
4686
+ if (!block) continue;
4687
+ const attrs = block.attrs;
4688
+ const alignment = normalizeParagraphAlignment(attrs?.alignment);
4689
+ const inlineDirection = getParagraphInlineDirection(attrs);
4690
+ alignments.set(blockId, alignment ?? (inlineDirection === "rtl" ? "right" : "left"));
4691
+ }
4692
+ return alignments;
4693
+ };
4694
+ const readParagraphAlignment = (doc, blockId, story, effectiveAlignments) => {
4695
+ const { value: result, status, refreshing } = readNodeById(doc, blockId, story);
4696
+ const effective = effectiveAlignments?.get(blockId);
4697
+ if (status === "pending") return effective ? {
4698
+ status: "uniform",
4699
+ value: effective
4700
+ } : { status: "pending" };
4701
+ if (status === "stale" && !refreshing) return { status: "pending" };
4702
+ if (status === "stale" && effective) return {
4703
+ status: "uniform",
4704
+ value: effective
4705
+ };
4706
+ if (status === "stale" && effectiveAlignments && !effective) return { status: "unavailable" };
4707
+ if (!result) return { status: "unavailable" };
4708
+ const node = result.node;
4709
+ if (!node) return { status: "unavailable" };
4710
+ const kind = node.kind;
4711
+ if (kind !== "paragraph" && kind !== "heading") return { status: "unavailable" };
4712
+ const payload = node[kind];
4713
+ if (!payload || typeof payload !== "object") return { status: "unavailable" };
4714
+ const directAlignment = payload.props?.alignment;
4715
+ if (directAlignment == null) return effective ? {
4716
+ status: "uniform",
4717
+ value: effective
4718
+ } : { status: "unavailable" };
4719
+ const alignment = normalizeParagraphAlignment(directAlignment);
4720
+ if (alignment) return {
4721
+ status: "uniform",
4722
+ value: alignment
4723
+ };
4724
+ if (isProjectionResolvedParagraphAlignment(directAlignment) && effective) return {
4725
+ status: "uniform",
4726
+ value: effective
4727
+ };
4728
+ return { status: "unavailable" };
4729
+ };
4730
+ const readUniformParagraphAlignment = (doc, selection$1) => {
4731
+ const blockIds = selectionBlockIds(selection$1);
4732
+ if (blockIds.length === 0 || !canProbeEverySelectedBlock(blockIds)) return { status: "unavailable" };
4733
+ const story = selectionStory(selection$1);
4734
+ const effectiveAlignments = readEffectiveParagraphAlignments(selection$1, blockIds);
4735
+ const first = readParagraphAlignment(doc, blockIds[0], story, effectiveAlignments);
4736
+ if (first.status !== "uniform") return first;
4737
+ for (const blockId of blockIds.slice(1)) {
4738
+ const next = readParagraphAlignment(doc, blockId, story, effectiveAlignments);
4739
+ if (next.status !== "uniform") return next;
4740
+ if (next.value !== first.value) return { status: "mixed" };
4741
+ }
4742
+ return first;
4743
+ };
4744
+ const paragraphAlignmentSelectionSignature = (selection$1) => {
4745
+ const blockIds = selectionBlockIds(selection$1);
4746
+ if (blockIds.length === 0) return null;
4747
+ return `${storyLocatorSignature(selectionStory(selection$1))}:${blockIds.join(",")}`;
4748
+ };
4749
+ const readToolbarParagraphAlignment = (doc, selection$1) => {
4750
+ const resolution = readUniformParagraphAlignment(doc, selection$1);
4751
+ const resolved = resolution.status === "uniform" ? resolution.value : void 0;
4752
+ const optimistic = optimisticParagraphAlignment;
4753
+ if (!optimistic) return resolved;
4754
+ const selectionSignature$1 = paragraphAlignmentSelectionSignature(selection$1);
4755
+ if (selectionSignature$1 && selectionSignature$1 !== optimistic.selectionSignature) {
4756
+ optimisticParagraphAlignment = null;
4757
+ return resolved;
4758
+ }
4759
+ if (!selectionSignature$1) return optimistic.value;
4760
+ if (!optimistic.settled) return optimistic.value;
4761
+ if (resolution.status !== "pending") {
4762
+ optimisticParagraphAlignment = null;
4763
+ return resolved;
4764
+ }
4765
+ return optimistic.value;
4766
+ };
4767
+ const armOptimisticParagraphAlignment = (selection$1, payload) => {
4768
+ const alignment = normalizeParagraphAlignment(payload);
4769
+ const selectionSignature$1 = paragraphAlignmentSelectionSignature(selection$1);
4770
+ const blockIds = selectionBlockIds(selection$1);
4771
+ if (!alignment || !selectionSignature$1 || blockIds.length === 0) return null;
4772
+ const generation = ++optimisticParagraphAlignmentGeneration;
4773
+ optimisticParagraphAlignment = {
4774
+ selectionSignature: selectionSignature$1,
4775
+ value: alignment,
4776
+ generation,
4777
+ settled: false,
4778
+ canReconcile: canProbeEverySelectedBlock(blockIds)
4779
+ };
4780
+ recompute();
4781
+ return generation;
4782
+ };
4783
+ const settleOptimisticParagraphAlignment = (generation, result) => {
4784
+ if (generation == null) return;
4785
+ const optimistic = optimisticParagraphAlignment;
4786
+ if (!optimistic || optimistic.generation !== generation) return;
4787
+ if (!commandResultSucceeded(result)) {
4788
+ optimisticParagraphAlignment = null;
4789
+ return;
4790
+ }
4791
+ if (!optimistic.canReconcile) {
4792
+ optimisticParagraphAlignment = null;
4793
+ return;
4794
+ }
4795
+ optimistic.settled = true;
4285
4796
  };
4286
4797
  const resolveListMembershipForBlockAsync = async (doc, blockId, story) => {
4287
4798
  const listsApi = doc?.lists;
@@ -4932,12 +5443,12 @@ function createSuperDocUI(options) {
4932
5443
  }
4933
5444
  return;
4934
5445
  }
5446
+ if (type === "mutation:committed" || type === "collaboration:remote-changed") lastEditableMutationAtMs = Date.now();
4935
5447
  if (type === "mutation:committed") {
4936
- lastEditableMutationAtMs = Date.now();
4937
5448
  clearAuthoritativeTrackChangesRetry();
4938
5449
  reviewMutationToken = {};
4939
5450
  }
4940
- if (type === "mutation:committed" || type === "save:completed") {
5451
+ if (type === "mutation:committed" || type === "save:completed" || type === "collaboration:remote-changed") {
4941
5452
  const impact = type === "mutation:committed" ? getV2TrackedChangeMutationImpact(event) : null;
4942
5453
  const supersedesPendingReviewReconcile = type === "mutation:committed" && reviewMutationReconciler.getPendingIds().size > 0;
4943
5454
  if (impact?.removedIds.size) markPostDecisionTrackChanges(new Set(impact.removedIds), event.receipt);
@@ -4967,13 +5478,20 @@ function createSuperDocUI(options) {
4967
5478
  }
4968
5479
  }
4969
5480
  if (authoritativeHistoryImpact) return;
4970
- if (type === "mutation:committed" && isEditableTextMutationEvent(event)) {
4971
- lastTypingMutationAtMs = Date.now();
5481
+ const isTypingBurst = type === "mutation:committed" && isEditableTextMutationEvent(event) || type === "collaboration:remote-changed" && Array.isArray(event.changedStoryIds) && event.changedStoryIds.length > 0;
5482
+ if (isTypingBurst) lastTypingMutationAtMs = Date.now();
5483
+ if (type === "mutation:committed" && isTypingBurst) {
4972
5484
  scheduleTypingDocumentContentInvalidation();
4973
5485
  return;
4974
5486
  }
4975
5487
  if (impact?.removedIds.size && impact.upsertIds.size === 0) return;
4976
- invalidateDocumentContentAndRecompute();
5488
+ if (type === "collaboration:remote-changed" || type === "mutation:committed" && !isTypingBurst) schedulePostPaintContentRefresh();
5489
+ const remoteCommentsPartChanged = type === "collaboration:remote-changed" && Array.isArray(event.changedPartUris) && event.changedPartUris.some((partUri) => typeof partUri === "string" && COMMENTS_CATALOG_PART_URIS.has(partUri));
5490
+ const loadedCommentAnchorsMayHaveChanged = type === "collaboration:remote-changed" && Array.isArray(event.changedStoryIds) && event.changedStoryIds.length > 0 && commentsCatalogMayHaveRows();
5491
+ if (remoteCommentsPartChanged && !isTypingBurst) heavyReadsHeldUntilIdle = true;
5492
+ invalidateDocumentContent();
5493
+ if (remoteCommentsPartChanged || loadedCommentAnchorsMayHaveChanged) demandHeavyDocRead("comments");
5494
+ recompute();
4977
5495
  }
4978
5496
  });
4979
5497
  if (typeof off === "function") detachHostEvents = off;
@@ -6100,7 +6618,13 @@ function createSuperDocUI(options) {
6100
6618
  return false;
6101
6619
  }
6102
6620
  }
6103
- if (descriptor.blockParagraph) return settleCommandExecution(executeBlockParagraph(descriptor, op, normalized));
6621
+ if (descriptor.blockParagraph) {
6622
+ const result = executeBlockParagraph(descriptor, op, normalized);
6623
+ const optimisticAlignmentGeneration = descriptor.blockParagraph.kind === "alignment" && commandResultSucceeded(result) ? armOptimisticParagraphAlignment(state.selection, normalized) : null;
6624
+ return settleCommandExecution(result, (settled) => {
6625
+ settleOptimisticParagraphAlignment(optimisticAlignmentGeneration, settled);
6626
+ });
6627
+ }
6104
6628
  if (descriptor.list) return settleCommandExecution(executeListCommand(descriptor, op));
6105
6629
  if (descriptor.link) return settleCommandExecution(executeLinkCommand(normalized));
6106
6630
  if (descriptor.create) return settleCommandExecution(executeCreateCommand(descriptor, op, normalized));
@@ -8206,6 +8730,12 @@ Object.defineProperty(exports, "BUILT_IN_COMMAND_IDS", {
8206
8730
  return BUILT_IN_COMMAND_IDS;
8207
8731
  }
8208
8732
  });
8733
+ Object.defineProperty(exports, "composeAuthorColorResolver", {
8734
+ enumerable: true,
8735
+ get: function() {
8736
+ return composeAuthorColorResolver;
8737
+ }
8738
+ });
8209
8739
  Object.defineProperty(exports, "createSuperDocUI", {
8210
8740
  enumerable: true,
8211
8741
  get: function() {