@ai-matrx/office 0.8.22 → 0.8.24

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,6 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.8.22
3
+ ## 0.8.24
4
+ ### C9: comment anchors that follow concurrent edits
5
+
6
+ - **Docs:** `addCommentAnchorActions(body, start, end, id)` anchors a passage with a `COMMENT` custom range (Univer's own builder, so the op has the link family's shape C0 proved); `commentAnchorsOf(body)` reads every anchor back (split pieces joined, Univer's inclusive end as `[start, end)`); `removeCommentAnchorActions` takes a cancelled draft's anchor out; `commentPartAnchor` / `anchorIdOfPartAnchor` are the thread's `part_anchor` (`anchor:<id>`, quote as `label`). Fuzz on the C0 corpora: anchor × every captured family and anchor × anchor, 0 divergences in 180,000 checks at 2,000 seeds; red check without a transform.
7
+ - **Sheets:** `installSheetCommentAnchors(univer)` registers `matrx.mutation.set-comment-anchor`, the `SHEET_MATRX_COMMENT_ANCHORS_PLUGIN` resource and a listener that moves anchors through every applied row/column insert, remove and move (and drops them with their row, column or sheet). `transformCommentAnchor` (own file) is called first by `transformMutation`; the registry declares the id (family `anchor`), and the census exempts our own ids from the Univer scan. Fuzz: 40,000 checks, 0 divergences (it caught the same anchor id on two sheets being treated as one); two headless seats: B5 becomes B6 when a peer inserts a row above, survives save and reload, and is gone on both seats when its row is deleted. The fold installs the anchors too.
8
+ - New export `./sheets/comment-anchors`. Consumer action: call `installSheetCommentAnchors(univer)` before creating a workbook unit that co-edits comments.
9
+
4
10
  ### C5c: Sheets co-editing — merge divergence fixed, number formats, counts and sheets travel
5
11
 
6
12
  - **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).
@@ -86,6 +86,8 @@ var SHEET_MUTATIONS = {
86
86
  "sheet.mutation.insert-sheet": covered("sheet", "index", "sheet"),
87
87
  "sheet.mutation.remove-sheet": covered("sheet", "sheetIndex", "sheetsLeft"),
88
88
  "sheet.mutation.set-worksheet-order": covered("sheet", "fromOrder", "toOrder"),
89
+ // ── covered (C9): our own cell comment anchors (comment-anchor-transform.ts; not emitted by Univer) ──────
90
+ "matrx.mutation.set-comment-anchor": covered("anchor", "anchorId", "cell"),
89
91
  "sheet.mutation.reorder-range": covered("sort", "range", "order"),
90
92
  // ── never travels ────────────────────────────────────────────────────────────────────────────────
91
93
  "formula.mutation.set-formula-calculation-start": never(FORMULA_RESULT),
@@ -528,6 +530,74 @@ function refSheetsFor(texts, sheets) {
528
530
  return Object.keys(out).length ? out : void 0;
529
531
  }
530
532
 
533
+ // src/collab/sheets/comment-anchor-transform.ts
534
+ var SET_SHEET_COMMENT_ANCHOR = "matrx.mutation.set-comment-anchor";
535
+ var REMOVE_SHEET = "sheet.mutation.remove-sheet";
536
+ function structuralAxisOp(id, params) {
537
+ const p = params;
538
+ switch (id) {
539
+ case "sheet.mutation.insert-row":
540
+ return p.range ? { axis: "row", op: { kind: "insert", at: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;
541
+ case "sheet.mutation.remove-rows":
542
+ return p.range ? { axis: "row", op: { kind: "remove", start: p.range.startRow, count: p.range.endRow - p.range.startRow + 1 } } : null;
543
+ case "sheet.mutation.insert-col":
544
+ return p.range ? { axis: "col", op: { kind: "insert", at: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;
545
+ case "sheet.mutation.remove-col":
546
+ return p.range ? { axis: "col", op: { kind: "remove", start: p.range.startColumn, count: p.range.endColumn - p.range.startColumn + 1 } } : null;
547
+ case "sheet.mutation.move-rows":
548
+ return p.sourceRange && p.targetRange ? { axis: "row", op: { kind: "move", from: p.sourceRange.startRow, count: p.sourceRange.endRow - p.sourceRange.startRow + 1, to: p.targetRange.startRow } } : null;
549
+ case "sheet.mutation.move-columns":
550
+ return p.sourceRange && p.targetRange ? {
551
+ axis: "col",
552
+ op: { kind: "move", from: p.sourceRange.startColumn, count: p.sourceRange.endColumn - p.sourceRange.startColumn + 1, to: p.targetRange.startColumn }
553
+ } : null;
554
+ default:
555
+ return null;
556
+ }
557
+ }
558
+ function moveSheetAnchorCell(cell, sheet, id, params) {
559
+ if (String(params.subUnitId ?? "") !== sheet) return cell;
560
+ if (id === REMOVE_SHEET) return null;
561
+ const s = structuralAxisOp(id, params);
562
+ if (!s) return cell;
563
+ if (s.axis === "row") {
564
+ const row = mapIndex(s.op, cell.row);
565
+ return row === null ? null : row === cell.row ? cell : { row, col: cell.col };
566
+ }
567
+ const col = mapIndex(s.op, cell.col);
568
+ return col === null ? null : col === cell.col ? cell : { row: cell.row, col };
569
+ }
570
+ function colName(c) {
571
+ let s = "";
572
+ for (let n = c + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + (n - 1) % 26) + s;
573
+ return s;
574
+ }
575
+ var sheetCellName = (cell) => `${colName(cell.col)}${cell.row + 1}`;
576
+ function cellOf(m) {
577
+ const c = m.params.cell;
578
+ return c && typeof c.row === "number" && typeof c.col === "number" ? c : null;
579
+ }
580
+ var ok = (op, lost = [], dropped = []) => ({ op, lost, dropped });
581
+ var label = (m) => {
582
+ const c = cellOf(m);
583
+ return `${String(m.params.subUnitId ?? "")}!${c ? sheetCellName(c) : "?"} comment`;
584
+ };
585
+ function transformCommentAnchor(x, y, side) {
586
+ const xa = x.id === SET_SHEET_COMMENT_ANCHOR;
587
+ const ya = y.id === SET_SHEET_COMMENT_ANCHOR;
588
+ if (!xa && !ya) return null;
589
+ if (!xa) return ok([x]);
590
+ if (ya) {
591
+ if (x.params.anchorId !== y.params.anchorId || x.params.subUnitId !== y.params.subUnitId) return ok([x]);
592
+ return side === "left" ? ok([x], [label(y)]) : ok([], [], [label(x)]);
593
+ }
594
+ const cell = cellOf(x);
595
+ if (!cell) return ok([x]);
596
+ const moved = moveSheetAnchorCell(cell, String(x.params.subUnitId ?? ""), y.id, y.params);
597
+ if (!moved) return ok([], [], [label(x)]);
598
+ return ok([moved === cell ? x : { id: x.id, params: { ...x.params, cell: moved } }]);
599
+ }
600
+
531
601
  // src/collab/sheets/transform/transform.ts
532
602
  var SHEET_TRANSFORMER_VERSION = "c5c-1";
533
603
  var ID = {
@@ -559,16 +629,16 @@ var ID = {
559
629
  var PAIR_BARRIERS = {};
560
630
  var pairKey = (a, b) => a < b ? `${a}|${b}` : `${b}|${a}`;
561
631
  var barrier2 = (reason) => ({ barrier: true, reason });
562
- var ok = (op, lost = [], dropped = []) => ({ op, lost, dropped });
632
+ var ok2 = (op, lost = [], dropped = []) => ({ op, lost, dropped });
563
633
  var flip = (side) => side === "left" ? "right" : "left";
564
634
  var sheetOf = (m) => String(m.params.subUnitId ?? "");
565
- function colName(c) {
635
+ function colName2(c) {
566
636
  let s = "";
567
637
  for (let n = c + 1; n > 0; n = Math.floor((n - 1) / 26)) s = String.fromCharCode(65 + (n - 1) % 26) + s;
568
638
  return s;
569
639
  }
570
- var cellRef = (sheet, row, col) => `${sheet}!${colName(col)}${row + 1}`;
571
- var rangeRef = (sheet, r) => `${sheet}!${colName(r.startColumn)}${r.startRow + 1}:${colName(r.endColumn)}${r.endRow + 1}`;
640
+ var cellRef = (sheet, row, col) => `${sheet}!${colName2(col)}${row + 1}`;
641
+ var rangeRef = (sheet, r) => `${sheet}!${colName2(r.startColumn)}${r.startRow + 1}:${colName2(r.endColumn)}${r.endRow + 1}`;
572
642
  function structuralOf(m) {
573
643
  const p = m.params;
574
644
  switch (m.id) {
@@ -823,9 +893,9 @@ function transformCell(X, Y, side) {
823
893
  return { entries: Object.keys(out).length ? [out, ...tail] : tail, xLost, yLost };
824
894
  }
825
895
  function transformCellsOverCells(x, y, side) {
826
- if (sheetOf(x) !== sheetOf(y)) return ok([x]);
896
+ if (sheetOf(x) !== sheetOf(y)) return ok2([x]);
827
897
  const ycells = cellsOf(y);
828
- if (!cellsOverlap(cellsOf(x), ycells)) return ok([x]);
898
+ if (!cellsOverlap(cellsOf(x), ycells)) return ok2([x]);
829
899
  const xd = isDerived(x);
830
900
  const yd = isDerived(y);
831
901
  const eff = derivedSide(x, y, side);
@@ -851,7 +921,7 @@ function transformCellsOverCells(x, y, side) {
851
921
  });
852
922
  if (why) return barrier2(why);
853
923
  const op = passes.filter((p) => !isEmptyMatrix(p)).map((p) => cellsMutation(x, p));
854
- return ok(op, lost, dropped);
924
+ return ok2(op, lost, dropped);
855
925
  }
856
926
  var SIDE_CAR_PARAMS = ["refSheets", "previousName", "derived", "sheetIndex", "sheetsLeft"];
857
927
  var isDerived = (m) => m.params.derived === true;
@@ -922,7 +992,7 @@ function cellThrough(sheet, change, r, c) {
922
992
  return nr === null || nc === null ? null : [nr, nc];
923
993
  }
924
994
  function cellsOverChange(x, change) {
925
- if (!hasFormula(x) && (change.kind !== "axis" || change.sheet !== sheetOf(x))) return ok([x]);
995
+ if (!hasFormula(x) && (change.kind !== "axis" || change.sheet !== sheetOf(x))) return ok2([x]);
926
996
  const ctx = formulaCtx(x);
927
997
  const out = {};
928
998
  const dropped = [];
@@ -951,9 +1021,9 @@ function cellsOverChange(x, change) {
951
1021
  });
952
1022
  if (why) return barrier2(why);
953
1023
  const rsAfter = refSheetsAfter(ctx.refSheets, change);
954
- if (!changed && JSON.stringify(rsAfter ?? null) === JSON.stringify(ctx.refSheets ?? null)) return ok([x]);
1024
+ if (!changed && JSON.stringify(rsAfter ?? null) === JSON.stringify(ctx.refSheets ?? null)) return ok2([x]);
955
1025
  const params = { ...withRefSheets(x.params, rsAfter), cellValue: out };
956
- return ok(isEmptyMatrix(out) ? [] : [{ id: x.id, params }], [], dropped);
1026
+ return ok2(isEmptyMatrix(out) ? [] : [{ id: x.id, params }], [], dropped);
957
1027
  }
958
1028
  function rewritesOf(y, change) {
959
1029
  if (!hasFormula(y)) return null;
@@ -1000,36 +1070,36 @@ function formulaPair(x, y, side) {
1000
1070
  const fy = familyOf(y.id);
1001
1071
  const SET_NAME_BARRIER = "set-defined-name without previousName (capture must record the name it replaces) against formula text";
1002
1072
  if (fx === "defname" && fy === "defname") {
1003
- if (String(x.params.id) !== String(y.params.id)) return ok([x]);
1073
+ if (String(x.params.id) !== String(y.params.id)) return ok2([x]);
1004
1074
  const eff = derivedSide(x, y, side);
1005
- if (eff === "left") return ok([x], isDerived(y) ? [] : [`defined name "${String(y.params.name)}"`]);
1006
- return ok([], [], isDerived(x) ? [] : [`defined name "${String(x.params.name)}"`]);
1075
+ if (eff === "left") return ok2([x], isDerived(y) ? [] : [`defined name "${String(y.params.name)}"`]);
1076
+ return ok2([], [], isDerived(x) ? [] : [`defined name "${String(x.params.name)}"`]);
1007
1077
  }
1008
1078
  if (fx === "defname") {
1009
1079
  if (fy === "cell") {
1010
1080
  if (setNameWithoutPrevious(x) && hasFormula(y)) return barrier2(SET_NAME_BARRIER);
1011
1081
  const ch2 = refChangeOf(x);
1012
- if (!ch2) return ok([x]);
1082
+ if (!ch2) return ok2([x]);
1013
1083
  const add = rewritesOf(y, ch2);
1014
1084
  if (typeof add === "string") return barrier2(add);
1015
- return ok(add ? [x, add] : [x]);
1085
+ return ok2(add ? [x, add] : [x]);
1016
1086
  }
1017
1087
  const ch = refChangeOf(y);
1018
- if (!ch) return ok([x]);
1088
+ if (!ch) return ok2([x]);
1019
1089
  const r = definedNameThrough(x, ch);
1020
- return typeof r === "string" ? barrier2(r) : ok([r.op]);
1090
+ return typeof r === "string" ? barrier2(r) : ok2([r.op]);
1021
1091
  }
1022
1092
  if (fy === "defname") {
1023
1093
  if (fx === "cell") {
1024
1094
  if (setNameWithoutPrevious(y) && hasFormula(x)) return barrier2(SET_NAME_BARRIER);
1025
1095
  const ch2 = refChangeOf(y);
1026
- return ch2 ? cellsOverChange(x, ch2) : ok([x]);
1096
+ return ch2 ? cellsOverChange(x, ch2) : ok2([x]);
1027
1097
  }
1028
1098
  const ch = refChangeOf(x);
1029
- if (!ch) return ok([x]);
1099
+ if (!ch) return ok2([x]);
1030
1100
  const r = definedNameThrough(y, ch);
1031
1101
  if (typeof r === "string") return barrier2(r);
1032
- return ok(r.changed ? [x, asDerived(r.op)] : [x]);
1102
+ return ok2(r.changed ? [x, asDerived(r.op)] : [x]);
1033
1103
  }
1034
1104
  if (fx === "cell") {
1035
1105
  const ch = refChangeOf(y);
@@ -1044,7 +1114,7 @@ function formulaPair(x, y, side) {
1044
1114
  });
1045
1115
  const add = rewritesOf(y, ch);
1046
1116
  if (typeof add === "string") return barrier2(add);
1047
- return ok(add ? [x, add] : [x], lost);
1117
+ return ok2(add ? [x, add] : [x], lost);
1048
1118
  }
1049
1119
  return null;
1050
1120
  }
@@ -1192,20 +1262,20 @@ function numfmtPair(x, y, side, sx, sy) {
1192
1262
  const nx = isNumfmt(x);
1193
1263
  const ny = isNumfmt(y);
1194
1264
  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]);
1265
+ if (nx && sy) return ok2(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsThrough(sy, p.ranges) }))));
1266
+ if (ny && sx) return ok2([x]);
1197
1267
  if (nx && ny) {
1198
- if (side === "left") return ok([x]);
1268
+ if (side === "left") return ok2([x]);
1199
1269
  const ys = nfParts(y).flatMap((p) => p.ranges);
1200
- return ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) }))));
1270
+ return ok2(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) }))));
1201
1271
  }
1202
1272
  if (nx && y.id === ID.values) {
1203
- if (side === "left") return ok([x]);
1273
+ if (side === "left") return ok2([x]);
1204
1274
  const ys = nWrites(y);
1205
- return ys.length ? ok(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) })))) : ok([x]);
1275
+ return ys.length ? ok2(nfBuild(x, nfParts(x).map((p) => ({ ...p, ranges: rectsMinus(p.ranges, ys) })))) : ok2([x]);
1206
1276
  }
1207
1277
  if (ny && x.id === ID.values) {
1208
- if (side === "left") return ok([x]);
1278
+ if (side === "left") return ok2([x]);
1209
1279
  const out = {};
1210
1280
  const tail = {};
1211
1281
  let touched = false;
@@ -1231,31 +1301,31 @@ function numfmtPair(x, y, side, sx, sy) {
1231
1301
  }
1232
1302
  putCell(out, r, c, v);
1233
1303
  });
1234
- if (!touched) return ok([x]);
1304
+ if (!touched) return ok2([x]);
1235
1305
  const ops = [];
1236
1306
  if (!isEmptyMatrix(out)) ops.push(cellsMutation(x, out));
1237
1307
  if (!isEmptyMatrix(tail)) ops.push({ id: ID.values, params: { subUnitId: sheetOf(x), cellValue: tail } });
1238
- return ok(ops);
1308
+ return ok2(ops);
1239
1309
  }
1240
- return ok([x]);
1310
+ return ok2([x]);
1241
1311
  }
1242
1312
  var isCount = (m) => m.id === ID.rowCount || m.id === ID.colCount;
1243
1313
  var countAxis = (m) => m.id === ID.rowCount ? "row" : "col";
1244
1314
  var countKey = (m) => m.id === ID.rowCount ? "rowCount" : "columnCount";
1245
1315
  function countPair(x, y, sy) {
1246
- if (!isCount(x)) return isCount(y) ? ok([x]) : null;
1316
+ if (!isCount(x)) return isCount(y) ? ok2([x]) : null;
1247
1317
  const k = countKey(x);
1248
1318
  const v = Number(x.params[k]);
1249
1319
  if (sy) {
1250
- if (sy.axis !== countAxis(x) || sy.op.kind === "move") return ok([x]);
1320
+ if (sy.axis !== countAxis(x) || sy.op.kind === "move") return ok2([x]);
1251
1321
  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) } }]);
1322
+ return ok2([{ id: x.id, params: { ...x.params, [k]: Math.max(1, v + d) } }]);
1253
1323
  }
1254
1324
  if (isCount(y) && y.id === x.id) {
1255
1325
  const w = Number(y.params[k]);
1256
- return ok([w > v ? { id: x.id, params: { ...x.params, [k]: w } } : x]);
1326
+ return ok2([w > v ? { id: x.id, params: { ...x.params, [k]: w } } : x]);
1257
1327
  }
1258
- return ok([x]);
1328
+ return ok2([x]);
1259
1329
  }
1260
1330
  var isSheetOp = (m) => m.id === ID.insertSheet || m.id === ID.removeSheet || m.id === ID.sheetOrder;
1261
1331
  var sheetIdOf = (m) => m.id === ID.insertSheet ? String(m.params.sheet?.id ?? "") : sheetOf(m);
@@ -1288,31 +1358,31 @@ function sheetPair(x, y, side) {
1288
1358
  const b = sheetAxisOf(y);
1289
1359
  if (!a || !b) return barrier2("a sheet add or remove without its index (capture must call annotateSheetOps)");
1290
1360
  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");
1361
+ return JSON.stringify(x.params) === JSON.stringify(y.params) ? ok2([]) : barrier2("the same sheet id added twice with different content");
1292
1362
  }
1293
1363
  if (x.id === ID.removeSheet && typeof x.params.sheetsLeft === "number" && sheetIdOf(x) !== sheetIdOf(y) && (y.id === ID.removeSheet || y.id === ID.insertSheet)) {
1294
1364
  const left = x.params.sheetsLeft + (y.id === ID.insertSheet ? 1 : -1);
1295
1365
  if (left < 1) return barrier2("concurrent sheet removes would leave the workbook without a sheet");
1296
1366
  const out2 = transformAxis(a, b, side);
1297
1367
  if (!out2) return barrier2("sheet order: no transform");
1298
- return ok(out2.map((o) => buildSheetOp({ id: x.id, params: { ...x.params, sheetsLeft: left } }, o)));
1368
+ return ok2(out2.map((o) => buildSheetOp({ id: x.id, params: { ...x.params, sheetsLeft: left } }, o)));
1299
1369
  }
1300
1370
  const out = transformAxis(a, b, side);
1301
1371
  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`] : []);
1372
+ return ok2(out.map((o) => buildSheetOp(x, o)), [], out.length === 0 && x.id === ID.sheetOrder ? [`${sheetIdOf(x)}: sheet order`] : []);
1303
1373
  }
1304
1374
  if (ys) {
1305
- if (y.id !== ID.removeSheet) return ok([x]);
1375
+ if (y.id !== ID.removeSheet) return ok2([x]);
1306
1376
  const gone2 = sheetOf(y);
1307
1377
  if (namesSheet(x, gone2)) return barrier2("a formula names a sheet removed at the same time");
1308
1378
  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]);
1379
+ return ok2([], [], isDerived(x) ? [] : [`${gone2}: the sheet was removed`]);
1380
+ return ok2([x]);
1311
1381
  }
1312
- if (x.id !== ID.removeSheet) return ok([x]);
1382
+ if (x.id !== ID.removeSheet) return ok2([x]);
1313
1383
  const gone = sheetOf(x);
1314
1384
  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`] : []);
1385
+ return ok2([x], sheetOf(y) === gone && !isDerived(y) && y.id !== ID.setName && y.id !== ID.removeName ? [`${gone}: the sheet was removed`] : []);
1316
1386
  }
1317
1387
  function annotateSheetOps(group, sheetOrder) {
1318
1388
  const order = [...sheetOrder];
@@ -1410,22 +1480,22 @@ function sortPair(x, y, sx, sy) {
1410
1480
  const a = sortOf(x).range;
1411
1481
  const b = sortOf(y).range;
1412
1482
  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]);
1483
+ return overlap ? barrier2("two sorts of overlapping ranges at once") : ok2([x]);
1414
1484
  }
1415
1485
  if (xs) {
1416
1486
  if (sy) {
1417
1487
  const t = sortThrough(sortOf(x), sy);
1418
- return t ? ok(buildSort(x, t)) : barrier2("a row or column move through a sorted range");
1488
+ return t ? ok2(buildSort(x, t)) : barrier2("a row or column move through a sorted range");
1419
1489
  }
1420
- return ok([x]);
1490
+ return ok2([x]);
1421
1491
  }
1422
1492
  const o = sortOf(y);
1423
1493
  const Q = sortQ(o);
1424
1494
  const moved = (r) => r >= o.range.startRow && r <= o.range.endRow && Q.has(r);
1425
1495
  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");
1496
+ if (sx.axis === "col") return sx.op.kind === "move" && !sortThrough(o, sx) ? barrier2("a column move through a sorted range") : ok2([x]);
1497
+ if (sx.op.kind === "insert") return ok2([x]);
1498
+ if (sx.op.kind === "move") return sortThrough(o, sx) ? ok2([x]) : barrier2("a row move through a sorted range");
1429
1499
  const rows = [];
1430
1500
  let partial = false;
1431
1501
  for (let i = sx.op.start; i < sx.op.start + sx.op.count; i++) {
@@ -1433,7 +1503,7 @@ function sortPair(x, y, sx, sy) {
1433
1503
  rows.push(Q.get(i) ?? i);
1434
1504
  }
1435
1505
  if (partial) return barrier2("a row remove against a concurrent sort of those rows");
1436
- return ok([x]);
1506
+ return ok2([x]);
1437
1507
  }
1438
1508
  if (x.id === ID.values) {
1439
1509
  const out = {};
@@ -1444,7 +1514,7 @@ function sortPair(x, y, sx, sy) {
1444
1514
  changed = true;
1445
1515
  } else putCell(out, r, c, v);
1446
1516
  });
1447
- return changed ? ok([cellsMutation(x, out)]) : ok([x]);
1517
+ return changed ? ok2([cellsMutation(x, out)]) : ok2([x]);
1448
1518
  }
1449
1519
  if (isNumfmt(x)) {
1450
1520
  const parts = nfParts(x).map((p) => {
@@ -1460,9 +1530,9 @@ function sortPair(x, y, sx, sy) {
1460
1530
  }
1461
1531
  return { ...p, ranges };
1462
1532
  });
1463
- return ok(nfBuild(x, parts));
1533
+ return ok2(nfBuild(x, parts));
1464
1534
  }
1465
- return ok([x]);
1535
+ return ok2([x]);
1466
1536
  }
1467
1537
  function familyOf(id) {
1468
1538
  const e = SHEET_MUTATIONS[id];
@@ -1478,13 +1548,15 @@ function transformMutation(x, y, side) {
1478
1548
  if (why) return barrier2(why);
1479
1549
  }
1480
1550
  }
1551
+ const ca = transformCommentAnchor(x, y, side);
1552
+ if (ca) return ca;
1481
1553
  const pb = PAIR_BARRIERS[pairKey(x.id, y.id)];
1482
1554
  if (pb) return barrier2(`${x.id} \xD7 ${y.id}: ${pb.reason}${pb.seed === null ? "" : ` (fuzz seed ${pb.seed})`}`);
1483
1555
  const sp = sheetPair(x, y, side);
1484
1556
  if (sp) return sp;
1485
1557
  const fp = formulaPair(x, y, side);
1486
1558
  if (fp) return fp;
1487
- if (sheetOf(x) !== sheetOf(y)) return ok([x]);
1559
+ if (sheetOf(x) !== sheetOf(y)) return ok2([x]);
1488
1560
  const fx = familyOf(x.id);
1489
1561
  const fy = familyOf(y.id);
1490
1562
  const sy = structuralOf(y);
@@ -1496,11 +1568,11 @@ function transformMutation(x, y, side) {
1496
1568
  const np = numfmtPair(x, y, side, sx, sy);
1497
1569
  if (np) return np;
1498
1570
  if (sx && sy) {
1499
- if (sx.axis !== sy.axis) return ok([x]);
1571
+ if (sx.axis !== sy.axis) return ok2([x]);
1500
1572
  const out = transformAxis(sx.op, sy.op, side);
1501
1573
  if (!out) return barrier2(`${x.id} over ${y.id}: moved blocks overlap in part, or the block would split`);
1502
1574
  const dropped = out.length === 0 && sx.op.kind === "move" ? [`${sheetOf(x)}: moved ${sx.axis}s`] : [];
1503
- return ok(out.map((o) => buildStructural(x, sx.axis, o)), [], dropped);
1575
+ return ok2(out.map((o) => buildStructural(x, sx.axis, o)), [], dropped);
1504
1576
  }
1505
1577
  if (sy) {
1506
1578
  if (fx === "merge") {
@@ -1511,23 +1583,23 @@ function transformMutation(x, y, side) {
1511
1583
  if (t) kept.push(t);
1512
1584
  else dropped.push(rangeRef(sheetOf(x), r));
1513
1585
  }
1514
- return ok(kept.length ? [withRanges(x, kept)] : [], [], x.id === ID.addMerge ? dropped : []);
1586
+ return ok2(kept.length ? [withRanges(x, kept)] : [], [], x.id === ID.addMerge ? dropped : []);
1515
1587
  }
1516
1588
  if (fx === "size") {
1517
1589
  const size = sizeOf(x);
1518
- if (size.axis !== sy.axis) return ok([x]);
1590
+ if (size.axis !== sy.axis) return ok2([x]);
1519
1591
  const next = /* @__PURE__ */ new Map();
1520
1592
  for (const [i, v] of size.values) {
1521
1593
  const m = mapIndex(sy.op, i);
1522
1594
  if (m !== null) next.set(m, v);
1523
1595
  }
1524
1596
  const built = buildSize(x, next);
1525
- return ok(built ? [built] : []);
1597
+ return ok2(built ? [built] : []);
1526
1598
  }
1527
- return ok([x]);
1599
+ return ok2([x]);
1528
1600
  }
1529
1601
  if (sx) {
1530
- if (fy !== "merge" || y.id !== ID.addMerge) return ok([x]);
1602
+ if (fy !== "merge" || y.id !== ID.addMerge) return ok2([x]);
1531
1603
  const remove = [];
1532
1604
  const add = [];
1533
1605
  for (const r of mergeRanges(y)) {
@@ -1537,11 +1609,11 @@ function transformMutation(x, y, side) {
1537
1609
  if (t.range) add.push(t.range);
1538
1610
  }
1539
1611
  const lostMerges = mergeRanges(y).filter((r) => mergeThrough(sx, r).range === null).map((r) => rangeRef(sheetOf(y), r));
1540
- if (!remove.length) return ok([x], lostMerges);
1612
+ if (!remove.length) return ok2([x], lostMerges);
1541
1613
  const sheet = sheetOf(x);
1542
1614
  const pre = sx.op.kind === "insert" ? [] : [mergeMutation(ID.removeMerge, sheet, remove)];
1543
1615
  const mid = sx.op.kind === "insert" ? [mergeMutation(ID.removeMerge, sheet, remove)] : [];
1544
- return ok([...pre, x, ...mid, ...add.length ? [mergeMutation(ID.addMerge, sheet, add)] : []], lostMerges);
1616
+ return ok2([...pre, x, ...mid, ...add.length ? [mergeMutation(ID.addMerge, sheet, add)] : []], lostMerges);
1545
1617
  }
1546
1618
  if (fx === "cell" && fy === "cell") return transformCellsOverCells(x, y, side);
1547
1619
  if (fx === "merge" && fy === "merge") {
@@ -1550,45 +1622,45 @@ function transformMutation(x, y, side) {
1550
1622
  const hit = (r) => yr.some((q) => intersects(r, q));
1551
1623
  const sheet = sheetOf(x);
1552
1624
  if (x.id === ID.addMerge && y.id === ID.addMerge) {
1553
- if (!xr.some(hit)) return ok([x]);
1625
+ if (!xr.some(hit)) return ok2([x]);
1554
1626
  if (side === "left") {
1555
1627
  const lost = yr.filter((q) => xr.some((r) => intersects(r, q))).map((q) => rangeRef(sheet, q));
1556
- return ok([mergeMutation(ID.removeMerge, sheet, xr.filter(hit)), x], lost);
1628
+ return ok2([mergeMutation(ID.removeMerge, sheet, xr.filter(hit)), x], lost);
1557
1629
  }
1558
1630
  const kept = xr.filter((r) => !hit(r));
1559
- return ok(kept.length ? [withRanges(x, kept)] : [], [], xr.filter(hit).map((r) => rangeRef(sheet, r)));
1631
+ return ok2(kept.length ? [withRanges(x, kept)] : [], [], xr.filter(hit).map((r) => rangeRef(sheet, r)));
1560
1632
  }
1561
1633
  if (x.id === ID.addMerge && y.id === ID.removeMerge) {
1562
- if (side === "left" || !xr.some(hit)) return ok([x], side === "left" && xr.some(hit) ? yr.filter((q) => xr.some((r) => intersects(r, q))).map((q) => rangeRef(sheet, q)) : []);
1634
+ if (side === "left" || !xr.some(hit)) return ok2([x], side === "left" && xr.some(hit) ? yr.filter((q) => xr.some((r) => intersects(r, q))).map((q) => rangeRef(sheet, q)) : []);
1563
1635
  const kept = xr.filter((r) => !hit(r));
1564
- return ok(kept.length ? [withRanges(x, kept)] : [], [], xr.filter(hit).map((r) => rangeRef(sheet, r)));
1636
+ return ok2(kept.length ? [withRanges(x, kept)] : [], [], xr.filter(hit).map((r) => rangeRef(sheet, r)));
1565
1637
  }
1566
1638
  if (x.id === ID.removeMerge && y.id === ID.addMerge) {
1567
1639
  const touched = yr.filter((q) => xr.some((r) => intersects(r, q)));
1568
- if (!touched.length) return ok([x]);
1569
- if (side === "left") return ok([x], touched.map((q) => rangeRef(sheet, q)));
1570
- return ok([x, mergeMutation(ID.addMerge, sheet, touched)], [], xr.filter(hit).map((r) => rangeRef(sheet, r)));
1640
+ if (!touched.length) return ok2([x]);
1641
+ if (side === "left") return ok2([x], touched.map((q) => rangeRef(sheet, q)));
1642
+ return ok2([x, mergeMutation(ID.addMerge, sheet, touched)], [], xr.filter(hit).map((r) => rangeRef(sheet, r)));
1571
1643
  }
1572
- return ok([x]);
1644
+ return ok2([x]);
1573
1645
  }
1574
1646
  if (fx === "size" && fy === "size") {
1575
1647
  const a = sizeOf(x);
1576
1648
  const b = sizeOf(y);
1577
- if (a.axis !== b.axis || a.attr !== b.attr) return ok([x]);
1649
+ if (a.axis !== b.axis || a.attr !== b.attr) return ok2([x]);
1578
1650
  if (side === "left") {
1579
1651
  const lost = [...a.values.keys()].filter((i) => b.values.has(i)).map((i) => `${sheetOf(x)}: ${a.axis} ${i + 1} ${a.attr}`);
1580
- return ok([x], lost);
1652
+ return ok2([x], lost);
1581
1653
  }
1582
1654
  const next = new Map([...a.values].filter(([i]) => !b.values.has(i)));
1583
1655
  const built = buildSize(x, next);
1584
1656
  const dropped = [...a.values.keys()].filter((i) => b.values.has(i)).map((i) => `${sheetOf(x)}: ${a.axis} ${i + 1} ${a.attr}`);
1585
- return ok(built ? [built] : [], [], dropped);
1657
+ return ok2(built ? [built] : [], [], dropped);
1586
1658
  }
1587
1659
  if (fx === "name" && fy === "name") {
1588
- if (side === "left") return ok([x], [`${sheetOf(x)}: name "${String(y.params.name)}"`]);
1589
- return ok([], [], [`${sheetOf(x)}: name "${String(x.params.name)}"`]);
1660
+ if (side === "left") return ok2([x], [`${sheetOf(x)}: name "${String(y.params.name)}"`]);
1661
+ return ok2([], [], [`${sheetOf(x)}: name "${String(x.params.name)}"`]);
1590
1662
  }
1591
- return ok([x]);
1663
+ return ok2([x]);
1592
1664
  }
1593
1665
  var blocked = (p) => !Array.isArray(p);
1594
1666
  function tSeq(xs, ys, side, acc) {