@ai-matrx/office 0.8.21 → 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,12 +1,13 @@
1
1
  # Changelog
2
2
 
3
- ## 0.8.21
3
+ ## 0.8.22
4
4
  ### C5c: Sheets co-editing — merge divergence fixed, number formats, counts and sheets travel
5
5
 
6
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
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
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
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.
10
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.
11
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.
12
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`.
@@ -86,6 +86,7 @@ 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
+ "sheet.mutation.reorder-range": covered("sort", "range", "order"),
89
90
  // ── never travels ────────────────────────────────────────────────────────────────────────────────
90
91
  "formula.mutation.set-formula-calculation-start": never(FORMULA_RESULT),
91
92
  "formula.mutation.set-trigger-formula-calculation-start": never(FORMULA_RESULT),
@@ -119,7 +120,6 @@ var SHEET_MUTATIONS = {
119
120
  "thread-comment.mutation.resolve-comment": never(UNIVER_COMMENTS),
120
121
  "thread-comment.mutation.delete-comment": never(UNIVER_COMMENTS),
121
122
  // ── barriers: X (no transform yet, design §10) ───────────────────────────────────────────────────
122
- "sheet.mutation.reorder-range": barrier("X", "sort: a permutation, not transformed yet"),
123
123
  "sheet.mutation.move-range": barrier("X", "cut-paste carries the sender's cell matrices"),
124
124
  "sheet.mutation.set-worksheet-hidden": barrier("X", "sheet-level setting: not in C5a"),
125
125
  "sheet.mutation.set-tab-color": barrier("X", "sheet-level setting: not in C5a"),
@@ -553,7 +553,8 @@ var ID = {
553
553
  colCount: "sheet.mutation.set-worksheet-column-count",
554
554
  insertSheet: "sheet.mutation.insert-sheet",
555
555
  removeSheet: "sheet.mutation.remove-sheet",
556
- sheetOrder: "sheet.mutation.set-worksheet-order"
556
+ sheetOrder: "sheet.mutation.set-worksheet-order",
557
+ sort: "sheet.mutation.reorder-range"
557
558
  };
558
559
  var PAIR_BARRIERS = {};
559
560
  var pairKey = (a, b) => a < b ? `${a}|${b}` : `${b}|${a}`;
@@ -1350,6 +1351,119 @@ function inlineSheetStyles(sheet, styleOf) {
1350
1351
  }
1351
1352
  return out;
1352
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
+ }
1353
1467
  function familyOf(id) {
1354
1468
  const e = SHEET_MUTATIONS[id];
1355
1469
  return e && e.kind === "covered" ? e.family : "";
@@ -1375,6 +1489,8 @@ function transformMutation(x, y, side) {
1375
1489
  const fy = familyOf(y.id);
1376
1490
  const sy = structuralOf(y);
1377
1491
  const sx = structuralOf(x);
1492
+ const sortp = sortPair(x, y, sx, sy);
1493
+ if (sortp) return sortp;
1378
1494
  const cp = countPair(x, y, sy);
1379
1495
  if (cp) return cp;
1380
1496
  const np = numfmtPair(x, y, side, sx, sy);