@ai-matrx/office 0.8.20 → 0.8.22

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.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.22
4
+ ### C5c: Sheets co-editing — merge divergence fixed, number formats, counts and sheets travel
5
+
6
+ - **Merge divergence across chained row/column deletes, fixed at its cause.** Univer 1.0.4's structural commands only fix the merges that intersect the COMMAND's range on the other axis (any `-by-range` call with a partial range, an API call) and only while its ref-range registration is current (never refreshed after a content load or a remove-merge). A cut merge it skipped stayed put on the sender while every transform moved it, so seats ended a column apart (the session fuzz's merge at column -1 vs 0). Capture now completes the command (`mergeFixupsOwed`): the cut merges still on the sheet are removed right before the structural mutation and re-added at their model position right after it, in the traveling group and on the sender alike. Multi-seat session fuzz: 0 hash mismatches (was 2–18 per run).
7
+ - **Number formats travel** (`sheet.mutation.set.numfmt` / `remove.numfmt`, family `numfmt`): typing a date or a currency no longer rolls back. Ranges follow row/column inserts, removes and moves cell by cell; against another write of the style key `n` (a numfmt, a style with `n`, a style clear, a cell delete) the later-committed op wins.
8
+ - **Row/column counts travel** (`set-worksheet-row-count` / `-column-count`, family `count`): a concurrent insert/remove on that axis shifts the count; two counts at once, the larger wins. "Refresh from table" growing a sheet is no longer rolled back.
9
+ - **Sheets: add, remove, order travel** (family `sheet`): the sheet order is transformed as one more axis. Capture records a removed sheet's index and how many sheets are left (`annotateSheetOps`, side-car `sheetIndex`, `sheetsLeft`); an insert-sheet's styles are inlined (`inlineSheetStyles`). An edit to a sheet removed at the same time is dropped and its author told; a formula naming a removed sheet, and two removes that would leave no sheet, are barriers.
10
+ - **Sort travels** (`reorder-range`, family `sort`): it is a row permutation inside its range, so concurrent edits follow their rows (a cell op inside the sorted columns lands where the sort moved its row; number formats likewise), a row insert keeps the new rows in place, column inserts/removes widen or clip the range. Barriers: two overlapping sorts, a row/column move through the sorted range, a row remove of rows the sort moved. The inverse is the inverted permutation.
11
+ - **Selections: a seat's own row/column/count/sheet change keeps its selection inside the sheet** (API calls set no selection; the editor crashed with "Range is out of bounds"); a remote count/sheet change clamps it too.
12
+ - **Transform speed:** the group transform walks the OT grid iteratively; untouched cell ops pass through by identity (no overlap, no formula, change elsewhere); barrier checks are cached per op; undo-stack rebase reuses each list's traveling shape and compares by identity.
13
+ - **Tests:** new chained TP1 fuzz (`sheet-transform-chain-fuzz.test.ts`, 3–5 real commands per side, plus capture fidelity); the multi-seat session fuzz is seed-repeatable (`createSheetsOtAdapter` hook `stateHash`, a synchronous hash in tests) and asserts zero incidents. `SHEET_TRANSFORMER_VERSION` = `c5c-1`.
14
+ - `SHEETS_COLLAB_PROTO` = 2: a room opened by this build is raised to proto 2 (when its checkpoint is at head) and older tabs open it read-only, so seats with and without merge completion never share a room.
15
+ - Consumer action: none.
16
+
3
17
  ## 0.8.20
4
18
 
5
- UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
- Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/office/src`.
19
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
20
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
21
+ Diff: `git diff 046a506e40 150104f478 -- apps/shared/office/src`
22
+
23
+ Changed shipped source files (2):
24
+ - `apps/shared/office/src/sheets/workbook-ot-room.ts`
25
+ - `apps/shared/office/src/univer/collab/WorkbookCollabSession.ts`
7
26
 
8
27
  ## 0.8.19
9
28
  ### C7a: the browser editor's plugin resources
@@ -28,19 +28,24 @@ __export(transform_exports, {
28
28
  SIDE_CAR_PARAMS: () => SIDE_CAR_PARAMS,
29
29
  allowParams: () => allowParams,
30
30
  annotateFormulas: () => annotateFormulas,
31
+ annotateSheetOps: () => annotateSheetOps,
31
32
  cellRef: () => cellRef,
32
33
  completeCellContent: () => completeCellContent,
33
34
  exactMergeRemovals: () => exactMergeRemovals,
34
35
  forApply: () => forApply,
35
36
  formulaBarrier: () => formulaBarrier,
37
+ inlineSheetStyles: () => inlineSheetStyles,
36
38
  isBarrier: () => isBarrier,
37
39
  isFormulaText: () => isFormulaText,
40
+ isStructuralMutation: () => isStructuralMutation,
38
41
  mapIndex: () => mapIndex,
42
+ mergeFixupsOwed: () => mergeFixupsOwed,
39
43
  mergeThrough: () => mergeThrough,
40
44
  refSheetsAfter: () => refSheetsAfter,
41
45
  refSheetsFor: () => refSheetsFor,
42
46
  rewriteFormula: () => rewriteFormula,
43
47
  sheetTransformer: () => sheetTransformer,
48
+ spanThrough: () => spanThrough,
44
49
  transformAxis: () => transformAxis,
45
50
  transformGroup: () => transformGroup,
46
51
  transformMutation: () => transformMutation
@@ -73,6 +78,15 @@ var SHEET_MUTATIONS = {
73
78
  "sheet.mutation.set-worksheet-row-auto-height": covered("size", "rowsAutoHeightInfo"),
74
79
  "sheet.mutation.set-worksheet-col-width": covered("size", "ranges", "colWidth"),
75
80
  "sheet.mutation.set-worksheet-name": covered("name", "name"),
81
+ // ── covered (C5c) ────────────────────────────────────────────────────────────────────────────────
82
+ "sheet.mutation.set.numfmt": covered("numfmt", "values", "refMap"),
83
+ "sheet.mutation.remove.numfmt": covered("numfmt", "ranges"),
84
+ "sheet.mutation.set-worksheet-row-count": covered("count", "rowCount"),
85
+ "sheet.mutation.set-worksheet-column-count": covered("count", "columnCount"),
86
+ "sheet.mutation.insert-sheet": covered("sheet", "index", "sheet"),
87
+ "sheet.mutation.remove-sheet": covered("sheet", "sheetIndex", "sheetsLeft"),
88
+ "sheet.mutation.set-worksheet-order": covered("sheet", "fromOrder", "toOrder"),
89
+ "sheet.mutation.reorder-range": covered("sort", "range", "order"),
76
90
  // ── never travels ────────────────────────────────────────────────────────────────────────────────
77
91
  "formula.mutation.set-formula-calculation-start": never(FORMULA_RESULT),
78
92
  "formula.mutation.set-trigger-formula-calculation-start": never(FORMULA_RESULT),
@@ -106,13 +120,7 @@ var SHEET_MUTATIONS = {
106
120
  "thread-comment.mutation.resolve-comment": never(UNIVER_COMMENTS),
107
121
  "thread-comment.mutation.delete-comment": never(UNIVER_COMMENTS),
108
122
  // ── barriers: X (no transform yet, design §10) ───────────────────────────────────────────────────
109
- "sheet.mutation.reorder-range": barrier("X", "sort: a permutation, not transformed yet"),
110
- "sheet.mutation.set-worksheet-row-count": barrier("X", "absolute row count; needs delta semantics"),
111
- "sheet.mutation.set-worksheet-column-count": barrier("X", "absolute column count; needs delta semantics"),
112
- "sheet.mutation.set-worksheet-order": barrier("X", "absolute sheet order; needs move semantics"),
113
123
  "sheet.mutation.move-range": barrier("X", "cut-paste carries the sender's cell matrices"),
114
- "sheet.mutation.insert-sheet": barrier("X", "sheet add: not in C5a"),
115
- "sheet.mutation.remove-sheet": barrier("X", "sheet remove: not in C5a"),
116
124
  "sheet.mutation.set-worksheet-hidden": barrier("X", "sheet-level setting: not in C5a"),
117
125
  "sheet.mutation.set-tab-color": barrier("X", "sheet-level setting: not in C5a"),
118
126
  "sheet.mutation.set-workbook-name": barrier("X", "workbook-level setting: not in C5a"),
@@ -127,8 +135,6 @@ var SHEET_MUTATIONS = {
127
135
  "sheet.mutation.set-row-visible": barrier("X", "row visibility: not in C5a"),
128
136
  "sheet.mutation.set-col-hidden": barrier("X", "column visibility: not in C5a"),
129
137
  "sheet.mutation.set-col-visible": barrier("X", "column visibility: not in C5a"),
130
- "sheet.mutation.set.numfmt": barrier("X", "number formats by range: not in C5a"),
131
- "sheet.mutation.remove.numfmt": barrier("X", "number formats by range: not in C5a"),
132
138
  "sheet.mutation.add-range-protection": barrier("X", "permissions: not in C5a"),
133
139
  "sheet.mutation.set-range-protection": barrier("X", "permissions: not in C5a"),
134
140
  "sheet.mutation.delete-range-protection": barrier("X", "permissions: not in C5a"),
@@ -523,7 +529,7 @@ function refSheetsFor(texts, sheets) {
523
529
  }
524
530
 
525
531
  // src/collab/sheets/transform/transform.ts
526
- var SHEET_TRANSFORMER_VERSION = "c5b-1";
532
+ var SHEET_TRANSFORMER_VERSION = "c5c-1";
527
533
  var ID = {
528
534
  values: "sheet.mutation.set-range-values",
529
535
  insertRow: "sheet.mutation.insert-row",
@@ -540,7 +546,15 @@ var ID = {
540
546
  colWidth: "sheet.mutation.set-worksheet-col-width",
541
547
  name: "sheet.mutation.set-worksheet-name",
542
548
  setName: "formula.mutation.set-defined-name",
543
- removeName: "formula.mutation.remove-defined-name"
549
+ removeName: "formula.mutation.remove-defined-name",
550
+ setNumfmt: "sheet.mutation.set.numfmt",
551
+ removeNumfmt: "sheet.mutation.remove.numfmt",
552
+ rowCount: "sheet.mutation.set-worksheet-row-count",
553
+ colCount: "sheet.mutation.set-worksheet-column-count",
554
+ insertSheet: "sheet.mutation.insert-sheet",
555
+ removeSheet: "sheet.mutation.remove-sheet",
556
+ sheetOrder: "sheet.mutation.set-worksheet-order",
557
+ sort: "sheet.mutation.reorder-range"
544
558
  };
545
559
  var PAIR_BARRIERS = {};
546
560
  var pairKey = (a, b) => a < b ? `${a}|${b}` : `${b}|${a}`;
@@ -652,6 +666,20 @@ function mergeThrough(s, m) {
652
666
  }
653
667
  return { range: m, fixup: false };
654
668
  }
669
+ function mergeFixupsOwed(m, mergesBefore) {
670
+ const st = structuralOf(m);
671
+ if (!st) return null;
672
+ const remove = [];
673
+ const add = [];
674
+ for (const q of mergesBefore) {
675
+ const t = mergeThrough(st, q);
676
+ if (!t.fixup) continue;
677
+ remove.push({ startRow: q.startRow, endRow: q.endRow, startColumn: q.startColumn, endColumn: q.endColumn, ...q.rangeType !== void 0 ? { rangeType: q.rangeType } : {} });
678
+ if (t.range) add.push({ ...t.range });
679
+ }
680
+ return remove.length ? { remove, add } : null;
681
+ }
682
+ var isStructuralMutation = (m) => structuralOf(m) !== null;
655
683
  function exactMergeRemovals(group, merges) {
656
684
  const sim = new Map(Object.entries(merges).map(([k, v]) => [k, v.map((r) => ({ ...r }))]));
657
685
  const out = [];
@@ -698,8 +726,24 @@ function cellsMutation(template, cells) {
698
726
  function putCell(cells, r, c, v) {
699
727
  (cells[r] ??= {})[c] = v;
700
728
  }
729
+ function cellsOverlap(a, b) {
730
+ for (const r of Object.keys(a)) {
731
+ const br = b[r];
732
+ if (!br) continue;
733
+ for (const c of Object.keys(a[r] ?? {})) if (c in br) return true;
734
+ }
735
+ return false;
736
+ }
701
737
  var isEmptyMatrix = (cells) => Object.values(cells).every((row) => Object.keys(row).length === 0);
738
+ var cellBarrierCache = /* @__PURE__ */ new WeakMap();
702
739
  function cellBarrier(m) {
740
+ const hit = cellBarrierCache.get(m);
741
+ if (hit !== void 0) return hit;
742
+ const why = cellBarrierOf(m);
743
+ cellBarrierCache.set(m, why);
744
+ return why;
745
+ }
746
+ function cellBarrierOf(m) {
703
747
  if (m.params.isOverrideStyle) return "set-range-values with isOverrideStyle (whole-style replace) is not covered";
704
748
  let why = null;
705
749
  forEachCell(cellsOf(m), (_r, _c, v) => {
@@ -781,6 +825,7 @@ function transformCell(X, Y, side) {
781
825
  function transformCellsOverCells(x, y, side) {
782
826
  if (sheetOf(x) !== sheetOf(y)) return ok([x]);
783
827
  const ycells = cellsOf(y);
828
+ if (!cellsOverlap(cellsOf(x), ycells)) return ok([x]);
784
829
  const xd = isDerived(x);
785
830
  const yd = isDerived(y);
786
831
  const eff = derivedSide(x, y, side);
@@ -808,7 +853,7 @@ function transformCellsOverCells(x, y, side) {
808
853
  const op = passes.filter((p) => !isEmptyMatrix(p)).map((p) => cellsMutation(x, p));
809
854
  return ok(op, lost, dropped);
810
855
  }
811
- var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived"];
856
+ var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived", "sheetIndex", "sheetsLeft"];
812
857
  var isDerived = (m) => m.params.derived === true;
813
858
  function derivedSide(x, y, side) {
814
859
  const xd = isDerived(x);
@@ -830,7 +875,15 @@ function withRefSheets(params, rs) {
830
875
  return out;
831
876
  }
832
877
  var hasFormulaText = (v) => !!v && (isFormulaText(v.f) || typeof v.ref === "string");
878
+ var hasFormulaCache = /* @__PURE__ */ new WeakMap();
833
879
  function hasFormula(m) {
880
+ const hit = hasFormulaCache.get(m);
881
+ if (hit !== void 0) return hit;
882
+ const any = hasFormulaOf(m);
883
+ hasFormulaCache.set(m, any);
884
+ return any;
885
+ }
886
+ function hasFormulaOf(m) {
834
887
  let any = false;
835
888
  forEachCell(cellsOf(m), (_r, _c, v) => {
836
889
  any ||= hasFormulaText(v);
@@ -869,15 +922,18 @@ function cellThrough(sheet, change, r, c) {
869
922
  return nr === null || nc === null ? null : [nr, nc];
870
923
  }
871
924
  function cellsOverChange(x, change) {
925
+ if (!hasFormula(x) && (change.kind !== "axis" || change.sheet !== sheetOf(x))) return ok([x]);
872
926
  const ctx = formulaCtx(x);
873
927
  const out = {};
874
928
  const dropped = [];
875
929
  let why = null;
930
+ let changed = false;
876
931
  forEachCell(cellsOf(x), (r, c, v) => {
877
932
  if (why) return;
878
933
  const at = cellThrough(sheetOf(x), change, r, c);
879
934
  if (!at) {
880
935
  dropped.push(cellRef(sheetOf(x), r, c));
936
+ changed = true;
881
937
  return;
882
938
  }
883
939
  let e = v;
@@ -888,14 +944,19 @@ function cellsOverChange(x, change) {
888
944
  return;
889
945
  }
890
946
  e = res.entry;
947
+ if (res.changed) changed = true;
891
948
  }
949
+ if (at[0] !== r || at[1] !== c) changed = true;
892
950
  putCell(out, at[0], at[1], e);
893
951
  });
894
952
  if (why) return barrier2(why);
895
- const params = { ...withRefSheets(x.params, refSheetsAfter(ctx.refSheets, change)), cellValue: out };
953
+ const rsAfter = refSheetsAfter(ctx.refSheets, change);
954
+ if (!changed && JSON.stringify(rsAfter ?? null) === JSON.stringify(ctx.refSheets ?? null)) return ok([x]);
955
+ const params = { ...withRefSheets(x.params, rsAfter), cellValue: out };
896
956
  return ok(isEmptyMatrix(out) ? [] : [{ id: x.id, params }], [], dropped);
897
957
  }
898
958
  function rewritesOf(y, change) {
959
+ if (!hasFormula(y)) return null;
899
960
  const ctx = formulaCtx(y);
900
961
  const out = {};
901
962
  let why = null;
@@ -1041,6 +1102,368 @@ function buildSize(template, values) {
1041
1102
  const value = uniform !== void 0 && [...values.values()].every((v) => v === uniform) ? uniform : Object.fromEntries(values);
1042
1103
  return { id: template.id, params: { ...p, ranges, [field]: value } };
1043
1104
  }
1105
+ var isNumfmt = (m) => m.id === ID.setNumfmt || m.id === ID.removeNumfmt;
1106
+ function nfParts(m) {
1107
+ if (m.id === ID.removeNumfmt) return [{ key: null, ranges: (m.params.ranges ?? []).map((r) => ({ ...r })) }];
1108
+ const values = m.params.values ?? {};
1109
+ return Object.keys(values).map((k) => ({ key: k, ranges: (values[k]?.ranges ?? []).map((r) => ({ ...r })) }));
1110
+ }
1111
+ function nfBuild(m, parts) {
1112
+ const keep = parts.filter((p) => p.ranges.length);
1113
+ if (!keep.length) return [];
1114
+ if (m.id === ID.removeNumfmt) return [{ id: m.id, params: { ...m.params, ranges: keep[0].ranges } }];
1115
+ const values = {};
1116
+ const refMap = {};
1117
+ const srcValues = m.params.values ?? {};
1118
+ const srcRef = m.params.refMap ?? {};
1119
+ for (const p of keep) {
1120
+ values[p.key] = { ...srcValues[p.key], ranges: p.ranges };
1121
+ refMap[p.key] = srcRef[p.key];
1122
+ }
1123
+ return [{ id: m.id, params: { ...m.params, values, refMap } }];
1124
+ }
1125
+ function nfValueAt(m, row, col) {
1126
+ for (const p of nfParts(m))
1127
+ if (p.ranges.some((r) => r.startRow <= row && row <= r.endRow && r.startColumn <= col && col <= r.endColumn)) {
1128
+ if (p.key === null) return null;
1129
+ const ref = (m.params.refMap ?? {})[p.key];
1130
+ return { pattern: ref?.pattern };
1131
+ }
1132
+ return void 0;
1133
+ }
1134
+ function spanThrough(op, lo, hi) {
1135
+ const pieces = [];
1136
+ if (op.kind === "insert") pieces.push([0, op.at - 1, 0], [op.at, Infinity, op.count]);
1137
+ else if (op.kind === "remove") pieces.push([0, op.start - 1, 0], [op.start + op.count, Infinity, -op.count]);
1138
+ else if (op.count <= 0 || op.to >= op.from && op.to <= op.from + op.count) pieces.push([0, Infinity, 0]);
1139
+ else if (op.to > op.from) {
1140
+ const n = op.count;
1141
+ 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]);
1142
+ } else {
1143
+ const n = op.count;
1144
+ 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]);
1145
+ }
1146
+ const out = [];
1147
+ for (const [a, b, d] of pieces) {
1148
+ const x0 = Math.max(a, lo);
1149
+ const x1 = Math.min(b, hi);
1150
+ if (x0 <= x1) out.push([x0 + d, x1 + d]);
1151
+ }
1152
+ out.sort((p, q) => p[0] - q[0]);
1153
+ const merged = [];
1154
+ for (const p of out) {
1155
+ const last = merged[merged.length - 1];
1156
+ if (last && last[1] + 1 >= p[0]) last[1] = Math.max(last[1], p[1]);
1157
+ else merged.push([p[0], p[1]]);
1158
+ }
1159
+ return merged;
1160
+ }
1161
+ function rectsThrough(st, rects) {
1162
+ const [a0, a1] = S(st.axis);
1163
+ const out = [];
1164
+ for (const r of rects) for (const [lo, hi] of spanThrough(st.op, r[a0], r[a1])) out.push({ ...r, [a0]: lo, [a1]: hi });
1165
+ return out;
1166
+ }
1167
+ function rectMinus(a, b) {
1168
+ if (a.startRow > b.endRow || b.startRow > a.endRow || a.startColumn > b.endColumn || b.startColumn > a.endColumn) return [a];
1169
+ const out = [];
1170
+ if (a.startRow < b.startRow) out.push({ ...a, endRow: b.startRow - 1 });
1171
+ if (a.endRow > b.endRow) out.push({ ...a, startRow: b.endRow + 1 });
1172
+ const r0 = Math.max(a.startRow, b.startRow);
1173
+ const r1 = Math.min(a.endRow, b.endRow);
1174
+ if (a.startColumn < b.startColumn) out.push({ ...a, startRow: r0, endRow: r1, endColumn: b.startColumn - 1 });
1175
+ if (a.endColumn > b.endColumn) out.push({ ...a, startRow: r0, endRow: r1, startColumn: b.endColumn + 1 });
1176
+ return out;
1177
+ }
1178
+ function rectsMinus(as, bs) {
1179
+ let cur = as;
1180
+ for (const b of bs) cur = cur.flatMap((a) => rectMinus(a, b));
1181
+ return cur;
1182
+ }
1183
+ function nWrites(m) {
1184
+ const out = [];
1185
+ forEachCell(cellsOf(m), (r, c, v) => {
1186
+ if (v === null || v.s === null || v.s && typeof v.s === "object" && "n" in v.s)
1187
+ out.push({ startRow: r, endRow: r, startColumn: c, endColumn: c });
1188
+ });
1189
+ return out;
1190
+ }
1191
+ function numfmtPair(x, y, side, sx, sy) {
1192
+ const nx = isNumfmt(x);
1193
+ const ny = isNumfmt(y);
1194
+ if (!nx && !ny) return null;
1195
+ if (nx && sy) return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsThrough(sy, p.ranges) }))));
1196
+ if (ny && sx) return ok([x]);
1197
+ if (nx && ny) {
1198
+ if (side === "left") return ok([x]);
1199
+ const ys = nfParts(y).flatMap((p) => p.ranges);
1200
+ return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) }))));
1201
+ }
1202
+ if (nx && y.id === ID.values) {
1203
+ if (side === "left") return ok([x]);
1204
+ const ys = nWrites(y);
1205
+ return ys.length ? ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) })))) : ok([x]);
1206
+ }
1207
+ if (ny && x.id === ID.values) {
1208
+ if (side === "left") return ok([x]);
1209
+ const out = {};
1210
+ const tail = {};
1211
+ let touched = false;
1212
+ forEachCell(cellsOf(x), (r, c, v) => {
1213
+ const n = nfValueAt(y, r, c);
1214
+ if (n === void 0) return putCell(out, r, c, v);
1215
+ if (v === null || v.s === null) {
1216
+ putCell(out, r, c, v);
1217
+ if (n) {
1218
+ putCell(tail, r, c, { s: { n } });
1219
+ touched = true;
1220
+ }
1221
+ return;
1222
+ }
1223
+ if (v.s && typeof v.s === "object" && "n" in v.s) {
1224
+ const { n: _drop, ...rest } = v.s;
1225
+ const e = { ...v };
1226
+ if (Object.keys(rest).length) e.s = rest;
1227
+ else delete e.s;
1228
+ touched = true;
1229
+ if (Object.keys(e).length) putCell(out, r, c, e);
1230
+ return;
1231
+ }
1232
+ putCell(out, r, c, v);
1233
+ });
1234
+ if (!touched) return ok([x]);
1235
+ const ops = [];
1236
+ if (!isEmptyMatrix(out)) ops.push(cellsMutation(x, out));
1237
+ if (!isEmptyMatrix(tail)) ops.push({ id: ID.values, params: { subUnitId: sheetOf(x), cellValue: tail } });
1238
+ return ok(ops);
1239
+ }
1240
+ return ok([x]);
1241
+ }
1242
+ var isCount = (m) => m.id === ID.rowCount || m.id === ID.colCount;
1243
+ var countAxis = (m) => m.id === ID.rowCount ? "row" : "col";
1244
+ var countKey = (m) => m.id === ID.rowCount ? "rowCount" : "columnCount";
1245
+ function countPair(x, y, sy) {
1246
+ if (!isCount(x)) return isCount(y) ? ok([x]) : null;
1247
+ const k = countKey(x);
1248
+ const v = Number(x.params[k]);
1249
+ if (sy) {
1250
+ if (sy.axis !== countAxis(x) || sy.op.kind === "move") return ok([x]);
1251
+ const d = sy.op.kind === "insert" ? sy.op.count : -sy.op.count;
1252
+ return ok([{ id: x.id, params: { ...x.params, [k]: Math.max(1, v + d) } }]);
1253
+ }
1254
+ if (isCount(y) && y.id === x.id) {
1255
+ const w = Number(y.params[k]);
1256
+ return ok([w > v ? { id: x.id, params: { ...x.params, [k]: w } } : x]);
1257
+ }
1258
+ return ok([x]);
1259
+ }
1260
+ var isSheetOp = (m) => m.id === ID.insertSheet || m.id === ID.removeSheet || m.id === ID.sheetOrder;
1261
+ var sheetIdOf = (m) => m.id === ID.insertSheet ? String(m.params.sheet?.id ?? "") : sheetOf(m);
1262
+ function sheetAxisOf(m) {
1263
+ const p = m.params;
1264
+ if (m.id === ID.insertSheet) return typeof p.index === "number" ? { kind: "insert", at: p.index, count: 1 } : null;
1265
+ if (m.id === ID.removeSheet) return typeof p.sheetIndex === "number" ? { kind: "remove", start: p.sheetIndex, count: 1 } : null;
1266
+ if (m.id === ID.sheetOrder) {
1267
+ const f = Number(p.fromOrder);
1268
+ const t = Number(p.toOrder);
1269
+ return { kind: "move", from: f, count: 1, to: t > f ? t + 1 : t };
1270
+ }
1271
+ return null;
1272
+ }
1273
+ function buildSheetOp(template, op) {
1274
+ if (op.kind === "insert") return { id: template.id, params: { ...template.params, index: op.at } };
1275
+ if (op.kind === "remove") return { id: template.id, params: { ...template.params, sheetIndex: op.start } };
1276
+ return { id: template.id, params: { ...template.params, fromOrder: op.from, toOrder: op.to > op.from ? op.to - 1 : op.to } };
1277
+ }
1278
+ function namesSheet(m, id) {
1279
+ const rs = refSheetsOf(m);
1280
+ return !!rs && Object.values(rs).includes(id) && (m.id === ID.setName || hasFormula(m));
1281
+ }
1282
+ function sheetPair(x, y, side) {
1283
+ const xs = isSheetOp(x);
1284
+ const ys = isSheetOp(y);
1285
+ if (!xs && !ys) return null;
1286
+ if (xs && ys) {
1287
+ const a = sheetAxisOf(x);
1288
+ const b = sheetAxisOf(y);
1289
+ if (!a || !b) return barrier2("a sheet add or remove without its index (capture must call annotateSheetOps)");
1290
+ if (x.id === ID.insertSheet && y.id === ID.insertSheet && sheetIdOf(x) === sheetIdOf(y)) {
1291
+ return JSON.stringify(x.params) === JSON.stringify(y.params) ? ok([]) : barrier2("the same sheet id added twice with different content");
1292
+ }
1293
+ if (x.id === ID.removeSheet && typeof x.params.sheetsLeft === "number" && sheetIdOf(x) !== sheetIdOf(y) && (y.id === ID.removeSheet || y.id === ID.insertSheet)) {
1294
+ const left = x.params.sheetsLeft + (y.id === ID.insertSheet ? 1 : -1);
1295
+ if (left < 1) return barrier2("concurrent sheet removes would leave the workbook without a sheet");
1296
+ const out2 = transformAxis(a, b, side);
1297
+ if (!out2) return barrier2("sheet order: no transform");
1298
+ return ok(out2.map((o) => buildSheetOp({ id: x.id, params: { ...x.params, sheetsLeft: left } }, o)));
1299
+ }
1300
+ const out = transformAxis(a, b, side);
1301
+ if (!out) return barrier2("sheet order: no transform");
1302
+ return ok(out.map((o) => buildSheetOp(x, o)), [], out.length === 0 && x.id === ID.sheetOrder ? [`${sheetIdOf(x)}: sheet order`] : []);
1303
+ }
1304
+ if (ys) {
1305
+ if (y.id !== ID.removeSheet) return ok([x]);
1306
+ const gone2 = sheetOf(y);
1307
+ if (namesSheet(x, gone2)) return barrier2("a formula names a sheet removed at the same time");
1308
+ if (sheetOf(x) === gone2 && x.id !== ID.setName && x.id !== ID.removeName)
1309
+ return ok([], [], isDerived(x) ? [] : [`${gone2}: the sheet was removed`]);
1310
+ return ok([x]);
1311
+ }
1312
+ if (x.id !== ID.removeSheet) return ok([x]);
1313
+ const gone = sheetOf(x);
1314
+ if (namesSheet(y, gone)) return barrier2("a formula names a sheet removed at the same time");
1315
+ return ok([x], sheetOf(y) === gone && !isDerived(y) && y.id !== ID.setName && y.id !== ID.removeName ? [`${gone}: the sheet was removed`] : []);
1316
+ }
1317
+ function annotateSheetOps(group, sheetOrder) {
1318
+ const order = [...sheetOrder];
1319
+ return group.map((m) => {
1320
+ if (m.id === ID.insertSheet) {
1321
+ const index = typeof m.params.index === "number" ? Math.min(m.params.index, order.length) : order.length;
1322
+ order.splice(index, 0, sheetIdOf(m));
1323
+ return index === m.params.index ? m : { id: m.id, params: { ...m.params, index } };
1324
+ }
1325
+ if (m.id === ID.removeSheet) {
1326
+ const i = order.indexOf(sheetOf(m));
1327
+ if (i >= 0) order.splice(i, 1);
1328
+ return i >= 0 ? { id: m.id, params: { ...m.params, sheetIndex: i, sheetsLeft: order.length } } : m;
1329
+ }
1330
+ if (m.id === ID.sheetOrder) {
1331
+ const f = Number(m.params.fromOrder);
1332
+ const [id] = order.splice(f, 1);
1333
+ order.splice(Number(m.params.toOrder), 0, id ?? sheetOf(m));
1334
+ }
1335
+ return m;
1336
+ });
1337
+ }
1338
+ function inlineSheetStyles(sheet, styleOf) {
1339
+ const out = JSON.parse(JSON.stringify(sheet));
1340
+ const fix = (o) => {
1341
+ if (o && typeof o.s === "string") {
1342
+ const st = styleOf(o.s);
1343
+ if (st) o.s = JSON.parse(JSON.stringify(st));
1344
+ }
1345
+ };
1346
+ for (const row of Object.values(out.cellData ?? {})) for (const cell of Object.values(row ?? {})) fix(cell);
1347
+ for (const key of ["rowData", "columnData"]) for (const v of Object.values(out[key] ?? {})) fix(v);
1348
+ if (typeof out.defaultStyle === "string") {
1349
+ const st = styleOf(out.defaultStyle);
1350
+ if (st) out.defaultStyle = JSON.parse(JSON.stringify(st));
1351
+ }
1352
+ return out;
1353
+ }
1354
+ var isSort = (m) => m.id === ID.sort;
1355
+ function sortOf(m) {
1356
+ const P = /* @__PURE__ */ new Map();
1357
+ for (const [t, src] of Object.entries(m.params.order ?? {})) P.set(Number(t), Number(src));
1358
+ return { range: { ...m.params.range }, P };
1359
+ }
1360
+ var sortQ = (o) => new Map([...o.P].map(([t, s0]) => [s0, t]));
1361
+ function buildSort(template, o) {
1362
+ const order = {};
1363
+ for (const [t, s0] of [...o.P].sort((a, b) => a[0] - b[0])) if (t !== s0) order[t] = s0;
1364
+ if (!Object.keys(order).length || o.range.startRow > o.range.endRow || o.range.startColumn > o.range.endColumn) return [];
1365
+ return [{ id: template.id, params: { ...template.params, range: o.range, order } }];
1366
+ }
1367
+ var inSortCols = (o, c) => c >= o.range.startColumn && c <= o.range.endColumn;
1368
+ function sortThrough(o, st) {
1369
+ const r = o.range;
1370
+ if (st.axis === "col") {
1371
+ if (st.op.kind === "move") {
1372
+ const { from, count, to } = st.op;
1373
+ const touches = !(from + count - 1 < r.startColumn || from > r.endColumn) || to > r.startColumn && to <= r.endColumn;
1374
+ if (touches) return null;
1375
+ const spans3 = spanThrough(st.op, r.startColumn, r.endColumn);
1376
+ return spans3.length === 1 ? { range: { ...r, startColumn: spans3[0][0], endColumn: spans3[0][1] }, P: o.P } : null;
1377
+ }
1378
+ const spans2 = spanThrough(st.op, r.startColumn, r.endColumn);
1379
+ if (!spans2.length) return { range: { ...r, startColumn: 1, endColumn: 0 }, P: /* @__PURE__ */ new Map() };
1380
+ return { range: { ...r, startColumn: spans2[0][0], endColumn: spans2[spans2.length - 1][1] }, P: o.P };
1381
+ }
1382
+ const op = st.op;
1383
+ if (op.kind === "move") {
1384
+ const touches = !(op.from + op.count - 1 < r.startRow || op.from > r.endRow) || op.to > r.startRow && op.to <= r.endRow;
1385
+ if (touches) return null;
1386
+ }
1387
+ if (op.kind === "insert" || op.kind === "move") {
1388
+ const P2 = /* @__PURE__ */ new Map();
1389
+ for (const [t, s0] of o.P) P2.set(mapIndex(op, t), mapIndex(op, s0));
1390
+ const spans2 = spanThrough(op, r.startRow, r.endRow);
1391
+ return { range: { ...r, startRow: spans2[0][0], endRow: spans2[spans2.length - 1][1] }, P: P2 };
1392
+ }
1393
+ const removed = (i) => i >= op.start && i < op.start + op.count;
1394
+ const Q = sortQ(o);
1395
+ const goneTargets = [];
1396
+ for (let i = op.start; i < op.start + op.count; i++) goneTargets.push(Q.get(i) ?? i);
1397
+ goneTargets.sort((a, b) => a - b);
1398
+ const compact = (t) => t - goneTargets.filter((g) => g < t).length;
1399
+ const P = /* @__PURE__ */ new Map();
1400
+ for (const [t, s0] of o.P) if (!removed(s0)) P.set(compact(t), mapIndex(op, s0));
1401
+ const spans = spanThrough(op, r.startRow, r.endRow);
1402
+ if (!spans.length) return { range: { ...r, startRow: 1, endRow: 0 }, P: /* @__PURE__ */ new Map() };
1403
+ return { range: { ...r, startRow: spans[0][0], endRow: spans[spans.length - 1][1] }, P };
1404
+ }
1405
+ function sortPair(x, y, sx, sy) {
1406
+ const xs = isSort(x);
1407
+ const ys = isSort(y);
1408
+ if (!xs && !ys) return null;
1409
+ if (xs && ys) {
1410
+ const a = sortOf(x).range;
1411
+ const b = sortOf(y).range;
1412
+ const overlap = a.startRow <= b.endRow && b.startRow <= a.endRow && a.startColumn <= b.endColumn && b.startColumn <= a.endColumn;
1413
+ return overlap ? barrier2("two sorts of overlapping ranges at once") : ok([x]);
1414
+ }
1415
+ if (xs) {
1416
+ if (sy) {
1417
+ const t = sortThrough(sortOf(x), sy);
1418
+ return t ? ok(buildSort(x, t)) : barrier2("a row or column move through a sorted range");
1419
+ }
1420
+ return ok([x]);
1421
+ }
1422
+ const o = sortOf(y);
1423
+ const Q = sortQ(o);
1424
+ const moved = (r) => r >= o.range.startRow && r <= o.range.endRow && Q.has(r);
1425
+ if (sx) {
1426
+ if (sx.axis === "col") return sx.op.kind === "move" && !sortThrough(o, sx) ? barrier2("a column move through a sorted range") : ok([x]);
1427
+ if (sx.op.kind === "insert") return ok([x]);
1428
+ if (sx.op.kind === "move") return sortThrough(o, sx) ? ok([x]) : barrier2("a row move through a sorted range");
1429
+ const rows = [];
1430
+ let partial = false;
1431
+ for (let i = sx.op.start; i < sx.op.start + sx.op.count; i++) {
1432
+ if (moved(i)) partial = true;
1433
+ rows.push(Q.get(i) ?? i);
1434
+ }
1435
+ if (partial) return barrier2("a row remove against a concurrent sort of those rows");
1436
+ return ok([x]);
1437
+ }
1438
+ if (x.id === ID.values) {
1439
+ const out = {};
1440
+ let changed = false;
1441
+ forEachCell(cellsOf(x), (r, c, v) => {
1442
+ if (inSortCols(o, c) && moved(r)) {
1443
+ putCell(out, Q.get(r), c, v);
1444
+ changed = true;
1445
+ } else putCell(out, r, c, v);
1446
+ });
1447
+ return changed ? ok([cellsMutation(x, out)]) : ok([x]);
1448
+ }
1449
+ if (isNumfmt(x)) {
1450
+ const parts = nfParts(x).map((p) => {
1451
+ const ranges = [];
1452
+ for (const rc of p.ranges) {
1453
+ const inner = { ...rc, startRow: Math.max(rc.startRow, o.range.startRow), endRow: Math.min(rc.endRow, o.range.endRow), startColumn: Math.max(rc.startColumn, o.range.startColumn), endColumn: Math.min(rc.endColumn, o.range.endColumn) };
1454
+ if (inner.startRow > inner.endRow || inner.startColumn > inner.endColumn) {
1455
+ ranges.push(rc);
1456
+ continue;
1457
+ }
1458
+ ranges.push(...rectMinus(rc, inner));
1459
+ for (let r = inner.startRow; r <= inner.endRow; r++) ranges.push({ ...inner, startRow: Q.get(r) ?? r, endRow: Q.get(r) ?? r });
1460
+ }
1461
+ return { ...p, ranges };
1462
+ });
1463
+ return ok(nfBuild(x, parts));
1464
+ }
1465
+ return ok([x]);
1466
+ }
1044
1467
  function familyOf(id) {
1045
1468
  const e = SHEET_MUTATIONS[id];
1046
1469
  return e && e.kind === "covered" ? e.family : "";
@@ -1057,6 +1480,8 @@ function transformMutation(x, y, side) {
1057
1480
  }
1058
1481
  const pb = PAIR_BARRIERS[pairKey(x.id, y.id)];
1059
1482
  if (pb) return barrier2(`${x.id} \xD7 ${y.id}: ${pb.reason}${pb.seed === null ? "" : ` (fuzz seed ${pb.seed})`}`);
1483
+ const sp = sheetPair(x, y, side);
1484
+ if (sp) return sp;
1060
1485
  const fp = formulaPair(x, y, side);
1061
1486
  if (fp) return fp;
1062
1487
  if (sheetOf(x) !== sheetOf(y)) return ok([x]);
@@ -1064,6 +1489,12 @@ function transformMutation(x, y, side) {
1064
1489
  const fy = familyOf(y.id);
1065
1490
  const sy = structuralOf(y);
1066
1491
  const sx = structuralOf(x);
1492
+ const sortp = sortPair(x, y, sx, sy);
1493
+ if (sortp) return sortp;
1494
+ const cp = countPair(x, y, sy);
1495
+ if (cp) return cp;
1496
+ const np = numfmtPair(x, y, side, sx, sy);
1497
+ if (np) return np;
1067
1498
  if (sx && sy) {
1068
1499
  if (sx.axis !== sy.axis) return ok([x]);
1069
1500
  const out = transformAxis(sx.op, sy.op, side);
@@ -1171,22 +1602,45 @@ function tSeq(xs, ys, side, acc) {
1171
1602
  acc.dropped.push(...a.dropped);
1172
1603
  return [a.op, b.op];
1173
1604
  }
1605
+ const outX = [];
1606
+ let curY = ys;
1607
+ for (const x of xs) {
1608
+ let curX = [x];
1609
+ const nextY = [];
1610
+ for (const y of curY) {
1611
+ if (curX.length === 0) {
1612
+ nextY.push(y);
1613
+ continue;
1614
+ }
1615
+ const r = tSeq(curX, [y], side, acc);
1616
+ if (blocked(r)) return r;
1617
+ curX = r[0];
1618
+ for (const m of r[1]) nextY.push(m);
1619
+ }
1620
+ for (const m of curX) outX.push(m);
1621
+ curY = nextY;
1622
+ }
1623
+ return [outX, curY];
1624
+ }
1625
+ function tSeqOld(xs, ys, side, acc) {
1626
+ if (xs.length === 0 || ys.length === 0) return [xs, ys];
1627
+ if (xs.length === 1 && ys.length === 1) return tSeq(xs, ys, side, acc);
1174
1628
  if (xs.length > 1) {
1175
- const first2 = tSeq([xs[0]], ys, side, acc);
1629
+ const first2 = tSeqOld([xs[0]], ys, side, acc);
1176
1630
  if (blocked(first2)) return first2;
1177
- const rest2 = tSeq(xs.slice(1), first2[1], side, acc);
1631
+ const rest2 = tSeqOld(xs.slice(1), first2[1], side, acc);
1178
1632
  if (blocked(rest2)) return rest2;
1179
1633
  return [[...first2[0], ...rest2[0]], rest2[1]];
1180
1634
  }
1181
- const first = tSeq(xs, [ys[0]], side, acc);
1635
+ const first = tSeqOld(xs, [ys[0]], side, acc);
1182
1636
  if (blocked(first)) return first;
1183
- const rest = tSeq(first[0], ys.slice(1), side, acc);
1637
+ const rest = tSeqOld(first[0], ys.slice(1), side, acc);
1184
1638
  if (blocked(rest)) return rest;
1185
1639
  return [rest[0], [...first[1], ...rest[1]]];
1186
1640
  }
1187
1641
  function transformGroup(x, y, side) {
1188
1642
  const acc = { lost: [], dropped: [] };
1189
- const r = tSeq([...x], [...y], side, acc);
1643
+ const r = (process.env.OLD_TSEQ ? tSeqOld : tSeq)([...x], [...y], side, acc);
1190
1644
  if (blocked(r)) return r;
1191
1645
  return { op: r[0], lost: [...new Set(acc.lost)], dropped: [...new Set(acc.dropped)] };
1192
1646
  }