@ai-matrx/office 0.8.24 → 0.8.25

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.
@@ -605,6 +605,12 @@ interface SheetsOtPort {
605
605
  service: unknown;
606
606
  unitId: string;
607
607
  }>;
608
+ /**
609
+ * Run `fn` right before this seat takes an undo or redo (synchronously, before the command reads its stack). With
610
+ * it the room rebases the undo stack lazily: a remote op only queues, and an undo catches its stack up first.
611
+ * Without it every remote op rebases the whole stack at once.
612
+ */
613
+ beforeUndo?(fn: () => void): () => void;
608
614
  /** Ask for a save (the record's one coalesced save). */
609
615
  markDirty(): void;
610
616
  }
@@ -657,6 +663,8 @@ declare function rebaseSheetUndoStack(items: UndoItem[], remote: SheetOp, unitId
657
663
  interface SheetsOtAdapterHooks {
658
664
  /** Called once per applied remote op that rebased this seat's undo stacks (for tests and logs). */
659
665
  onUndoRebased?: (dropped: number) => void;
666
+ /** Called with the milliseconds of undo bookkeeping each remote op (and each catch-up) cost (tests, logs). */
667
+ onUndoWork?: (ms: number, kind: "remote" | "catch_up") => void;
660
668
  /**
661
669
  * The state hash of a snapshot (default `sheetsStateHash`, Web Crypto). A seeded test passes a synchronous one:
662
670
  * Web Crypto resolves on a worker thread, outside a virtual clock, so the same seed would not replay.
@@ -605,6 +605,12 @@ interface SheetsOtPort {
605
605
  service: unknown;
606
606
  unitId: string;
607
607
  }>;
608
+ /**
609
+ * Run `fn` right before this seat takes an undo or redo (synchronously, before the command reads its stack). With
610
+ * it the room rebases the undo stack lazily: a remote op only queues, and an undo catches its stack up first.
611
+ * Without it every remote op rebases the whole stack at once.
612
+ */
613
+ beforeUndo?(fn: () => void): () => void;
608
614
  /** Ask for a save (the record's one coalesced save). */
609
615
  markDirty(): void;
610
616
  }
@@ -657,6 +663,8 @@ declare function rebaseSheetUndoStack(items: UndoItem[], remote: SheetOp, unitId
657
663
  interface SheetsOtAdapterHooks {
658
664
  /** Called once per applied remote op that rebased this seat's undo stacks (for tests and logs). */
659
665
  onUndoRebased?: (dropped: number) => void;
666
+ /** Called with the milliseconds of undo bookkeeping each remote op (and each catch-up) cost (tests, logs). */
667
+ onUndoWork?: (ms: number, kind: "remote" | "catch_up") => void;
660
668
  /**
661
669
  * The state hash of a snapshot (default `sheetsStateHash`, Web Crypto). A seeded test passes a synchronous one:
662
670
  * Web Crypto resolves on a worker thread, outside a virtual clock, so the same seed would not replay.
@@ -3500,17 +3500,65 @@ function rebaseSheetUndoStack(items, remote, unitId) {
3500
3500
  function createSheetsOtAdapter(port, hooks = {}) {
3501
3501
  let queue = [];
3502
3502
  let draining = false;
3503
+ const lazy = typeof port.beforeUndo === "function";
3504
+ let pending = [];
3505
+ let pendingBase = 0;
3506
+ let applied = 0;
3507
+ let currentAt = /* @__PURE__ */ new WeakMap();
3508
+ const PENDING_CAP = 2e3;
3509
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
3510
+ const stacksOfTargets = () => port.undoTargets().flatMap((t) => {
3511
+ const stacks = undoStacksOf2(t.service);
3512
+ return stacks ? [stacks.undo, stacks.redo].map((map) => ({ items: map.get(t.unitId), unitId: t.unitId, isUndo: map === stacks.undo })) : [];
3513
+ });
3514
+ const markNew = (items) => {
3515
+ for (let i = items.length - 1; i >= 0 && !currentAt.has(items[i]); i--) currentAt.set(items[i], applied);
3516
+ };
3517
+ const catchUpItem = (item, unitId) => {
3518
+ const from = currentAt.get(item) ?? applied;
3519
+ for (let k = from - pendingBase; k < pending.length; k++) if (rebaseSheetUndoStack([item], pending[k], unitId) > 0) return false;
3520
+ currentAt.set(item, applied);
3521
+ return true;
3522
+ };
3523
+ const catchUp = () => {
3524
+ if (!pending.length) return;
3525
+ const t0 = now();
3526
+ let dropped = 0;
3527
+ for (const { items, unitId } of stacksOfTargets()) {
3528
+ if (!items?.length) continue;
3529
+ for (let i = items.length - 1; i >= 0; i--)
3530
+ if (!catchUpItem(items[i], unitId)) {
3531
+ items.splice(i, 1);
3532
+ dropped++;
3533
+ }
3534
+ }
3535
+ pending = [];
3536
+ pendingBase = applied;
3537
+ hooks.onUndoWork?.(now() - t0, "catch_up");
3538
+ if (dropped) hooks.onUndoRebased?.(dropped);
3539
+ };
3540
+ if (lazy) port.beforeUndo(catchUp);
3503
3541
  const applyNow = (op) => {
3504
3542
  for (const m of op) port.applyRemote(m);
3543
+ const t0 = now();
3505
3544
  let dropped = 0;
3506
- for (const t of port.undoTargets()) {
3507
- const stacks = undoStacksOf2(t.service);
3508
- if (!stacks) continue;
3509
- for (const map of [stacks.undo, stacks.redo]) {
3510
- const items = map.get(t.unitId);
3511
- if (items?.length) dropped += rebaseSheetUndoStack(items, op, t.unitId);
3545
+ if (!lazy) {
3546
+ for (const { items, unitId } of stacksOfTargets()) if (items?.length) dropped += rebaseSheetUndoStack(items, op, unitId);
3547
+ } else if (!isNoopSheetOp(op)) {
3548
+ for (const { items, unitId, isUndo } of stacksOfTargets()) {
3549
+ if (!items?.length) continue;
3550
+ markNew(items);
3551
+ const top = items[items.length - 1];
3552
+ if (isUndo && (!catchUpItem(top, unitId) || rebaseSheetUndoStack([top], op, unitId) > 0)) {
3553
+ items.pop();
3554
+ dropped++;
3555
+ } else if (isUndo) currentAt.set(top, applied + 1);
3512
3556
  }
3557
+ pending.push(op);
3558
+ applied++;
3559
+ if (pending.length > PENDING_CAP) catchUp();
3513
3560
  }
3561
+ hooks.onUndoWork?.(now() - t0, "remote");
3514
3562
  hooks.onUndoRebased?.(dropped);
3515
3563
  port.markDirty();
3516
3564
  };
@@ -3548,6 +3596,9 @@ function createSheetsOtAdapter(port, hooks = {}) {
3548
3596
  stacks?.undo.delete(t.unitId);
3549
3597
  stacks?.redo.delete(t.unitId);
3550
3598
  }
3599
+ pending = [];
3600
+ pendingBase = applied;
3601
+ currentAt = /* @__PURE__ */ new WeakMap();
3551
3602
  },
3552
3603
  snapshot: () => port.snapshot(),
3553
3604
  async hash() {
@@ -3735,6 +3786,8 @@ var REMOVE_MERGE = "sheet.mutation.remove-worksheet-merge";
3735
3786
  var SHEET_NAME = "sheet.mutation.set-worksheet-name";
3736
3787
  var INSERT_SHEET = "sheet.mutation.insert-sheet";
3737
3788
  var RESHAPE = /* @__PURE__ */ new Set(["sheet.mutation.set-worksheet-row-count", "sheet.mutation.set-worksheet-column-count", "sheet.mutation.remove-sheet", INSERT_SHEET]);
3789
+ var UNDO = "univer.command.undo";
3790
+ var REDO = "univer.command.redo";
3738
3791
  var DEPTH_GUARD = /* @__PURE__ */ Symbol.for("ai-matrx.office.sheets.command-depth-guard");
3739
3792
  var json = (v) => v === void 0 ? v : JSON.parse(JSON.stringify(v));
3740
3793
  var flagged = (options) => {
@@ -3744,6 +3797,7 @@ var flagged = (options) => {
3744
3797
  function univerSheetsOtPort(o) {
3745
3798
  const cs = o.commandService;
3746
3799
  const listeners = /* @__PURE__ */ new Set();
3800
+ const undoWatchers = /* @__PURE__ */ new Set();
3747
3801
  const idle = [];
3748
3802
  const pre = /* @__PURE__ */ new WeakMap();
3749
3803
  const tracker = { stack: [], settle: () => settle() };
@@ -3800,6 +3854,7 @@ function univerSheetsOtPort(o) {
3800
3854
  if (tracker.stack.length === 0 && !group) for (const fn of idle.splice(0)) fn();
3801
3855
  }
3802
3856
  const before = cs.beforeCommandExecuted((info, options) => {
3857
+ if (info.id === UNDO || info.id === REDO) for (const fn of [...undoWatchers]) fn();
3803
3858
  if (info.type === COMMAND) {
3804
3859
  if (tracker.stack.length === 0 && !group) group = openGroup(info.id);
3805
3860
  tracker.stack.push(info.id);
@@ -4057,8 +4112,13 @@ function univerSheetsOtPort(o) {
4057
4112
  const service = o.undoRedo?.();
4058
4113
  return u && service ? [{ service, unitId: u }] : [];
4059
4114
  },
4115
+ beforeUndo(fn) {
4116
+ undoWatchers.add(fn);
4117
+ return () => void undoWatchers.delete(fn);
4118
+ },
4060
4119
  markDirty: () => o.markDirty(),
4061
4120
  dispose() {
4121
+ undoWatchers.clear();
4062
4122
  before.dispose();
4063
4123
  after.dispose();
4064
4124
  collab.dispose();