superdoc 2.4.0-next.17 → 2.4.0-next.18

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.
@@ -19,7 +19,7 @@ var COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
19
19
  var PRIVATE_ENGINE_INFO = (0, _superdoc_docx_engine_collaboration_upgrade_engine.getCollaborationUpgradeEngineInfo)();
20
20
  var ENGINE_INFO = Object.freeze({
21
21
  ...PRIVATE_ENGINE_INFO,
22
- superdocVersion: "2.4.0-next.17",
22
+ superdocVersion: "2.4.0-next.18",
23
23
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
24
24
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
25
25
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
@@ -18,7 +18,7 @@ var COLLABORATION_UPGRADE_ENGINE_MINIMUM_NODE_MAJOR = 20;
18
18
  var PRIVATE_ENGINE_INFO = getCollaborationUpgradeEngineInfo$1();
19
19
  var ENGINE_INFO = Object.freeze({
20
20
  ...PRIVATE_ENGINE_INFO,
21
- superdocVersion: "2.4.0-next.17",
21
+ superdocVersion: "2.4.0-next.18",
22
22
  roomSchemaVersion: Object.freeze({ ...PRIVATE_ENGINE_INFO.roomSchemaVersion }),
23
23
  supportedBundleVersions: SUPPORTED_COLLABORATION_UPGRADE_BUNDLE_VERSIONS,
24
24
  supportedV1ReaderContractVersions: SUPPORTED_V1_READER_CONTRACT_VERSIONS
package/dist/superdoc.cjs CHANGED
@@ -8562,6 +8562,61 @@ var useCommentsStore = defineStore("comments", () => {
8562
8562
  syncCommentsToClients(superdoc, event);
8563
8563
  return Promise.resolve({ ok: true });
8564
8564
  };
8565
+ const applyV2ThreadLifecycleReceipt = ({ superdoc, documentId, lifecycle } = {}) => {
8566
+ const commentId = normalizeCommentId(lifecycle?.commentId);
8567
+ const status = lifecycle?.status;
8568
+ if (!commentId || status !== "open" && status !== "resolved") return null;
8569
+ const normalizedDocumentId = normalizeCommentId(documentId);
8570
+ const rows = commentsList.value.filter((comment) => {
8571
+ if (!comment || comment.trackedChange === true || isV2SyntheticTrackedChangeRow(comment)) return false;
8572
+ const rowDocumentId = normalizeCommentId(comment.fileId);
8573
+ return normalizedDocumentId == null || rowDocumentId == null || rowDocumentId === normalizedDocumentId;
8574
+ });
8575
+ const byAlias = /* @__PURE__ */ new Map();
8576
+ const childrenByParentAlias = /* @__PURE__ */ new Map();
8577
+ for (const row of rows) {
8578
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8579
+ const parentId = normalizeCommentId(row.parentCommentId);
8580
+ if (parentId) {
8581
+ const children = childrenByParentAlias.get(parentId) ?? [];
8582
+ children.push(row);
8583
+ childrenByParentAlias.set(parentId, children);
8584
+ }
8585
+ }
8586
+ let root = byAlias.get(commentId) ?? null;
8587
+ const seenParents = /* @__PURE__ */ new Set();
8588
+ while (root?.parentCommentId != null) {
8589
+ const parentId = normalizeCommentId(root.parentCommentId);
8590
+ if (!parentId || seenParents.has(parentId)) break;
8591
+ seenParents.add(parentId);
8592
+ const parent = byAlias.get(parentId);
8593
+ if (!parent) break;
8594
+ root = parent;
8595
+ }
8596
+ if (!root) return null;
8597
+ const family = [];
8598
+ const queue = [root];
8599
+ const visited = /* @__PURE__ */ new Set();
8600
+ while (queue.length > 0) {
8601
+ const row = queue.shift();
8602
+ if (!row || visited.has(row)) continue;
8603
+ visited.add(row);
8604
+ family.push(row);
8605
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8606
+ }
8607
+ const isResolved = status === "resolved";
8608
+ const resolvedAt = Date.now();
8609
+ for (const row of family) {
8610
+ row.resolvedTime = isResolved ? row.resolvedTime ?? resolvedAt : null;
8611
+ row.resolvedById = isResolved ? superdoc?.user?.id ?? null : null;
8612
+ row.resolvedByEmail = isResolved ? superdoc?.user?.email ?? null : null;
8613
+ row.resolvedByName = isResolved ? superdoc?.user?.name ?? null : null;
8614
+ }
8615
+ return {
8616
+ added: null,
8617
+ lifecycleUpdated: family.length
8618
+ };
8619
+ };
8565
8620
  const runV2CommentMutation = async ({ superdoc, adapter, fileId, operation, eventType, rejectionFallbackReason, rejectionEventExtras = {}, validateOutcome, successEventBuilder }) => {
8566
8621
  let outcome;
8567
8622
  try {
@@ -8611,10 +8666,15 @@ var useCommentsStore = defineStore("comments", () => {
8611
8666
  superdoc?.emit?.("comments-update", rejectedEvent);
8612
8667
  return failedOutcome;
8613
8668
  }
8614
- const reconciled = reconcileCommentsFromV2({
8669
+ const documentId = adapter.documentId ?? fileId;
8670
+ const reconciled = applyV2ThreadLifecycleReceipt({
8671
+ superdoc,
8672
+ documentId,
8673
+ lifecycle: outcome.threadLifecycle
8674
+ }) ?? reconcileCommentsFromV2({
8615
8675
  superdoc,
8616
8676
  adapter,
8617
- documentId: adapter.documentId ?? fileId,
8677
+ documentId,
8618
8678
  items: outcome.items ?? [],
8619
8679
  pruneStale: outcome.complete !== false
8620
8680
  });
@@ -8626,7 +8686,11 @@ var useCommentsStore = defineStore("comments", () => {
8626
8686
  return {
8627
8687
  ok: true,
8628
8688
  items: outcome.items ?? [],
8629
- reconciled
8689
+ reconciled,
8690
+ ...outcome.complete === false ? { complete: false } : {},
8691
+ ...outcome.visibleWindowSource != null ? { visibleWindowSource: outcome.visibleWindowSource } : {},
8692
+ ...outcome.threadLifecycle != null ? { threadLifecycle: outcome.threadLifecycle } : {},
8693
+ ...outcome.mutationPath != null ? { mutationPath: outcome.mutationPath } : {}
8630
8694
  };
8631
8695
  };
8632
8696
  const mapV2OutcomeCommentInputs = ({ outcome, adapter, fileId }) => {
@@ -8642,6 +8706,23 @@ var useCommentsStore = defineStore("comments", () => {
8642
8706
  const commentInputParentId = (input) => input?.parentCommentId != null ? String(input.parentCommentId) : null;
8643
8707
  const validateV2ThreadLifecycleRefresh = ({ outcome, adapter, fileId, commentId, expectedResolved, operation }) => {
8644
8708
  const id = commentId != null ? String(commentId) : null;
8709
+ const receiptLifecycle = outcome?.threadLifecycle;
8710
+ if (receiptLifecycle != null) {
8711
+ const observedId = normalizeCommentId(receiptLifecycle.commentId);
8712
+ const observedResolved = receiptLifecycle.status === "resolved";
8713
+ if (observedId === id && observedResolved === expectedResolved) return { ok: true };
8714
+ return {
8715
+ ok: false,
8716
+ reason: `v2-${operation}-receipt-lifecycle-mismatch`,
8717
+ detail: {
8718
+ expected: {
8719
+ commentId: id,
8720
+ status: expectedResolved ? "resolved" : "open"
8721
+ },
8722
+ observed: receiptLifecycle
8723
+ }
8724
+ };
8725
+ }
8645
8726
  const inputs = mapV2OutcomeCommentInputs({
8646
8727
  outcome,
8647
8728
  adapter,
@@ -8787,8 +8868,8 @@ var useCommentsStore = defineStore("comments", () => {
8787
8868
  * Resolve an existing comment through the v2 adapter.
8788
8869
  *
8789
8870
  * Plan §4.3 rules:
8790
- * - successful resolve refreshes from v2 list and clears the active
8791
- * comment / dialog target if the resolved comment was active
8871
+ * - successful resolve applies the committed lifecycle receipt to the
8872
+ * hydrated thread and clears the active comment / dialog target
8792
8873
  * - rejection leaves the row active, emits a rejected event
8793
8874
  * - active state must never reference a deleted/missing anchor — the
8794
8875
  * reconciler is already family-scoped (see TCS 001 §5)
@@ -8846,18 +8927,19 @@ var useCommentsStore = defineStore("comments", () => {
8846
8927
  *
8847
8928
  * Symmetric inverse of {@link resolveCommentV2}. The adapter routes the
8848
8929
  * reopen through `activeEditor.doc.comments.patch({ status: 'active' })`,
8849
- * which removes the resolved anchors and restores the live comment mark,
8850
- * then refreshes from the v2 comment list. Rules:
8930
+ * which removes the resolved anchors and restores the live comment mark.
8931
+ * The successful receipt updates the hydrated thread immediately while
8932
+ * normal review hydration remains the eventual reconciliation path. Rules:
8851
8933
  * - the store owns mutation gating (via the adapter capability state),
8852
- * adapter identity stamping, refresh reconciliation, and event emission
8934
+ * adapter identity stamping, local lifecycle projection, and event emission
8853
8935
  * - there is no dedicated REOPENED event in the comment event enum, so a
8854
- * successful reopen emits an UPDATE event with the refreshed (now open)
8936
+ * successful reopen emits an UPDATE event with the now-open
8855
8937
  * comment payload
8856
8938
  * - rejection is non-mutating and emits the same rejected-event shape as
8857
8939
  * the other v2 comment mutations; the row stays resolved so the user can
8858
8940
  * retry
8859
- * - body / replies / anchor identity are preserved by the reconciler; we
8860
- * only confirm the open state after the refreshed list lands
8941
+ * - body / replies / anchor identity are preserved; only lifecycle metadata
8942
+ * changes on the hydrated thread
8861
8943
  */
8862
8944
  const reopenCommentV2 = async ({ superdoc, commentId } = {}) => {
8863
8945
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
@@ -43335,7 +43417,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43335
43417
  this.config.colors = shuffleArray(this.config.colors);
43336
43418
  this.userColorMap = /* @__PURE__ */ new Map();
43337
43419
  this.colorIndex = 0;
43338
- this.version = "2.4.0-next.17";
43420
+ this.version = "2.4.0-next.18";
43339
43421
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43340
43422
  this.superdocId = config.superdocId || require_uuid.v4();
43341
43423
  this.colors = this.config.colors ?? [];
@@ -8561,6 +8561,61 @@ var useCommentsStore = defineStore("comments", () => {
8561
8561
  syncCommentsToClients(superdoc, event);
8562
8562
  return Promise.resolve({ ok: true });
8563
8563
  };
8564
+ const applyV2ThreadLifecycleReceipt = ({ superdoc, documentId, lifecycle } = {}) => {
8565
+ const commentId = normalizeCommentId(lifecycle?.commentId);
8566
+ const status = lifecycle?.status;
8567
+ if (!commentId || status !== "open" && status !== "resolved") return null;
8568
+ const normalizedDocumentId = normalizeCommentId(documentId);
8569
+ const rows = commentsList.value.filter((comment) => {
8570
+ if (!comment || comment.trackedChange === true || isV2SyntheticTrackedChangeRow(comment)) return false;
8571
+ const rowDocumentId = normalizeCommentId(comment.fileId);
8572
+ return normalizedDocumentId == null || rowDocumentId == null || rowDocumentId === normalizedDocumentId;
8573
+ });
8574
+ const byAlias = /* @__PURE__ */ new Map();
8575
+ const childrenByParentAlias = /* @__PURE__ */ new Map();
8576
+ for (const row of rows) {
8577
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) if (!byAlias.has(alias)) byAlias.set(alias, row);
8578
+ const parentId = normalizeCommentId(row.parentCommentId);
8579
+ if (parentId) {
8580
+ const children = childrenByParentAlias.get(parentId) ?? [];
8581
+ children.push(row);
8582
+ childrenByParentAlias.set(parentId, children);
8583
+ }
8584
+ }
8585
+ let root = byAlias.get(commentId) ?? null;
8586
+ const seenParents = /* @__PURE__ */ new Set();
8587
+ while (root?.parentCommentId != null) {
8588
+ const parentId = normalizeCommentId(root.parentCommentId);
8589
+ if (!parentId || seenParents.has(parentId)) break;
8590
+ seenParents.add(parentId);
8591
+ const parent = byAlias.get(parentId);
8592
+ if (!parent) break;
8593
+ root = parent;
8594
+ }
8595
+ if (!root) return null;
8596
+ const family = [];
8597
+ const queue = [root];
8598
+ const visited = /* @__PURE__ */ new Set();
8599
+ while (queue.length > 0) {
8600
+ const row = queue.shift();
8601
+ if (!row || visited.has(row)) continue;
8602
+ visited.add(row);
8603
+ family.push(row);
8604
+ for (const alias of [row.commentId, row.importedId].map(normalizeCommentId).filter(Boolean)) queue.push(...childrenByParentAlias.get(alias) ?? []);
8605
+ }
8606
+ const isResolved = status === "resolved";
8607
+ const resolvedAt = Date.now();
8608
+ for (const row of family) {
8609
+ row.resolvedTime = isResolved ? row.resolvedTime ?? resolvedAt : null;
8610
+ row.resolvedById = isResolved ? superdoc?.user?.id ?? null : null;
8611
+ row.resolvedByEmail = isResolved ? superdoc?.user?.email ?? null : null;
8612
+ row.resolvedByName = isResolved ? superdoc?.user?.name ?? null : null;
8613
+ }
8614
+ return {
8615
+ added: null,
8616
+ lifecycleUpdated: family.length
8617
+ };
8618
+ };
8564
8619
  const runV2CommentMutation = async ({ superdoc, adapter, fileId, operation, eventType, rejectionFallbackReason, rejectionEventExtras = {}, validateOutcome, successEventBuilder }) => {
8565
8620
  let outcome;
8566
8621
  try {
@@ -8610,10 +8665,15 @@ var useCommentsStore = defineStore("comments", () => {
8610
8665
  superdoc?.emit?.("comments-update", rejectedEvent);
8611
8666
  return failedOutcome;
8612
8667
  }
8613
- const reconciled = reconcileCommentsFromV2({
8668
+ const documentId = adapter.documentId ?? fileId;
8669
+ const reconciled = applyV2ThreadLifecycleReceipt({
8670
+ superdoc,
8671
+ documentId,
8672
+ lifecycle: outcome.threadLifecycle
8673
+ }) ?? reconcileCommentsFromV2({
8614
8674
  superdoc,
8615
8675
  adapter,
8616
- documentId: adapter.documentId ?? fileId,
8676
+ documentId,
8617
8677
  items: outcome.items ?? [],
8618
8678
  pruneStale: outcome.complete !== false
8619
8679
  });
@@ -8625,7 +8685,11 @@ var useCommentsStore = defineStore("comments", () => {
8625
8685
  return {
8626
8686
  ok: true,
8627
8687
  items: outcome.items ?? [],
8628
- reconciled
8688
+ reconciled,
8689
+ ...outcome.complete === false ? { complete: false } : {},
8690
+ ...outcome.visibleWindowSource != null ? { visibleWindowSource: outcome.visibleWindowSource } : {},
8691
+ ...outcome.threadLifecycle != null ? { threadLifecycle: outcome.threadLifecycle } : {},
8692
+ ...outcome.mutationPath != null ? { mutationPath: outcome.mutationPath } : {}
8629
8693
  };
8630
8694
  };
8631
8695
  const mapV2OutcomeCommentInputs = ({ outcome, adapter, fileId }) => {
@@ -8641,6 +8705,23 @@ var useCommentsStore = defineStore("comments", () => {
8641
8705
  const commentInputParentId = (input) => input?.parentCommentId != null ? String(input.parentCommentId) : null;
8642
8706
  const validateV2ThreadLifecycleRefresh = ({ outcome, adapter, fileId, commentId, expectedResolved, operation }) => {
8643
8707
  const id = commentId != null ? String(commentId) : null;
8708
+ const receiptLifecycle = outcome?.threadLifecycle;
8709
+ if (receiptLifecycle != null) {
8710
+ const observedId = normalizeCommentId(receiptLifecycle.commentId);
8711
+ const observedResolved = receiptLifecycle.status === "resolved";
8712
+ if (observedId === id && observedResolved === expectedResolved) return { ok: true };
8713
+ return {
8714
+ ok: false,
8715
+ reason: `v2-${operation}-receipt-lifecycle-mismatch`,
8716
+ detail: {
8717
+ expected: {
8718
+ commentId: id,
8719
+ status: expectedResolved ? "resolved" : "open"
8720
+ },
8721
+ observed: receiptLifecycle
8722
+ }
8723
+ };
8724
+ }
8644
8725
  const inputs = mapV2OutcomeCommentInputs({
8645
8726
  outcome,
8646
8727
  adapter,
@@ -8786,8 +8867,8 @@ var useCommentsStore = defineStore("comments", () => {
8786
8867
  * Resolve an existing comment through the v2 adapter.
8787
8868
  *
8788
8869
  * Plan §4.3 rules:
8789
- * - successful resolve refreshes from v2 list and clears the active
8790
- * comment / dialog target if the resolved comment was active
8870
+ * - successful resolve applies the committed lifecycle receipt to the
8871
+ * hydrated thread and clears the active comment / dialog target
8791
8872
  * - rejection leaves the row active, emits a rejected event
8792
8873
  * - active state must never reference a deleted/missing anchor — the
8793
8874
  * reconciler is already family-scoped (see TCS 001 §5)
@@ -8845,18 +8926,19 @@ var useCommentsStore = defineStore("comments", () => {
8845
8926
  *
8846
8927
  * Symmetric inverse of {@link resolveCommentV2}. The adapter routes the
8847
8928
  * reopen through `activeEditor.doc.comments.patch({ status: 'active' })`,
8848
- * which removes the resolved anchors and restores the live comment mark,
8849
- * then refreshes from the v2 comment list. Rules:
8929
+ * which removes the resolved anchors and restores the live comment mark.
8930
+ * The successful receipt updates the hydrated thread immediately while
8931
+ * normal review hydration remains the eventual reconciliation path. Rules:
8850
8932
  * - the store owns mutation gating (via the adapter capability state),
8851
- * adapter identity stamping, refresh reconciliation, and event emission
8933
+ * adapter identity stamping, local lifecycle projection, and event emission
8852
8934
  * - there is no dedicated REOPENED event in the comment event enum, so a
8853
- * successful reopen emits an UPDATE event with the refreshed (now open)
8935
+ * successful reopen emits an UPDATE event with the now-open
8854
8936
  * comment payload
8855
8937
  * - rejection is non-mutating and emits the same rejected-event shape as
8856
8938
  * the other v2 comment mutations; the row stays resolved so the user can
8857
8939
  * retry
8858
- * - body / replies / anchor identity are preserved by the reconciler; we
8859
- * only confirm the open state after the refreshed list lands
8940
+ * - body / replies / anchor identity are preserved; only lifecycle metadata
8941
+ * changes on the hydrated thread
8860
8942
  */
8861
8943
  const reopenCommentV2 = async ({ superdoc, commentId } = {}) => {
8862
8944
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
@@ -43269,7 +43351,7 @@ var SuperDoc = class extends import_eventemitter3.default {
43269
43351
  this.config.colors = shuffleArray(this.config.colors);
43270
43352
  this.userColorMap = /* @__PURE__ */ new Map();
43271
43353
  this.colorIndex = 0;
43272
- this.version = "2.4.0-next.17";
43354
+ this.version = "2.4.0-next.18";
43273
43355
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43274
43356
  this.superdocId = config.superdocId || v4();
43275
43357
  this.colors = this.config.colors ?? [];