@ai-matrx/office 0.8.23 → 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,13 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.8.23
3
+ ## 0.8.24
4
4
  ### C9: comment anchors that follow concurrent edits
5
5
 
6
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
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
8
  - New export `./sheets/comment-anchors`. Consumer action: call `installSheetCommentAnchors(univer)` before creating a workbook unit that co-edits comments.
9
9
 
10
- ## 0.8.22
11
10
  ### C5c: Sheets co-editing — merge divergence fixed, number formats, counts and sheets travel
12
11
 
13
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).
@@ -21,19 +20,6 @@
21
20
  - `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.
22
21
  - Consumer action: none.
23
22
 
24
- ## 0.8.21
25
- ### C5c: Sheets co-editing — merge divergence fixed, number formats, counts and sheets travel
26
-
27
- - **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).
28
- - **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.
29
- - **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.
30
- - **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.
31
- - **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.
32
- - **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.
33
- - **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`.
34
- - `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.
35
- - Consumer action: none.
36
-
37
23
  ## 0.8.20
38
24
 
39
25
  SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
@@ -60,6 +60,7 @@ __export(collab_exports, {
60
60
  isNoopDocsOp: () => isNoopDocsOp,
61
61
  moveSelectionsThrough: () => moveSelectionsThrough,
62
62
  normalizeDocsOp: () => normalizeDocsOp,
63
+ offlineTextOfGroups: () => offlineTextOfGroups,
63
64
  openDocsOtRoom: () => openDocsOtRoom,
64
65
  openSheetsOtRoom: () => openSheetsOtRoom,
65
66
  rebaseSheetUndoStack: () => rebaseSheetUndoStack,
@@ -1370,7 +1371,9 @@ function openDocsOtRoom(o) {
1370
1371
  ...o.presence ? { presence: o.presence } : {},
1371
1372
  onNotice: (n) => notice(n),
1372
1373
  ...o.onIncident ? { onIncident: o.onIncident } : {},
1373
- ...o.onStatus ? { onStatus: o.onStatus } : {}
1374
+ ...o.onStatus ? { onStatus: o.onStatus } : {},
1375
+ ...o.offline ? { offline: o.offline } : {},
1376
+ ...o.onOffline ? { onOffline: o.onOffline } : {}
1374
1377
  });
1375
1378
  const isSaver = () => {
1376
1379
  if (!o.presence) return true;
@@ -1638,7 +1641,9 @@ function documentOtFactory(o) {
1638
1641
  replace: ({ snapshot, stateHash, clientId }) => (0, import_collab3.collabReplace)(client, { roomId: o.documentId, snapshot, stateHash, origin: "restore", clientId }),
1639
1642
  ...o.onNotice ? { onNotice: o.onNotice } : {},
1640
1643
  ...o.onIncident ? { onIncident: o.onIncident } : {},
1641
- ...o.onStatus ? { onStatus: o.onStatus } : {}
1644
+ ...o.onStatus ? { onStatus: o.onStatus } : {},
1645
+ offline: (0, import_collab3.browserOfflineOptions)(`${o.documentId}:${o.uid}`),
1646
+ ...o.onOffline ? { onOffline: o.onOffline } : {}
1642
1647
  });
1643
1648
  await room.session.ready;
1644
1649
  let lastKey = "";
@@ -3693,7 +3698,9 @@ function openSheetsOtRoom(o) {
3693
3698
  ...o.presence ? { presence: o.presence } : {},
3694
3699
  onNotice: (n) => notice(n),
3695
3700
  ...o.onIncident ? { onIncident: o.onIncident } : {},
3696
- ...o.onStatus ? { onStatus: o.onStatus } : {}
3701
+ ...o.onStatus ? { onStatus: o.onStatus } : {},
3702
+ ...o.offline ? { offline: o.offline } : {},
3703
+ ...o.onOffline ? { onOffline: o.onOffline } : {}
3697
3704
  });
3698
3705
  const isSaver = () => {
3699
3706
  if (!o.presence) return true;
@@ -3724,6 +3731,65 @@ function openSheetsOtRoom(o) {
3724
3731
  };
3725
3732
  }
3726
3733
 
3734
+ // src/univer/collab/sheets-ot/plugin-teardown.ts
3735
+ var perSheetList = (data) => Object.entries(data).flatMap(([sheet, list]) => Array.isArray(list) ? [[sheet, list.filter((x) => !!x && typeof x === "object")]] : []);
3736
+ var PLUGIN_TEARDOWNS = [
3737
+ {
3738
+ resource: "SHEET_CONDITIONAL_FORMATTING_PLUGIN",
3739
+ removals: (unitId, data) => perSheetList(data).flatMap(
3740
+ ([subUnitId, rules]) => rules.filter((r) => typeof r.cfId === "string").map((r) => ({ id: "sheet.mutation.delete-conditional-rule", params: { unitId, subUnitId, cfId: r.cfId } }))
3741
+ )
3742
+ },
3743
+ {
3744
+ resource: "SHEET_DATA_VALIDATION_PLUGIN",
3745
+ removals: (unitId, data) => perSheetList(data).flatMap(([subUnitId, rules]) => {
3746
+ const ids = rules.map((r) => r.uid).filter((u) => typeof u === "string");
3747
+ return ids.length ? [{ id: "data-validation.mutation.removeRule", params: { unitId, subUnitId, ruleId: ids, source: "command" } }] : [];
3748
+ })
3749
+ },
3750
+ {
3751
+ resource: "SHEET_NOTE_PLUGIN",
3752
+ removals: (unitId, data) => Object.entries(data).flatMap(
3753
+ ([sheetId, rows]) => Object.entries(rows ?? {}).flatMap(
3754
+ ([row, cols]) => Object.entries(cols ?? {}).map(([col, note]) => ({
3755
+ id: "sheet.mutation.remove-note",
3756
+ params: { unitId, sheetId, row: Number(row), col: Number(col), ...note && typeof note.id === "string" ? { noteId: note.id } : {}, silent: true }
3757
+ }))
3758
+ )
3759
+ )
3760
+ }
3761
+ ];
3762
+ function resourcesOf(saved) {
3763
+ const out = /* @__PURE__ */ new Map();
3764
+ const list = saved?.resources;
3765
+ for (const r of Array.isArray(list) ? list : []) {
3766
+ if (!r?.name) continue;
3767
+ try {
3768
+ const data = typeof r.data === "string" ? r.data ? JSON.parse(r.data) : null : r.data;
3769
+ if (data && typeof data === "object") out.set(r.name, data);
3770
+ } catch {
3771
+ }
3772
+ }
3773
+ return out;
3774
+ }
3775
+ function releaseUnitPluginState(cs, unitId, saved) {
3776
+ const resources = resourcesOf(saved);
3777
+ let ran = 0;
3778
+ for (const t of PLUGIN_TEARDOWNS) {
3779
+ const data = resources.get(t.resource);
3780
+ if (!data) continue;
3781
+ for (const m of t.removals(unitId, data)) {
3782
+ try {
3783
+ cs.syncExecuteCommand(m.id, m.params, { onlyLocal: true });
3784
+ ran++;
3785
+ } catch (err) {
3786
+ console.warn(`[sheets-ot] could not release ${t.resource} state before a reload`, err);
3787
+ }
3788
+ }
3789
+ }
3790
+ return ran;
3791
+ }
3792
+
3727
3793
  // src/univer/collab/sheets-ot/univer-port.ts
3728
3794
  var COMMAND = 0;
3729
3795
  var MUTATION = 2;
@@ -4043,6 +4109,8 @@ function univerSheetsOtPort(o) {
4043
4109
  group = null;
4044
4110
  applying = true;
4045
4111
  try {
4112
+ const u = unitId();
4113
+ if (u) releaseUnitPluginState(cs, u, o.saveUnit?.(u) ?? null);
4046
4114
  o.load(snapshot);
4047
4115
  } finally {
4048
4116
  applying = false;
@@ -4132,6 +4200,53 @@ function moveSelectionsThrough(selections, m, maxRows, maxCols) {
4132
4200
  return selections.map((s) => ({ ...s, range: box(s.range, s.range.rangeType), ...s.primary ? { primary: box(s.primary) } : {} }));
4133
4201
  }
4134
4202
 
4203
+ // src/univer/collab/offline-text.ts
4204
+ var colName3 = (c) => {
4205
+ let n = c;
4206
+ let s = "";
4207
+ do {
4208
+ s = String.fromCharCode(65 + n % 26) + s;
4209
+ n = Math.floor(n / 26) - 1;
4210
+ } while (n >= 0);
4211
+ return s;
4212
+ };
4213
+ function collectDocText(v, out) {
4214
+ if (Array.isArray(v)) {
4215
+ for (const x of v) collectDocText(x, out);
4216
+ return;
4217
+ }
4218
+ if (!v || typeof v !== "object") return;
4219
+ const o = v;
4220
+ if (typeof o.dataStream === "string" && o.dataStream.length) out.push(o.dataStream);
4221
+ for (const k of Object.keys(o)) if (k !== "dataStream") collectDocText(o[k], out);
4222
+ }
4223
+ function collectCells(cellValue, out) {
4224
+ if (!cellValue || typeof cellValue !== "object") return;
4225
+ for (const [r, cols] of Object.entries(cellValue)) {
4226
+ if (!cols || typeof cols !== "object") continue;
4227
+ for (const [c, cell] of Object.entries(cols)) {
4228
+ const x = cell;
4229
+ const val = x?.f ?? x?.v;
4230
+ if (val !== void 0 && val !== null && val !== "") out.push(`${colName3(Number(c))}${Number(r) + 1}: ${String(val)}`);
4231
+ }
4232
+ }
4233
+ }
4234
+ function offlineTextOfGroups(buffers) {
4235
+ const docs = [];
4236
+ const cells = [];
4237
+ for (const groups of buffers) {
4238
+ for (const g of groups) {
4239
+ for (const m of g.mutations) {
4240
+ const p = m.params;
4241
+ if (p && "cellValue" in p) collectCells(p.cellValue, cells);
4242
+ else collectDocText(p, docs);
4243
+ }
4244
+ }
4245
+ }
4246
+ const text = docs.join("").replace(/\r/g, "\n").replace(/\u0000/g, "");
4247
+ return [text, cells.join("\n")].filter((s) => s.length > 0).join("\n");
4248
+ }
4249
+
4135
4250
  // src/sheets/workbook-ot-room.ts
4136
4251
  var Y3 = __toESM(require("yjs"), 1);
4137
4252
  var import_awareness3 = require("y-protocols/awareness");
@@ -4234,6 +4349,7 @@ async function startWorkbookOtRoom(port, o) {
4234
4349
  await provider.connect();
4235
4350
  publishAwareness();
4236
4351
  const client = db4();
4352
+ const offline = (0, import_collab6.browserOfflineOptions)(`${o.workbookId}:${o.uid}`);
4237
4353
  const room = openSheetsOtRoom({
4238
4354
  port,
4239
4355
  clientId: o.clientId,
@@ -4254,7 +4370,9 @@ async function startWorkbookOtRoom(port, o) {
4254
4370
  replace: ({ snapshot, stateHash, clientId }) => (0, import_collab6.collabReplace)(client, { roomId: o.workbookId, snapshot, stateHash, origin: "restore", clientId }),
4255
4371
  ...o.onNotice ? { onNotice: o.onNotice } : {},
4256
4372
  ...o.onIncident ? { onIncident: o.onIncident } : {},
4257
- ...o.onStatus ? { onStatus: o.onStatus } : {}
4373
+ ...o.onStatus ? { onStatus: o.onStatus } : {},
4374
+ ...offline ? { offline } : {},
4375
+ ...o.onOffline ? { onOffline: o.onOffline } : {}
4258
4376
  });
4259
4377
  await room.session.ready;
4260
4378
  return {