@ai-matrx/office 0.8.19 → 0.8.21

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.
@@ -3,7 +3,7 @@
3
3
  // src/univer/collab/WorkbookCollabSession.ts
4
4
  import * as Y from "yjs";
5
5
  import { Awareness } from "y-protocols/awareness";
6
- import { electCollabHost, remoteOriginOf, REMOTE_PARAM_TAG } from "@ai-matrx/data/realtime/collab";
6
+ import { claimCollaboratorColor, electCollabHost, remoteOriginOf, REMOTE_PARAM_TAG } from "@ai-matrx/data/realtime/collab";
7
7
 
8
8
  // src/univer/collab/covered.ts
9
9
  var COLLAB_COVERED_KEY = "__matrxCollabCovered";
@@ -39,6 +39,7 @@ var WorkbookCollabSession = class {
39
39
  muted = 0;
40
40
  started = false;
41
41
  disposed = false;
42
+ stopColor = null;
42
43
  /** Room entries this seat holds in its content: applied from a peer, or made here. */
43
44
  applied = /* @__PURE__ */ new Set();
44
45
  /** Entries without an id (a peer on an older build) are applied once, by identity. */
@@ -60,12 +61,12 @@ var WorkbookCollabSession = class {
60
61
  uid: this.options.uid,
61
62
  clientId: this.options.clientId,
62
63
  name: this.options.uid.slice(0, 8),
63
- color: pickColor(this.options.uid),
64
64
  sheetId: null,
65
65
  row: null,
66
66
  col: null,
67
67
  ts: Date.now()
68
68
  });
69
+ this.stopColor = claimCollaboratorColor(this.awareness, { uid: this.options.uid, clientId: this.options.clientId });
69
70
  this.provider = this.options.makeProvider({
70
71
  workbookId: this.options.workbookId,
71
72
  clientId: this.options.clientId,
@@ -126,6 +127,8 @@ var WorkbookCollabSession = class {
126
127
  this.awareness.off("change", this.awarenessObserver);
127
128
  this.awarenessObserver = null;
128
129
  }
130
+ this.stopColor?.();
131
+ this.stopColor = null;
129
132
  this.provider?.disconnect();
130
133
  this.provider = null;
131
134
  if (this.awareness) {
@@ -332,32 +335,6 @@ function stripSenderView(params) {
332
335
  function isCellEditorScratch(info) {
333
336
  return typeof info.id === "string" && info.id.startsWith("doc.mutation.");
334
337
  }
335
- var CURSOR_PALETTE = [
336
- "#3b82f6",
337
- // blue
338
- "#10b981",
339
- // emerald
340
- "#f59e0b",
341
- // amber
342
- "#ef4444",
343
- // red
344
- "#8b5cf6",
345
- // violet
346
- "#06b6d4",
347
- // cyan
348
- "#ec4899",
349
- // pink
350
- "#84cc16"
351
- // lime
352
- ];
353
- function pickColor(uid) {
354
- let h = 2166136261;
355
- for (let i = 0; i < uid.length; i++) {
356
- h ^= uid.charCodeAt(i);
357
- h = Math.imul(h, 16777619);
358
- }
359
- return CURSOR_PALETTE[Math.abs(h) % CURSOR_PALETTE.length] ?? "#ec4899";
360
- }
361
338
 
362
339
  // src/univer/collab/docs-ot/transform.ts
363
340
  import { JSONX, TextX, getBodySliceForSplitTextXAction, normalizeTextRuns, merge as univerMerge } from "@univerjs/core";
@@ -1351,7 +1328,7 @@ import * as Y2 from "yjs";
1351
1328
  import { Awareness as Awareness2 } from "y-protocols/awareness";
1352
1329
  import {
1353
1330
  awarenessOpPresence,
1354
- claimCollaboratorColor,
1331
+ claimCollaboratorColor as claimCollaboratorColor2,
1355
1332
  collabReplace,
1356
1333
  collabRoomExists,
1357
1334
  SupabaseYjsProvider,
@@ -1530,7 +1507,7 @@ function documentOtFactory(o) {
1530
1507
  const doc = new Y2.Doc();
1531
1508
  const awareness = new Awareness2(doc);
1532
1509
  awareness.setLocalState({ uid: o.uid, clientId: o.clientId, name: o.displayName ?? o.uid.slice(0, 8), ts: Date.now() });
1533
- const stopColor = claimCollaboratorColor(awareness, { uid: o.uid, clientId: o.clientId });
1510
+ const stopColor = claimCollaboratorColor2(awareness, { uid: o.uid, clientId: o.clientId });
1534
1511
  const carets = new RemoteCaretBook(awareness.clientID);
1535
1512
  const emit = () => onAwareness({ states: new Map(awareness.getStates()), selfUid: o.uid, carets: carets.list() });
1536
1513
  const publishAwareness = () => {
@@ -1708,6 +1685,14 @@ var SHEET_MUTATIONS = {
1708
1685
  "sheet.mutation.set-worksheet-row-auto-height": covered("size", "rowsAutoHeightInfo"),
1709
1686
  "sheet.mutation.set-worksheet-col-width": covered("size", "ranges", "colWidth"),
1710
1687
  "sheet.mutation.set-worksheet-name": covered("name", "name"),
1688
+ // ── covered (C5c) ────────────────────────────────────────────────────────────────────────────────
1689
+ "sheet.mutation.set.numfmt": covered("numfmt", "values", "refMap"),
1690
+ "sheet.mutation.remove.numfmt": covered("numfmt", "ranges"),
1691
+ "sheet.mutation.set-worksheet-row-count": covered("count", "rowCount"),
1692
+ "sheet.mutation.set-worksheet-column-count": covered("count", "columnCount"),
1693
+ "sheet.mutation.insert-sheet": covered("sheet", "index", "sheet"),
1694
+ "sheet.mutation.remove-sheet": covered("sheet", "sheetIndex", "sheetsLeft"),
1695
+ "sheet.mutation.set-worksheet-order": covered("sheet", "fromOrder", "toOrder"),
1711
1696
  // ── never travels ────────────────────────────────────────────────────────────────────────────────
1712
1697
  "formula.mutation.set-formula-calculation-start": never(FORMULA_RESULT),
1713
1698
  "formula.mutation.set-trigger-formula-calculation-start": never(FORMULA_RESULT),
@@ -1742,12 +1727,7 @@ var SHEET_MUTATIONS = {
1742
1727
  "thread-comment.mutation.delete-comment": never(UNIVER_COMMENTS),
1743
1728
  // ── barriers: X (no transform yet, design §10) ───────────────────────────────────────────────────
1744
1729
  "sheet.mutation.reorder-range": barrier("X", "sort: a permutation, not transformed yet"),
1745
- "sheet.mutation.set-worksheet-row-count": barrier("X", "absolute row count; needs delta semantics"),
1746
- "sheet.mutation.set-worksheet-column-count": barrier("X", "absolute column count; needs delta semantics"),
1747
- "sheet.mutation.set-worksheet-order": barrier("X", "absolute sheet order; needs move semantics"),
1748
1730
  "sheet.mutation.move-range": barrier("X", "cut-paste carries the sender's cell matrices"),
1749
- "sheet.mutation.insert-sheet": barrier("X", "sheet add: not in C5a"),
1750
- "sheet.mutation.remove-sheet": barrier("X", "sheet remove: not in C5a"),
1751
1731
  "sheet.mutation.set-worksheet-hidden": barrier("X", "sheet-level setting: not in C5a"),
1752
1732
  "sheet.mutation.set-tab-color": barrier("X", "sheet-level setting: not in C5a"),
1753
1733
  "sheet.mutation.set-workbook-name": barrier("X", "workbook-level setting: not in C5a"),
@@ -1762,8 +1742,6 @@ var SHEET_MUTATIONS = {
1762
1742
  "sheet.mutation.set-row-visible": barrier("X", "row visibility: not in C5a"),
1763
1743
  "sheet.mutation.set-col-hidden": barrier("X", "column visibility: not in C5a"),
1764
1744
  "sheet.mutation.set-col-visible": barrier("X", "column visibility: not in C5a"),
1765
- "sheet.mutation.set.numfmt": barrier("X", "number formats by range: not in C5a"),
1766
- "sheet.mutation.remove.numfmt": barrier("X", "number formats by range: not in C5a"),
1767
1745
  "sheet.mutation.add-range-protection": barrier("X", "permissions: not in C5a"),
1768
1746
  "sheet.mutation.set-range-protection": barrier("X", "permissions: not in C5a"),
1769
1747
  "sheet.mutation.delete-range-protection": barrier("X", "permissions: not in C5a"),
@@ -2167,7 +2145,7 @@ function refSheetsFor(texts, sheets) {
2167
2145
  }
2168
2146
 
2169
2147
  // src/collab/sheets/transform/transform.ts
2170
- var SHEET_TRANSFORMER_VERSION = "c5b-1";
2148
+ var SHEET_TRANSFORMER_VERSION = "c5c-1";
2171
2149
  var ID = {
2172
2150
  values: "sheet.mutation.set-range-values",
2173
2151
  insertRow: "sheet.mutation.insert-row",
@@ -2184,7 +2162,14 @@ var ID = {
2184
2162
  colWidth: "sheet.mutation.set-worksheet-col-width",
2185
2163
  name: "sheet.mutation.set-worksheet-name",
2186
2164
  setName: "formula.mutation.set-defined-name",
2187
- removeName: "formula.mutation.remove-defined-name"
2165
+ removeName: "formula.mutation.remove-defined-name",
2166
+ setNumfmt: "sheet.mutation.set.numfmt",
2167
+ removeNumfmt: "sheet.mutation.remove.numfmt",
2168
+ rowCount: "sheet.mutation.set-worksheet-row-count",
2169
+ colCount: "sheet.mutation.set-worksheet-column-count",
2170
+ insertSheet: "sheet.mutation.insert-sheet",
2171
+ removeSheet: "sheet.mutation.remove-sheet",
2172
+ sheetOrder: "sheet.mutation.set-worksheet-order"
2188
2173
  };
2189
2174
  var PAIR_BARRIERS = {};
2190
2175
  var pairKey = (a, b) => a < b ? `${a}|${b}` : `${b}|${a}`;
@@ -2296,6 +2281,20 @@ function mergeThrough(s, m) {
2296
2281
  }
2297
2282
  return { range: m, fixup: false };
2298
2283
  }
2284
+ function mergeFixupsOwed(m, mergesBefore) {
2285
+ const st = structuralOf(m);
2286
+ if (!st) return null;
2287
+ const remove = [];
2288
+ const add = [];
2289
+ for (const q of mergesBefore) {
2290
+ const t = mergeThrough(st, q);
2291
+ if (!t.fixup) continue;
2292
+ remove.push({ startRow: q.startRow, endRow: q.endRow, startColumn: q.startColumn, endColumn: q.endColumn, ...q.rangeType !== void 0 ? { rangeType: q.rangeType } : {} });
2293
+ if (t.range) add.push({ ...t.range });
2294
+ }
2295
+ return remove.length ? { remove, add } : null;
2296
+ }
2297
+ var isStructuralMutation = (m) => structuralOf(m) !== null;
2299
2298
  function exactMergeRemovals(group, merges) {
2300
2299
  const sim = new Map(Object.entries(merges).map(([k, v]) => [k, v.map((r) => ({ ...r }))]));
2301
2300
  const out = [];
@@ -2342,8 +2341,24 @@ function cellsMutation(template, cells) {
2342
2341
  function putCell(cells, r, c, v) {
2343
2342
  (cells[r] ??= {})[c] = v;
2344
2343
  }
2344
+ function cellsOverlap(a, b) {
2345
+ for (const r of Object.keys(a)) {
2346
+ const br = b[r];
2347
+ if (!br) continue;
2348
+ for (const c of Object.keys(a[r] ?? {})) if (c in br) return true;
2349
+ }
2350
+ return false;
2351
+ }
2345
2352
  var isEmptyMatrix = (cells) => Object.values(cells).every((row) => Object.keys(row).length === 0);
2353
+ var cellBarrierCache = /* @__PURE__ */ new WeakMap();
2346
2354
  function cellBarrier(m) {
2355
+ const hit = cellBarrierCache.get(m);
2356
+ if (hit !== void 0) return hit;
2357
+ const why = cellBarrierOf(m);
2358
+ cellBarrierCache.set(m, why);
2359
+ return why;
2360
+ }
2361
+ function cellBarrierOf(m) {
2347
2362
  if (m.params.isOverrideStyle) return "set-range-values with isOverrideStyle (whole-style replace) is not covered";
2348
2363
  let why = null;
2349
2364
  forEachCell(cellsOf(m), (_r, _c, v) => {
@@ -2425,6 +2440,7 @@ function transformCell(X, Y4, side) {
2425
2440
  function transformCellsOverCells(x, y, side) {
2426
2441
  if (sheetOf(x) !== sheetOf(y)) return ok([x]);
2427
2442
  const ycells = cellsOf(y);
2443
+ if (!cellsOverlap(cellsOf(x), ycells)) return ok([x]);
2428
2444
  const xd = isDerived(x);
2429
2445
  const yd = isDerived(y);
2430
2446
  const eff = derivedSide(x, y, side);
@@ -2452,7 +2468,7 @@ function transformCellsOverCells(x, y, side) {
2452
2468
  const op = passes.filter((p) => !isEmptyMatrix(p)).map((p) => cellsMutation(x, p));
2453
2469
  return ok(op, lost, dropped);
2454
2470
  }
2455
- var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived"];
2471
+ var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived", "sheetIndex", "sheetsLeft"];
2456
2472
  var isDerived = (m) => m.params.derived === true;
2457
2473
  function derivedSide(x, y, side) {
2458
2474
  const xd = isDerived(x);
@@ -2474,7 +2490,15 @@ function withRefSheets(params, rs) {
2474
2490
  return out;
2475
2491
  }
2476
2492
  var hasFormulaText = (v) => !!v && (isFormulaText(v.f) || typeof v.ref === "string");
2493
+ var hasFormulaCache = /* @__PURE__ */ new WeakMap();
2477
2494
  function hasFormula(m) {
2495
+ const hit = hasFormulaCache.get(m);
2496
+ if (hit !== void 0) return hit;
2497
+ const any = hasFormulaOf(m);
2498
+ hasFormulaCache.set(m, any);
2499
+ return any;
2500
+ }
2501
+ function hasFormulaOf(m) {
2478
2502
  let any = false;
2479
2503
  forEachCell(cellsOf(m), (_r, _c, v) => {
2480
2504
  any ||= hasFormulaText(v);
@@ -2513,15 +2537,18 @@ function cellThrough(sheet, change, r, c) {
2513
2537
  return nr === null || nc === null ? null : [nr, nc];
2514
2538
  }
2515
2539
  function cellsOverChange(x, change) {
2540
+ if (!hasFormula(x) && (change.kind !== "axis" || change.sheet !== sheetOf(x))) return ok([x]);
2516
2541
  const ctx = formulaCtx(x);
2517
2542
  const out = {};
2518
2543
  const dropped = [];
2519
2544
  let why = null;
2545
+ let changed = false;
2520
2546
  forEachCell(cellsOf(x), (r, c, v) => {
2521
2547
  if (why) return;
2522
2548
  const at = cellThrough(sheetOf(x), change, r, c);
2523
2549
  if (!at) {
2524
2550
  dropped.push(cellRef(sheetOf(x), r, c));
2551
+ changed = true;
2525
2552
  return;
2526
2553
  }
2527
2554
  let e = v;
@@ -2532,14 +2559,19 @@ function cellsOverChange(x, change) {
2532
2559
  return;
2533
2560
  }
2534
2561
  e = res.entry;
2562
+ if (res.changed) changed = true;
2535
2563
  }
2564
+ if (at[0] !== r || at[1] !== c) changed = true;
2536
2565
  putCell(out, at[0], at[1], e);
2537
2566
  });
2538
2567
  if (why) return barrier2(why);
2539
- const params = { ...withRefSheets(x.params, refSheetsAfter(ctx.refSheets, change)), cellValue: out };
2568
+ const rsAfter = refSheetsAfter(ctx.refSheets, change);
2569
+ if (!changed && JSON.stringify(rsAfter ?? null) === JSON.stringify(ctx.refSheets ?? null)) return ok([x]);
2570
+ const params = { ...withRefSheets(x.params, rsAfter), cellValue: out };
2540
2571
  return ok(isEmptyMatrix(out) ? [] : [{ id: x.id, params }], [], dropped);
2541
2572
  }
2542
2573
  function rewritesOf(y, change) {
2574
+ if (!hasFormula(y)) return null;
2543
2575
  const ctx = formulaCtx(y);
2544
2576
  const out = {};
2545
2577
  let why = null;
@@ -2685,6 +2717,255 @@ function buildSize(template, values) {
2685
2717
  const value = uniform !== void 0 && [...values.values()].every((v) => v === uniform) ? uniform : Object.fromEntries(values);
2686
2718
  return { id: template.id, params: { ...p, ranges, [field]: value } };
2687
2719
  }
2720
+ var isNumfmt = (m) => m.id === ID.setNumfmt || m.id === ID.removeNumfmt;
2721
+ function nfParts(m) {
2722
+ if (m.id === ID.removeNumfmt) return [{ key: null, ranges: (m.params.ranges ?? []).map((r) => ({ ...r })) }];
2723
+ const values = m.params.values ?? {};
2724
+ return Object.keys(values).map((k) => ({ key: k, ranges: (values[k]?.ranges ?? []).map((r) => ({ ...r })) }));
2725
+ }
2726
+ function nfBuild(m, parts) {
2727
+ const keep = parts.filter((p) => p.ranges.length);
2728
+ if (!keep.length) return [];
2729
+ if (m.id === ID.removeNumfmt) return [{ id: m.id, params: { ...m.params, ranges: keep[0].ranges } }];
2730
+ const values = {};
2731
+ const refMap = {};
2732
+ const srcValues = m.params.values ?? {};
2733
+ const srcRef = m.params.refMap ?? {};
2734
+ for (const p of keep) {
2735
+ values[p.key] = { ...srcValues[p.key], ranges: p.ranges };
2736
+ refMap[p.key] = srcRef[p.key];
2737
+ }
2738
+ return [{ id: m.id, params: { ...m.params, values, refMap } }];
2739
+ }
2740
+ function nfValueAt(m, row, col) {
2741
+ for (const p of nfParts(m))
2742
+ if (p.ranges.some((r) => r.startRow <= row && row <= r.endRow && r.startColumn <= col && col <= r.endColumn)) {
2743
+ if (p.key === null) return null;
2744
+ const ref = (m.params.refMap ?? {})[p.key];
2745
+ return { pattern: ref?.pattern };
2746
+ }
2747
+ return void 0;
2748
+ }
2749
+ function spanThrough(op, lo, hi) {
2750
+ const pieces = [];
2751
+ if (op.kind === "insert") pieces.push([0, op.at - 1, 0], [op.at, Infinity, op.count]);
2752
+ else if (op.kind === "remove") pieces.push([0, op.start - 1, 0], [op.start + op.count, Infinity, -op.count]);
2753
+ else if (op.count <= 0 || op.to >= op.from && op.to <= op.from + op.count) pieces.push([0, Infinity, 0]);
2754
+ else if (op.to > op.from) {
2755
+ const n = op.count;
2756
+ pieces.push([0, op.from - 1, 0], [op.from, op.from + n - 1, op.to - n - op.from], [op.from + n, op.to - 1, -n], [op.to, Infinity, 0]);
2757
+ } else {
2758
+ const n = op.count;
2759
+ pieces.push([0, op.to - 1, 0], [op.to, op.from - 1, n], [op.from, op.from + n - 1, op.to - op.from], [op.from + n, Infinity, 0]);
2760
+ }
2761
+ const out = [];
2762
+ for (const [a, b, d] of pieces) {
2763
+ const x0 = Math.max(a, lo);
2764
+ const x1 = Math.min(b, hi);
2765
+ if (x0 <= x1) out.push([x0 + d, x1 + d]);
2766
+ }
2767
+ out.sort((p, q) => p[0] - q[0]);
2768
+ const merged = [];
2769
+ for (const p of out) {
2770
+ const last = merged[merged.length - 1];
2771
+ if (last && last[1] + 1 >= p[0]) last[1] = Math.max(last[1], p[1]);
2772
+ else merged.push([p[0], p[1]]);
2773
+ }
2774
+ return merged;
2775
+ }
2776
+ function rectsThrough(st, rects) {
2777
+ const [a0, a1] = S(st.axis);
2778
+ const out = [];
2779
+ for (const r of rects) for (const [lo, hi] of spanThrough(st.op, r[a0], r[a1])) out.push({ ...r, [a0]: lo, [a1]: hi });
2780
+ return out;
2781
+ }
2782
+ function rectMinus(a, b) {
2783
+ if (a.startRow > b.endRow || b.startRow > a.endRow || a.startColumn > b.endColumn || b.startColumn > a.endColumn) return [a];
2784
+ const out = [];
2785
+ if (a.startRow < b.startRow) out.push({ ...a, endRow: b.startRow - 1 });
2786
+ if (a.endRow > b.endRow) out.push({ ...a, startRow: b.endRow + 1 });
2787
+ const r0 = Math.max(a.startRow, b.startRow);
2788
+ const r1 = Math.min(a.endRow, b.endRow);
2789
+ if (a.startColumn < b.startColumn) out.push({ ...a, startRow: r0, endRow: r1, endColumn: b.startColumn - 1 });
2790
+ if (a.endColumn > b.endColumn) out.push({ ...a, startRow: r0, endRow: r1, startColumn: b.endColumn + 1 });
2791
+ return out;
2792
+ }
2793
+ function rectsMinus(as, bs) {
2794
+ let cur = as;
2795
+ for (const b of bs) cur = cur.flatMap((a) => rectMinus(a, b));
2796
+ return cur;
2797
+ }
2798
+ function nWrites(m) {
2799
+ const out = [];
2800
+ forEachCell(cellsOf(m), (r, c, v) => {
2801
+ if (v === null || v.s === null || v.s && typeof v.s === "object" && "n" in v.s)
2802
+ out.push({ startRow: r, endRow: r, startColumn: c, endColumn: c });
2803
+ });
2804
+ return out;
2805
+ }
2806
+ function numfmtPair(x, y, side, sx, sy) {
2807
+ const nx = isNumfmt(x);
2808
+ const ny = isNumfmt(y);
2809
+ if (!nx && !ny) return null;
2810
+ if (nx && sy) return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsThrough(sy, p.ranges) }))));
2811
+ if (ny && sx) return ok([x]);
2812
+ if (nx && ny) {
2813
+ if (side === "left") return ok([x]);
2814
+ const ys = nfParts(y).flatMap((p) => p.ranges);
2815
+ return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) }))));
2816
+ }
2817
+ if (nx && y.id === ID.values) {
2818
+ if (side === "left") return ok([x]);
2819
+ const ys = nWrites(y);
2820
+ return ys.length ? ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) })))) : ok([x]);
2821
+ }
2822
+ if (ny && x.id === ID.values) {
2823
+ if (side === "left") return ok([x]);
2824
+ const out = {};
2825
+ const tail = {};
2826
+ let touched = false;
2827
+ forEachCell(cellsOf(x), (r, c, v) => {
2828
+ const n = nfValueAt(y, r, c);
2829
+ if (n === void 0) return putCell(out, r, c, v);
2830
+ if (v === null || v.s === null) {
2831
+ putCell(out, r, c, v);
2832
+ if (n) {
2833
+ putCell(tail, r, c, { s: { n } });
2834
+ touched = true;
2835
+ }
2836
+ return;
2837
+ }
2838
+ if (v.s && typeof v.s === "object" && "n" in v.s) {
2839
+ const { n: _drop, ...rest } = v.s;
2840
+ const e = { ...v };
2841
+ if (Object.keys(rest).length) e.s = rest;
2842
+ else delete e.s;
2843
+ touched = true;
2844
+ if (Object.keys(e).length) putCell(out, r, c, e);
2845
+ return;
2846
+ }
2847
+ putCell(out, r, c, v);
2848
+ });
2849
+ if (!touched) return ok([x]);
2850
+ const ops = [];
2851
+ if (!isEmptyMatrix(out)) ops.push(cellsMutation(x, out));
2852
+ if (!isEmptyMatrix(tail)) ops.push({ id: ID.values, params: { subUnitId: sheetOf(x), cellValue: tail } });
2853
+ return ok(ops);
2854
+ }
2855
+ return ok([x]);
2856
+ }
2857
+ var isCount = (m) => m.id === ID.rowCount || m.id === ID.colCount;
2858
+ var countAxis = (m) => m.id === ID.rowCount ? "row" : "col";
2859
+ var countKey = (m) => m.id === ID.rowCount ? "rowCount" : "columnCount";
2860
+ function countPair(x, y, sy) {
2861
+ if (!isCount(x)) return isCount(y) ? ok([x]) : null;
2862
+ const k = countKey(x);
2863
+ const v = Number(x.params[k]);
2864
+ if (sy) {
2865
+ if (sy.axis !== countAxis(x) || sy.op.kind === "move") return ok([x]);
2866
+ const d = sy.op.kind === "insert" ? sy.op.count : -sy.op.count;
2867
+ return ok([{ id: x.id, params: { ...x.params, [k]: Math.max(1, v + d) } }]);
2868
+ }
2869
+ if (isCount(y) && y.id === x.id) {
2870
+ const w = Number(y.params[k]);
2871
+ return ok([w > v ? { id: x.id, params: { ...x.params, [k]: w } } : x]);
2872
+ }
2873
+ return ok([x]);
2874
+ }
2875
+ var isSheetOp = (m) => m.id === ID.insertSheet || m.id === ID.removeSheet || m.id === ID.sheetOrder;
2876
+ var sheetIdOf = (m) => m.id === ID.insertSheet ? String(m.params.sheet?.id ?? "") : sheetOf(m);
2877
+ function sheetAxisOf(m) {
2878
+ const p = m.params;
2879
+ if (m.id === ID.insertSheet) return typeof p.index === "number" ? { kind: "insert", at: p.index, count: 1 } : null;
2880
+ if (m.id === ID.removeSheet) return typeof p.sheetIndex === "number" ? { kind: "remove", start: p.sheetIndex, count: 1 } : null;
2881
+ if (m.id === ID.sheetOrder) {
2882
+ const f = Number(p.fromOrder);
2883
+ const t = Number(p.toOrder);
2884
+ return { kind: "move", from: f, count: 1, to: t > f ? t + 1 : t };
2885
+ }
2886
+ return null;
2887
+ }
2888
+ function buildSheetOp(template, op) {
2889
+ if (op.kind === "insert") return { id: template.id, params: { ...template.params, index: op.at } };
2890
+ if (op.kind === "remove") return { id: template.id, params: { ...template.params, sheetIndex: op.start } };
2891
+ return { id: template.id, params: { ...template.params, fromOrder: op.from, toOrder: op.to > op.from ? op.to - 1 : op.to } };
2892
+ }
2893
+ function namesSheet(m, id) {
2894
+ const rs = refSheetsOf(m);
2895
+ return !!rs && Object.values(rs).includes(id) && (m.id === ID.setName || hasFormula(m));
2896
+ }
2897
+ function sheetPair(x, y, side) {
2898
+ const xs = isSheetOp(x);
2899
+ const ys = isSheetOp(y);
2900
+ if (!xs && !ys) return null;
2901
+ if (xs && ys) {
2902
+ const a = sheetAxisOf(x);
2903
+ const b = sheetAxisOf(y);
2904
+ if (!a || !b) return barrier2("a sheet add or remove without its index (capture must call annotateSheetOps)");
2905
+ if (x.id === ID.insertSheet && y.id === ID.insertSheet && sheetIdOf(x) === sheetIdOf(y)) {
2906
+ return JSON.stringify(x.params) === JSON.stringify(y.params) ? ok([]) : barrier2("the same sheet id added twice with different content");
2907
+ }
2908
+ if (x.id === ID.removeSheet && typeof x.params.sheetsLeft === "number" && sheetIdOf(x) !== sheetIdOf(y) && (y.id === ID.removeSheet || y.id === ID.insertSheet)) {
2909
+ const left = x.params.sheetsLeft + (y.id === ID.insertSheet ? 1 : -1);
2910
+ if (left < 1) return barrier2("concurrent sheet removes would leave the workbook without a sheet");
2911
+ const out2 = transformAxis(a, b, side);
2912
+ if (!out2) return barrier2("sheet order: no transform");
2913
+ return ok(out2.map((o) => buildSheetOp({ id: x.id, params: { ...x.params, sheetsLeft: left } }, o)));
2914
+ }
2915
+ const out = transformAxis(a, b, side);
2916
+ if (!out) return barrier2("sheet order: no transform");
2917
+ return ok(out.map((o) => buildSheetOp(x, o)), [], out.length === 0 && x.id === ID.sheetOrder ? [`${sheetIdOf(x)}: sheet order`] : []);
2918
+ }
2919
+ if (ys) {
2920
+ if (y.id !== ID.removeSheet) return ok([x]);
2921
+ const gone2 = sheetOf(y);
2922
+ if (namesSheet(x, gone2)) return barrier2("a formula names a sheet removed at the same time");
2923
+ if (sheetOf(x) === gone2 && x.id !== ID.setName && x.id !== ID.removeName)
2924
+ return ok([], [], isDerived(x) ? [] : [`${gone2}: the sheet was removed`]);
2925
+ return ok([x]);
2926
+ }
2927
+ if (x.id !== ID.removeSheet) return ok([x]);
2928
+ const gone = sheetOf(x);
2929
+ if (namesSheet(y, gone)) return barrier2("a formula names a sheet removed at the same time");
2930
+ return ok([x], sheetOf(y) === gone && !isDerived(y) && y.id !== ID.setName && y.id !== ID.removeName ? [`${gone}: the sheet was removed`] : []);
2931
+ }
2932
+ function annotateSheetOps(group, sheetOrder) {
2933
+ const order = [...sheetOrder];
2934
+ return group.map((m) => {
2935
+ if (m.id === ID.insertSheet) {
2936
+ const index = typeof m.params.index === "number" ? Math.min(m.params.index, order.length) : order.length;
2937
+ order.splice(index, 0, sheetIdOf(m));
2938
+ return index === m.params.index ? m : { id: m.id, params: { ...m.params, index } };
2939
+ }
2940
+ if (m.id === ID.removeSheet) {
2941
+ const i = order.indexOf(sheetOf(m));
2942
+ if (i >= 0) order.splice(i, 1);
2943
+ return i >= 0 ? { id: m.id, params: { ...m.params, sheetIndex: i, sheetsLeft: order.length } } : m;
2944
+ }
2945
+ if (m.id === ID.sheetOrder) {
2946
+ const f = Number(m.params.fromOrder);
2947
+ const [id] = order.splice(f, 1);
2948
+ order.splice(Number(m.params.toOrder), 0, id ?? sheetOf(m));
2949
+ }
2950
+ return m;
2951
+ });
2952
+ }
2953
+ function inlineSheetStyles(sheet, styleOf) {
2954
+ const out = JSON.parse(JSON.stringify(sheet));
2955
+ const fix = (o) => {
2956
+ if (o && typeof o.s === "string") {
2957
+ const st = styleOf(o.s);
2958
+ if (st) o.s = JSON.parse(JSON.stringify(st));
2959
+ }
2960
+ };
2961
+ for (const row of Object.values(out.cellData ?? {})) for (const cell of Object.values(row ?? {})) fix(cell);
2962
+ for (const key of ["rowData", "columnData"]) for (const v of Object.values(out[key] ?? {})) fix(v);
2963
+ if (typeof out.defaultStyle === "string") {
2964
+ const st = styleOf(out.defaultStyle);
2965
+ if (st) out.defaultStyle = JSON.parse(JSON.stringify(st));
2966
+ }
2967
+ return out;
2968
+ }
2688
2969
  function familyOf(id) {
2689
2970
  const e = SHEET_MUTATIONS[id];
2690
2971
  return e && e.kind === "covered" ? e.family : "";
@@ -2701,6 +2982,8 @@ function transformMutation(x, y, side) {
2701
2982
  }
2702
2983
  const pb = PAIR_BARRIERS[pairKey(x.id, y.id)];
2703
2984
  if (pb) return barrier2(`${x.id} \xD7 ${y.id}: ${pb.reason}${pb.seed === null ? "" : ` (fuzz seed ${pb.seed})`}`);
2985
+ const sp = sheetPair(x, y, side);
2986
+ if (sp) return sp;
2704
2987
  const fp = formulaPair(x, y, side);
2705
2988
  if (fp) return fp;
2706
2989
  if (sheetOf(x) !== sheetOf(y)) return ok([x]);
@@ -2708,6 +2991,10 @@ function transformMutation(x, y, side) {
2708
2991
  const fy = familyOf(y.id);
2709
2992
  const sy = structuralOf(y);
2710
2993
  const sx = structuralOf(x);
2994
+ const cp = countPair(x, y, sy);
2995
+ if (cp) return cp;
2996
+ const np = numfmtPair(x, y, side, sx, sy);
2997
+ if (np) return np;
2711
2998
  if (sx && sy) {
2712
2999
  if (sx.axis !== sy.axis) return ok([x]);
2713
3000
  const out = transformAxis(sx.op, sy.op, side);
@@ -2815,22 +3102,45 @@ function tSeq(xs, ys, side, acc) {
2815
3102
  acc.dropped.push(...a.dropped);
2816
3103
  return [a.op, b.op];
2817
3104
  }
3105
+ const outX = [];
3106
+ let curY = ys;
3107
+ for (const x of xs) {
3108
+ let curX = [x];
3109
+ const nextY = [];
3110
+ for (const y of curY) {
3111
+ if (curX.length === 0) {
3112
+ nextY.push(y);
3113
+ continue;
3114
+ }
3115
+ const r = tSeq(curX, [y], side, acc);
3116
+ if (blocked(r)) return r;
3117
+ curX = r[0];
3118
+ for (const m of r[1]) nextY.push(m);
3119
+ }
3120
+ for (const m of curX) outX.push(m);
3121
+ curY = nextY;
3122
+ }
3123
+ return [outX, curY];
3124
+ }
3125
+ function tSeqOld(xs, ys, side, acc) {
3126
+ if (xs.length === 0 || ys.length === 0) return [xs, ys];
3127
+ if (xs.length === 1 && ys.length === 1) return tSeq(xs, ys, side, acc);
2818
3128
  if (xs.length > 1) {
2819
- const first2 = tSeq([xs[0]], ys, side, acc);
3129
+ const first2 = tSeqOld([xs[0]], ys, side, acc);
2820
3130
  if (blocked(first2)) return first2;
2821
- const rest2 = tSeq(xs.slice(1), first2[1], side, acc);
3131
+ const rest2 = tSeqOld(xs.slice(1), first2[1], side, acc);
2822
3132
  if (blocked(rest2)) return rest2;
2823
3133
  return [[...first2[0], ...rest2[0]], rest2[1]];
2824
3134
  }
2825
- const first = tSeq(xs, [ys[0]], side, acc);
3135
+ const first = tSeqOld(xs, [ys[0]], side, acc);
2826
3136
  if (blocked(first)) return first;
2827
- const rest = tSeq(first[0], ys.slice(1), side, acc);
3137
+ const rest = tSeqOld(first[0], ys.slice(1), side, acc);
2828
3138
  if (blocked(rest)) return rest;
2829
3139
  return [rest[0], [...first[1], ...rest[1]]];
2830
3140
  }
2831
3141
  function transformGroup(x, y, side) {
2832
3142
  const acc = { lost: [], dropped: [] };
2833
- const r = tSeq([...x], [...y], side, acc);
3143
+ const r = (process.env.OLD_TSEQ ? tSeqOld : tSeq)([...x], [...y], side, acc);
2834
3144
  if (blocked(r)) return r;
2835
3145
  return { op: r[0], lost: [...new Set(acc.lost)], dropped: [...new Set(acc.dropped)] };
2836
3146
  }
@@ -2900,7 +3210,7 @@ async function sheetsStateHash(snapshot) {
2900
3210
  }
2901
3211
 
2902
3212
  // src/univer/collab/sheets-ot/room.ts
2903
- var SHEETS_COLLAB_PROTO = 1;
3213
+ var SHEETS_COLLAB_PROTO = 2;
2904
3214
  var SHEETS_EDIT_GROUP = "sheets.edit";
2905
3215
  var flip2 = (side) => side === "left" ? "right" : "left";
2906
3216
  var isNoopSheetOp = (op) => op == null || op.length === 0;
@@ -2947,6 +3257,11 @@ function travelingShape(m) {
2947
3257
  }
2948
3258
  return { id: m.id, params };
2949
3259
  }
3260
+ var shapedLists = /* @__PURE__ */ new WeakMap();
3261
+ function cachedShape(list) {
3262
+ const hit = shapedLists.get(list);
3263
+ return hit && hit.src.length === list.length && hit.src.every((m, i) => m === list[i]) ? hit.shaped : null;
3264
+ }
2950
3265
  function rebaseSheetUndoStack(items, remote, unitId) {
2951
3266
  if (isNoopSheetOp(remote)) return 0;
2952
3267
  let dropped = 0;
@@ -2958,7 +3273,11 @@ function rebaseSheetUndoStack(items, remote, unitId) {
2958
3273
  if (!ok2 || !list?.length) continue;
2959
3274
  const isOperation = (id) => !SHEET_MUTATIONS[id] && id.includes(".operation.");
2960
3275
  const operations = list.filter((m) => isOperation(m.id));
2961
- const shaped = list.filter((m) => !isOperation(m.id)).map(travelingShape);
3276
+ let shaped = cachedShape(list);
3277
+ if (!shaped) {
3278
+ shaped = list.filter((m) => !isOperation(m.id)).map(travelingShape);
3279
+ shapedLists.set(list, { src: list.slice(), shaped });
3280
+ }
2962
3281
  if (shaped.some((m) => m === null)) {
2963
3282
  ok2 = false;
2964
3283
  continue;
@@ -2970,8 +3289,10 @@ function rebaseSheetUndoStack(items, remote, unitId) {
2970
3289
  ok2 = false;
2971
3290
  continue;
2972
3291
  }
2973
- if (JSON.stringify(r.op) === JSON.stringify(shaped)) continue;
2974
- item[key] = [...r.op.map((m) => ({ id: m.id, params: { ...forApply(m).params, unitId } })), ...operations];
3292
+ if (r.op.length === shaped.length && r.op.every((m, i2) => m === shaped[i2])) continue;
3293
+ const next = [...r.op.map((m) => ({ id: m.id, params: { ...forApply(m).params, unitId } })), ...operations];
3294
+ shapedLists.set(next, { src: next.slice(), shaped: r.op });
3295
+ item[key] = next;
2975
3296
  } catch {
2976
3297
  ok2 = false;
2977
3298
  }
@@ -3046,7 +3367,7 @@ function createSheetsOtAdapter(port, hooks = {}) {
3046
3367
  });
3047
3368
  });
3048
3369
  }
3049
- return sheetsStateHash(port.snapshot());
3370
+ return (hooks.stateHash ?? sheetsStateHash)(port.snapshot());
3050
3371
  },
3051
3372
  held: () => queue.length,
3052
3373
  rebaseLocalOverHeld(local) {
@@ -3158,6 +3479,8 @@ var VALUES = "sheet.mutation.set-range-values";
3158
3479
  var ADD_MERGE = "sheet.mutation.add-worksheet-merge";
3159
3480
  var REMOVE_MERGE = "sheet.mutation.remove-worksheet-merge";
3160
3481
  var SHEET_NAME = "sheet.mutation.set-worksheet-name";
3482
+ var INSERT_SHEET = "sheet.mutation.insert-sheet";
3483
+ var RESHAPE = /* @__PURE__ */ new Set(["sheet.mutation.set-worksheet-row-count", "sheet.mutation.set-worksheet-column-count", "sheet.mutation.remove-sheet", INSERT_SHEET]);
3161
3484
  var DEPTH_GUARD = /* @__PURE__ */ Symbol.for("ai-matrx.office.sheets.command-depth-guard");
3162
3485
  var json = (v) => v === void 0 ? v : JSON.parse(JSON.stringify(v));
3163
3486
  var flagged = (options) => {
@@ -3194,7 +3517,8 @@ function univerSheetsOtPort(o) {
3194
3517
  for (const [id, d] of Object.entries(map)) out[id] = String(d?.name ?? "");
3195
3518
  return out;
3196
3519
  };
3197
- const openGroup = (label) => ({ label, sheets: sheetNames(), names: definedNames(), mutations: [], inverses: [], notShared: null });
3520
+ const sheetOrder = () => (o.workbook()?.getSheets() ?? []).map((ws) => ws.getSheetId());
3521
+ const openGroup = (label) => ({ label, order: sheetOrder(), sheets: sheetNames(), names: definedNames(), mutations: [], inverses: [], notShared: null });
3198
3522
  const emit = (e) => {
3199
3523
  for (const l of [...listeners]) {
3200
3524
  try {
@@ -3210,7 +3534,8 @@ function univerSheetsOtPort(o) {
3210
3534
  if (!g) return;
3211
3535
  if (g.notShared) emit({ kind: "not_shared", mutationId: g.notShared, label: g.label });
3212
3536
  else if (g.mutations.length) {
3213
- const mutations = annotateFormulas(g.mutations, g.sheets, g.names);
3537
+ const mutations = annotateSheetOps(annotateFormulas(g.mutations, g.sheets, g.names), g.order);
3538
+ if (mutations.some((m) => isStructuralMutation(m) || RESHAPE.has(m.id))) clampSelection();
3214
3539
  const known = g.inverses.every((i) => i !== void 0);
3215
3540
  const inverse = known ? annotateFormulas(g.inverses.slice().reverse().flat(), g.sheets, g.names) : void 0;
3216
3541
  emit({ kind: "group", mutations, ...inverse !== void 0 ? { inverse } : {}, label: g.label });
@@ -3244,7 +3569,27 @@ function univerSheetsOtPort(o) {
3244
3569
  }
3245
3570
  if (info.id === SHEET_NAME) record.name = ws.getName();
3246
3571
  pre.set(p, record);
3572
+ const owed = isStructuralMutation({ id: info.id, params: p }) ? mergeFixupsOwed({ id: info.id, params: p }, record.merges ?? []) : null;
3573
+ if (owed) {
3574
+ runOwn(REMOVE_MERGE, String(p.subUnitId), owed.remove, ADD_MERGE);
3575
+ if (owed.add.length) record.addAfter = owed.add;
3576
+ }
3247
3577
  });
3578
+ const runOwn = (id, sheet, ranges, inverseId) => {
3579
+ const u = unitId();
3580
+ if (!u) return;
3581
+ applying = true;
3582
+ try {
3583
+ cs.syncExecuteCommand(id, { unitId: u, subUnitId: sheet, ranges: json(ranges) }, { onlyLocal: true });
3584
+ } finally {
3585
+ applying = false;
3586
+ }
3587
+ if (!group) group = openGroup(null);
3588
+ if (!group.notShared) {
3589
+ group.mutations.push({ id, params: { subUnitId: sheet, ranges: json(ranges) } });
3590
+ group.inverses.push([{ id: inverseId, params: { subUnitId: sheet, ranges: json(ranges) } }]);
3591
+ }
3592
+ };
3248
3593
  const after = cs.onCommandExecuted((info) => {
3249
3594
  if (info.type !== COMMAND) return;
3250
3595
  const at = tracker.stack.lastIndexOf(info.id);
@@ -3342,6 +3687,8 @@ function univerSheetsOtPort(o) {
3342
3687
  if (!m2) return { m: null, inverse: void 0 };
3343
3688
  return { m: m2, inverse: [{ id: ADD_MERGE, params: { subUnitId: sheet, ranges: json(m2.params.ranges) } }] };
3344
3689
  }
3690
+ if (info.id === INSERT_SHEET && params.sheet && typeof params.sheet === "object")
3691
+ params.sheet = inlineSheetStyles(params.sheet, (id) => o.workbook()?.getStyles().get(id));
3345
3692
  const m = { id: info.id, params };
3346
3693
  switch (info.id) {
3347
3694
  case "sheet.mutation.insert-row":
@@ -3358,8 +3705,7 @@ function univerSheetsOtPort(o) {
3358
3705
  };
3359
3706
  const collab = cs.onMutationExecutedForCollab((info, options) => {
3360
3707
  if (applying || flagged(options) || !mine(info.params)) return;
3361
- const lone = !group;
3362
- if (lone) group = openGroup(null);
3708
+ if (!group) group = openGroup(null);
3363
3709
  const g = group;
3364
3710
  if (!g.notShared) {
3365
3711
  const r = capture(info);
@@ -3369,11 +3715,29 @@ function univerSheetsOtPort(o) {
3369
3715
  g.inverses.push(r.inverse);
3370
3716
  }
3371
3717
  }
3372
- if (lone && tracker.stack.length === 0) settle();
3718
+ const owedAdd = pre.get(info.params)?.addAfter;
3719
+ if (owedAdd?.length) runOwn(ADD_MERGE, String(info.params.subUnitId ?? ""), owedAdd, REMOVE_MERGE);
3720
+ if (tracker.stack.length === 0) settle();
3373
3721
  });
3722
+ const clampSelection = () => {
3723
+ const sel = o.selections;
3724
+ if (!sel) return;
3725
+ try {
3726
+ const sheet = sel.sheetId();
3727
+ const ws = sheet ? o.workbook()?.getSheetBySheetId(sheet) : null;
3728
+ const current = sel.get();
3729
+ if (!ws || !current?.length) return;
3730
+ const rows = ws.getMaxRows?.() ?? Number.MAX_SAFE_INTEGER;
3731
+ const cols = ws.getMaxColumns?.() ?? Number.MAX_SAFE_INTEGER;
3732
+ const next = moveSelectionsThrough(json(current), { id: "", params: {} }, rows, cols);
3733
+ if (JSON.stringify(next) !== JSON.stringify(current)) sel.set(next);
3734
+ } catch (err) {
3735
+ console.warn("[sheets-ot] could not keep this seat's selection inside the sheet after its own row or column change", err);
3736
+ }
3737
+ };
3374
3738
  const followSelection = (m) => {
3375
3739
  const sel = o.selections;
3376
- if (!sel || !axisOpOf(m)) return;
3740
+ if (!sel || !axisOpOf(m) && !RESHAPE.has(m.id)) return;
3377
3741
  try {
3378
3742
  const sheet = String(m.params.subUnitId ?? "");
3379
3743
  if (sel.sheetId() !== sheet) return;
@@ -3516,6 +3880,7 @@ import * as Y3 from "yjs";
3516
3880
  import { Awareness as Awareness3 } from "y-protocols/awareness";
3517
3881
  import {
3518
3882
  awarenessOpPresence as awarenessOpPresence2,
3883
+ claimCollaboratorColor as claimCollaboratorColor3,
3519
3884
  collabReplace as collabReplace3,
3520
3885
  collabRoomExists as collabRoomExists3,
3521
3886
  SupabaseYjsProvider as SupabaseYjsProvider2,
@@ -3585,15 +3950,6 @@ async function workbookUsesOrderedRoom(workbookId, knob, isNewWorkbook) {
3585
3950
  if (knob === "new") return isNewWorkbook();
3586
3951
  return false;
3587
3952
  }
3588
- function pickColor2(uid) {
3589
- const palette = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#8b5cf6", "#ec4899", "#14b8a6", "#f97316"];
3590
- let h = 2166136261;
3591
- for (let i = 0; i < uid.length; i++) {
3592
- h ^= uid.charCodeAt(i);
3593
- h = Math.imul(h, 16777619);
3594
- }
3595
- return palette[Math.abs(h) % palette.length] ?? "#ec4899";
3596
- }
3597
3953
  async function startWorkbookOtRoom(port, o) {
3598
3954
  const latest = await getLatestSnapshot(o.workbookId);
3599
3955
  if (!latest.success) throw new Error(latest.error);
@@ -3610,12 +3966,12 @@ async function startWorkbookOtRoom(port, o) {
3610
3966
  uid: o.uid,
3611
3967
  clientId: o.clientId,
3612
3968
  name: o.displayName ?? o.uid.slice(0, 8),
3613
- color: pickColor2(o.uid),
3614
3969
  sheetId: null,
3615
3970
  row: null,
3616
3971
  col: null,
3617
3972
  ts: Date.now()
3618
3973
  });
3974
+ const stopColor = claimCollaboratorColor3(awareness, { uid: o.uid, clientId: o.clientId });
3619
3975
  const publishAwareness = () => o.onAwareness?.(new Map(awareness.getStates()));
3620
3976
  awareness.on("change", publishAwareness);
3621
3977
  const provider = new SupabaseYjsProvider2({
@@ -3661,6 +4017,7 @@ async function startWorkbookOtRoom(port, o) {
3661
4017
  awareness.setLocalState({ ...current, sheetId: c.sheetId, row: c.row, col: c.col, ts: Date.now() });
3662
4018
  },
3663
4019
  stop() {
4020
+ stopColor();
3664
4021
  room.stop();
3665
4022
  port.dispose?.();
3666
4023
  awareness.off("change", publishAwareness);