@ai-matrx/office 0.5.3 → 0.6.0

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,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ Fix: two-seat co-editing corrupted the saved document on reload. Live, both screens showed the same clean text; after a reload the stored version showed it twice or interleaved, and the next reload scrambled it further. Cause: the Docs model never trimmed the room's shared mutation log, so a reloading seat booted from the newest saved snapshot (which already held every edit) and then replayed the whole log on top of it; it then saved that. Workbooks trimmed the log by POSITION after the save, which could drop an entry that arrived while the save was in flight, and a seat that read a snapshot older than the trim lost edits.
6
+
7
+ - Every room entry has an id (`eid`). A saved snapshot names the entries it holds (`stampCovered`; read back with `takeCovered`, both also on `./univer`, no Yjs), and a session given `base` never replays them.
8
+ - `WorkbookCollabSession.coveredNow()` (read with the body you save) and `checkpoint(snapshotId, covered)` (the elected saver, after the save lands) replace `compactLog(upTo)`: entries leave the log by id, never by position, and the room records its newest checkpoint.
9
+ - Join-time sync: a seat that booted from a snapshot older than the room's newest checkpoint reloads the newest one (`base.reload`) before applying the log.
10
+ - `DocumentModel` stamps and checkpoints its saves; `DocumentCollabFactory` receives the `base` as a 4th argument and `DocumentCollabHandle` gains `coveredNow` and `checkpoint`.
11
+
12
+ Known, not fixed here: two seats typing within the same relay window (a few seconds) still diverge — mutations carry offsets computed on the sender's body and are applied without transformation.
13
+
14
+ ### Consumer action
15
+
16
+ Document editor: pass the factory's `base` to `new WorkbookCollabSession({ base })` and return `coveredNow` / `checkpoint` from the handle; elect with `electHost({ tabTieBreak: true })` so one tab saves. Workbook editor: read `coveredNow()` right before `workbook.save()`, save `stampCovered(snapshot, covered)`, then `checkpoint(rowId, covered)` when host (replaces `logLength` + `compactLog`); load every snapshot through `takeCovered` and pass `base` (`snapshotId`, `covered`, `reload`).
17
+
3
18
  ## 0.5.3
4
19
 
5
20
  New `./docs/host` entry: the Docs host ports alone (`configureDocsHost`, `getDocsHost`, `DocsHostPorts`, `DocsSupabase`). It imports nothing heavy, so a start-up module can register the host without pulling the document engine (Univer, lodash-es, rxjs, mammoth) into first load. `./docs` still re-exports the same functions; both share one `globalThis` slot.
package/dist/docs.cjs CHANGED
@@ -28,6 +28,22 @@ function isSnapshotMutation(command) {
28
28
  return true;
29
29
  }
30
30
 
31
+ // src/univer/collab/covered.ts
32
+ var COLLAB_COVERED_KEY = "__matrxCollabCovered";
33
+ function stampCovered(snapshot, covered) {
34
+ return { ...snapshot, [COLLAB_COVERED_KEY]: [...covered] };
35
+ }
36
+ function takeCovered(snapshot) {
37
+ if (!snapshot || typeof snapshot !== "object" || !(COLLAB_COVERED_KEY in snapshot)) {
38
+ return { snapshot, covered: /* @__PURE__ */ new Set() };
39
+ }
40
+ const { [COLLAB_COVERED_KEY]: list, ...rest } = snapshot;
41
+ return {
42
+ snapshot: rest,
43
+ covered: new Set(Array.isArray(list) ? list.filter((e2) => typeof e2 === "string") : [])
44
+ };
45
+ }
46
+
31
47
  // src/docs/document-model.ts
32
48
  var DOCUMENT_SAVE_DELAY_MS = 2500;
33
49
  var viewIds = 0;
@@ -45,14 +61,17 @@ var DocumentModel = (_class = class {
45
61
  __init6() {this.knownSnapshots = /* @__PURE__ */ new Set()}
46
62
  __init7() {this.collabListeners = /* @__PURE__ */ new Set()}
47
63
  __init8() {this.collab = null}
48
- __init9() {this.collabStarting = null}
49
- __init10() {this.awareness = null}
50
- __init11() {this.awarenessListeners = /* @__PURE__ */ new Set()}
51
- __init12() {this.realtimeClose = null}
52
- __init13() {this.pageClose = null}
64
+ /** The saved snapshot the body started from, and the room entries it already holds. */
65
+ __init9() {this.baseId = null}
66
+ __init10() {this.baseCovered = /* @__PURE__ */ new Set()}
67
+ __init11() {this.collabStarting = null}
68
+ __init12() {this.awareness = null}
69
+ __init13() {this.awarenessListeners = /* @__PURE__ */ new Set()}
70
+ __init14() {this.realtimeClose = null}
71
+ __init15() {this.pageClose = null}
53
72
  /** The last view's editor, alive and off the page until a view takes it back. */
54
- __init14() {this.parked = null}
55
- constructor(handle, deps) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);_class.prototype.__init18.call(this);
73
+ __init16() {this.parked = null}
74
+ constructor(handle, deps) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);_class.prototype.__init18.call(this);_class.prototype.__init19.call(this);_class.prototype.__init20.call(this);
56
75
  this.documentId = handle.id;
57
76
  this.handle = handle;
58
77
  this.deps = deps;
@@ -83,11 +102,12 @@ var DocumentModel = (_class = class {
83
102
  if (!this.loading) {
84
103
  this.loading = this.deps.loadLatest(this.documentId).then(
85
104
  (row) => {
86
- if (row) {
105
+ const loaded = row ? this.takeBase(row) : null;
106
+ if (row && loaded) {
87
107
  this.knownSnapshots.add(row.id);
88
- this.savedFingerprint = contentFingerprint(row.snapshot);
108
+ this.savedFingerprint = contentFingerprint(loaded);
89
109
  }
90
- if (!this.stored) this.stored = row ? row.snapshot : createEmpty();
110
+ if (!this.stored) this.stored = _nullishCoalesce(loaded, () => ( createEmpty()));
91
111
  return _nullishCoalesce(this.latest(), () => ( this.stored));
92
112
  },
93
113
  (error) => {
@@ -209,13 +229,13 @@ var DocumentModel = (_class = class {
209
229
  markDirty() {
210
230
  this.handle.touch();
211
231
  }
212
- __init15() {this.saving = false}
232
+ __init17() {this.saving = false}
213
233
  /**
214
234
  * The content of the version the server holds (loaded or last written). A
215
235
  * save of the same content is no save: attaching, detaching, waking or a
216
236
  * flush of an unchanged document never writes a snapshot row.
217
237
  */
218
- __init16() {this.savedFingerprint = null}
238
+ __init18() {this.savedFingerprint = null}
219
239
  /**
220
240
  * Write the body as it is now. Returns false when this tab is a collab peer
221
241
  * that does not write (the elected host writes the canonical snapshot; a
@@ -228,11 +248,18 @@ var DocumentModel = (_class = class {
228
248
  const fingerprint = contentFingerprint(snapshot);
229
249
  if (reason !== "manual" && fingerprint === this.savedFingerprint) return false;
230
250
  const origin = reason === "manual" ? "manual" : "autosave";
251
+ const collab = this.collab;
252
+ const covered = collab ? collab.coveredNow() : null;
231
253
  this.saving = true;
232
254
  try {
233
- const { id } = await this.deps.save({ documentId: this.documentId, snapshot, origin });
255
+ const { id } = await this.deps.save({
256
+ documentId: this.documentId,
257
+ snapshot: covered ? stampCovered(snapshot, covered) : snapshot,
258
+ origin
259
+ });
234
260
  this.knownSnapshots.add(id);
235
261
  this.savedFingerprint = fingerprint;
262
+ if (collab && covered && collab.isHost()) collab.checkpoint(id, covered);
236
263
  } finally {
237
264
  this.saving = false;
238
265
  }
@@ -287,14 +314,22 @@ var DocumentModel = (_class = class {
287
314
  if (!row || this.knownSnapshots.has(row.id)) return;
288
315
  this.show(row);
289
316
  }
317
+ /** A loaded row becomes the base: its content (what views mount) and the room entries it holds. */
318
+ takeBase(row) {
319
+ const { snapshot, covered } = takeCovered(row.snapshot);
320
+ this.baseId = row.id;
321
+ this.baseCovered = covered;
322
+ return snapshot;
323
+ }
290
324
  show(row) {
325
+ const snapshot = this.takeBase(row);
291
326
  this.knownSnapshots.add(row.id);
292
- this.savedFingerprint = contentFingerprint(row.snapshot);
293
- this.stored = row.snapshot;
327
+ this.savedFingerprint = contentFingerprint(snapshot);
328
+ this.stored = snapshot;
294
329
  this.relaying = true;
295
330
  try {
296
- for (const view of this.views.values()) view.port.remount(row.snapshot);
297
- _optionalChain([this, 'access', _19 => _19.parked, 'optionalAccess', _20 => _20.remount, 'call', _21 => _21(row.snapshot)]);
331
+ for (const view of this.views.values()) view.port.remount(snapshot);
332
+ _optionalChain([this, 'access', _19 => _19.parked, 'optionalAccess', _20 => _20.remount, 'call', _21 => _21(snapshot)]);
298
333
  } finally {
299
334
  this.relaying = false;
300
335
  }
@@ -305,7 +340,7 @@ var DocumentModel = (_class = class {
305
340
  * binds to. Local edits from any view go out once; a peer's edit is applied
306
341
  * to every view.
307
342
  */
308
- __init17() {this.virtualCommandService = {
343
+ __init19() {this.virtualCommandService = {
309
344
  onMutationExecutedForCollab: (listener) => {
310
345
  this.collabListeners.add(listener);
311
346
  return { dispose: () => this.collabListeners.delete(listener) };
@@ -331,6 +366,14 @@ var DocumentModel = (_class = class {
331
366
  void this.onPeerReplaced().catch(
332
367
  (error) => console.warn(`[document-model] could not load ${this.documentId}'s replaced version`, error)
333
368
  );
369
+ },
370
+ {
371
+ snapshotId: () => this.baseId,
372
+ covered: () => this.baseCovered,
373
+ reload: async () => {
374
+ const row = await this.deps.loadLatest(this.documentId);
375
+ if (row && !this.closed) this.show(row);
376
+ }
334
377
  }
335
378
  ).then((handle) => {
336
379
  if (!handle) return;
@@ -375,7 +418,7 @@ var DocumentModel = (_class = class {
375
418
  this.collab = null;
376
419
  this.awareness = null;
377
420
  }
378
- __init18() {this.closed = false}
421
+ __init20() {this.closed = false}
379
422
  close() {
380
423
  this.closed = true;
381
424
  const parked = this.parked;
package/dist/docs.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs.cjs","../src/univer/isSnapshotMutation.ts","../src/docs/document-model.ts","../src/docs/document-write-validation.ts","../src/docs/fit-zoom-ratio.ts","../src/docs/documents-hub-display.ts","../src/docs/doc-symbols.ts","../src/docs/document-body-text.ts"],"names":["e"],"mappings":"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AAA;AACxB,EAAA;AACF;AAEuB;AACD,EAAA;AACM,EAAA;AACL,EAAA;AACG,EAAA;AAC1B;AAGgB;AACO,EAAA;AACT,IAAA;AACR,MAAA;AACE,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AACmB,EAAA;AACP,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AACqB,EAAA;AACvB;AAOgB;AAIP,EAAA;AACgB,IAAA;AACE,MAAA;AACvB,IAAA;AACkB,IAAA;AACH,MAAA;AACO,MAAA;AACF,MAAA;AACP,MAAA;AACC,QAAA;AACR,UAAA;AACF,QAAA;AACF,MAAA;AACe,MAAA;AACM,MAAA;AACD,QAAA;AACpB,MAAA;AACO,MAAA;AACI,QAAA;AACQ,QAAA;AACnB,MAAA;AACF,IAAA;AACF,EAAA;AACF;AP2Z2B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs.cjs","sourcesContent":[null,"/**\n * isSnapshotMutation — the autosave dirty-filter for the Univer editors.\n *\n * D97: `onCommandExecuted` fires for EVERY command — including viewport\n * scrolls and selection moves — so an unfiltered listener marked the doc\n * dirty on mere scrolling and wrote a new snapshot row (version history +\n * collab broadcast) 2.5s later. Only CommandType.MUTATION changes the data\n * that a snapshot persists (Univer's own docs: OPERATION = \"not saved to\n * snapshot, such as modifying scroll position\"). We therefore accept ONLY\n * mutations, and additionally deny-list any mutation whose id marks it as\n * scroll / selection / viewport bookkeeping, belt-and-braces.\n */\nimport type { ICommandInfo } from \"@univerjs/core\";\n\n/**\n * Univer's `CommandType.MUTATION`. A literal, so this filter (and the document\n * model that uses it outside the editor chunk) never pulls Univer's runtime —\n * which touches `window` at import — into a server-rendered chunk.\n */\nconst MUTATION = 2;\n\n/** Non-content command ids that must never mark the document dirty. */\nconst NON_CONTENT_ID_PATTERN =\n /(scroll|selection|set-selections|viewport|zoom|hover|cursor|focus|activate)/i;\n\nexport function isSnapshotMutation(command: ICommandInfo): boolean {\n if (command.type !== MUTATION) return false;\n if (NON_CONTENT_ID_PATTERN.test(command.id)) return false;\n return true;\n}\n\n/**\n * Mutations a SPREADSHEET fires on its own while it opens and recalculates —\n * they change nothing the person typed, so they must never mark the workbook\n * dirty. Observed on a plain open of a workbook with formulas (2026-10-10):\n * `formula.mutation.set-trigger-formula-calculation-start`,\n * `...set-formula-calculation-start`, `...set-formula-calculation-notification`\n * and a `doc.mutation.rich-text-editing` from the internal cell-editor document.\n * Each open then wrote a new snapshot (history bloat, and a false \"both changed\"\n * against Google). A real edit always arrives as a `sheet.mutation.*`.\n */\nconst WORKBOOK_SELF_MUTATION_PATTERN =\n /^(formula\\.mutation\\.[a-z-]*(calculation|trigger)[a-z-]*|doc\\.mutation\\.)/;\n\nexport function isWorkbookSnapshotMutation(command: ICommandInfo): boolean {\n if (!isSnapshotMutation(command)) return false;\n return !WORKBOOK_SELF_MUTATION_PATTERN.test(command.id);\n}\n","/**\n * THE DOCUMENT MODEL — the engine of one cloud document per tab.\n *\n * Before this, the document lived only inside each `DocumentEditor`'s Univer\n * instance. A remount (a board tile waking, a tile removed and undone, a route\n * change and back) rebuilt the editor from the SERVER copy, so whatever the\n * last save had not carried yet was gone; two mounts of one document were two\n * editors, each saving full snapshots of its own copy (last write wins), each\n * with its own realtime channel and its own collab room.\n *\n * The document is a record on THE one working-copy primitive\n * (`@ai-matrx/kit/working-copy`, kind `udt_document`,\n * `./documentModels.ts`): its save status, dirty and view count are Redux state\n * (`workingCopies[\"udt_document:<id>\"]`), its one coalesced save is the kind's.\n * What Redux cannot hold — Univer's body — is this engine, registered on the\n * record's session under the same key:\n *\n * - BODY. Every local mutation in one view is replayed into every other view\n * of the document (the same mutation stream collab sends between machines),\n * so two views show one document. A new view boots from the engine's latest\n * state — a live view's snapshot, or the snapshot the last view left behind\n * — never the stale server copy while the record's session is alive.\n * - SIDE EFFECTS, ONCE. The snapshot realtime channel, the collab room, and\n * the pagehide / visibility / beforeunload flush are opened once per\n * document and closed once.\n * - THE EDITOR ITSELF, KEPT. Univer keeps its undo / redo history inside the\n * editor instance, so rebuilding the editor on a remount threw the history\n * away. When the last view leaves, its editor (a Univer instance with its\n * own DOM host) is parked here — off the page, alive — and the next view of\n * the document RE-ATTACHES that same instance to its own container instead\n * of booting a new one (`parkEditor` / `takeParkedEditor`). Undo and redo\n * therefore work across hide / show, a remount and a removed tile undone,\n * for as long as the record's session is warm; `close()` disposes it.\n */\n\nimport type { ICommandInfo, IDocumentData } from \"@univerjs/core\";\nimport type { CommitReason, WorkingCopyHandle } from \"@ai-matrx/kit/working-copy\";\nimport type { AwarenessState } from \"@ai-matrx/data/realtime/collab\";\nimport { isSnapshotMutation } from \"../univer\";\nimport type {\n CollabMutationInfo,\n CommandServiceLike,\n} from \"../univer/collab\";\n\n/** Debounce from the last edit to the save. */\nexport const DOCUMENT_SAVE_DELAY_MS = 2500;\n\nexport type DocumentSnapshotData = Partial<IDocumentData>;\n\n/** What a view lends the model while it is mounted. */\nexport interface DocumentViewPort {\n /** Univer's command service for this view's instance. */\n commandService: CommandServiceLike;\n /** The live document of this view (`doc.save()`), or null if it has none. */\n snapshot: () => DocumentSnapshotData | null;\n /** Replace the whole document in this view (a collaborator's snapshot). */\n remount: (snapshot: DocumentSnapshotData) => void;\n /** Whether this view may change the document (read at edit time). */\n editable: () => boolean;\n /**\n * The Univer unit id of this view's mounted document. Every seat mounts its own unit, so a\n * mutation carries the SENDER's id and must be re-addressed to this view's unit before it is\n * applied. Falls back to the id of `snapshot()` when a view does not state it.\n */\n unitId?: () => string | null;\n}\n\n/** Everything the engine reaches outside itself — injected so it is testable. */\nexport interface DocumentModelDeps {\n /** The newest stored snapshot (its row id + body), or null for a document never saved. */\n loadLatest: (documentId: string) => Promise<{ id: string; snapshot: DocumentSnapshotData } | null>;\n save: (input: {\n documentId: string;\n snapshot: DocumentSnapshotData;\n origin: \"autosave\" | \"manual\";\n }) => Promise<{ id: string; createdBy: string | null }>;\n onSaved?: (documentId: string, origin: \"autosave\" | \"manual\") => void;\n /** Window-level listeners (pagehide, visibilitychange, beforeunload). */\n listenToPage?: (handlers: { flush: () => void; hasUnsaved: () => boolean }) => () => void;\n /** A mutation could not be applied to a view: the app says so (a notice); the model re-syncs from the saved version. */\n onApplyFailed?: (documentId: string, mutationId: string, error: unknown) => void;\n}\n\ninterface AttachedView {\n id: number;\n port: DocumentViewPort;\n dispose: () => void;\n}\n\n/** A collab room (WorkbookCollabSession) bound to the model's command stream. */\nexport interface DocumentCollabHandle {\n stop: () => void;\n isHost: () => boolean;\n /** This tab replaced the whole document and saved it as `snapshotId`: peers reload it. */\n announceReplaced: (snapshotId: string) => void;\n}\n\n/** Who else is in the document's room (shared by every view of it). */\nexport interface DocumentAwareness {\n states: Map<number, AwarenessState>;\n selfUid: string;\n}\n\n/**\n * An editor instance kept alive between views of the document: its DOM host\n * (re-parented into the next view's container) and the instance behind it,\n * whose undo history survives because the instance does. Opaque to the model\n * apart from what it calls.\n */\nexport interface ParkedDocumentEditor<T = unknown> {\n /** The element the editor renders into; the next view appends it to its container. */\n element: HTMLElement;\n instance: T;\n /** Replace the whole document (a collaborator's snapshot arrived while parked). */\n remount: (snapshot: DocumentSnapshotData) => void;\n /** Throw the instance away (the record's session closed). */\n dispose: () => void;\n}\n\nexport type DocumentCollabFactory = (\n commandService: CommandServiceLike,\n onAwareness: (awareness: DocumentAwareness) => void,\n /** A peer replaced the whole document (history restore, Google update). */\n onReplaced: () => void,\n) => Promise<DocumentCollabHandle | null>;\n\nlet viewIds = 0;\n\nexport class DocumentModel {\n readonly documentId: string;\n private readonly deps: DocumentModelDeps;\n private readonly handle: WorkingCopyHandle;\n private readonly views = new Map<number, AttachedView>();\n private lastEditedView: number | null = null;\n /** The document as the last view left it, or as last loaded / saved. */\n private stored: DocumentSnapshotData | null = null;\n private loading: Promise<DocumentSnapshotData | null> | null = null;\n private relaying = false;\n /** Snapshot rows this tab already holds (loaded or written) — never re-applied. */\n private readonly knownSnapshots = new Set<string>();\n private readonly collabListeners = new Set<(info: CollabMutationInfo) => void>();\n private collab: DocumentCollabHandle | null = null;\n private collabStarting: Promise<void> | null = null;\n private awareness: DocumentAwareness | null = null;\n private readonly awarenessListeners = new Set<() => void>();\n private realtimeClose: (() => void) | null = null;\n private pageClose: (() => void) | null = null;\n /** The last view's editor, alive and off the page until a view takes it back. */\n private parked: ParkedDocumentEditor | null = null;\n\n constructor(handle: WorkingCopyHandle, deps: DocumentModelDeps) {\n this.documentId = handle.id;\n this.handle = handle;\n this.deps = deps;\n }\n\n // ── body ────────────────────────────────────────────────────────────────\n\n /** The newest state of the body this tab holds, without a network read. */\n latest(): DocumentSnapshotData | null {\n const preferred = this.lastEditedView !== null ? this.views.get(this.lastEditedView) : undefined;\n for (const view of preferred ? [preferred, ...this.views.values()] : this.views.values()) {\n try {\n const snapshot = view.port.snapshot();\n if (snapshot) return snapshot;\n } catch (error) {\n console.warn(`[document-model] a view of ${this.documentId} could not report its document`, error);\n }\n }\n return this.stored;\n }\n\n /**\n * What a NEW view should boot from: the model's latest state, or (only when\n * this tab holds none) the newest server snapshot. Null = an empty document.\n * Re-read `latest()` synchronously right before mounting — another view may\n * have typed while the server read was in flight.\n */\n async openingSnapshot(createEmpty: () => DocumentSnapshotData): Promise<DocumentSnapshotData> {\n const held = this.latest();\n if (held) return held;\n if (!this.loading) {\n this.loading = this.deps.loadLatest(this.documentId).then(\n (row) => {\n if (row) {\n this.knownSnapshots.add(row.id);\n this.savedFingerprint = contentFingerprint(row.snapshot);\n }\n // A document never saved opens as ONE empty document for every view\n // (one unit id), so their edits replay into each other.\n if (!this.stored) this.stored = row ? row.snapshot : createEmpty();\n return this.latest() ?? this.stored;\n },\n (error: unknown) => {\n this.loading = null;\n throw error;\n },\n );\n }\n return (await this.loading) ?? createEmpty();\n }\n\n /** A view's Univer instance is ready: it shares the document from now on. */\n attachView(port: DocumentViewPort): () => void {\n const id = ++viewIds;\n const subscription = port.commandService.onMutationExecutedForCollab((info, options) =>\n this.onViewMutation(id, info, options),\n );\n this.views.set(id, { id, port, dispose: () => subscription.dispose() });\n // Nothing unsaved: what this view mounted IS the saved version (Univer and\n // the snapshot repair may restate it — page geometry, defaults — without\n // anyone changing a word). That is the baseline a save compares against.\n if (!this.handle.hasPending()) {\n try {\n const mounted = port.snapshot();\n if (mounted) this.savedFingerprint = contentFingerprint(mounted);\n } catch (error) {\n console.warn(`[document-model] could not read ${this.documentId}'s mounted document`, error);\n }\n }\n let detached = false;\n return () => {\n if (detached) return;\n detached = true;\n const view = this.views.get(id);\n if (!view) return;\n // Whatever this view shows is the newest body when it is the last one:\n // keep it, so a remount (or the flush on the way out) reads it.\n if (this.views.size === 1) {\n try {\n const snapshot = port.snapshot();\n if (snapshot) this.stored = snapshot;\n } catch (error) {\n console.warn(`[document-model] could not keep ${this.documentId}'s last view's document`, error);\n }\n }\n view.dispose();\n this.views.delete(id);\n if (this.lastEditedView === id) this.lastEditedView = null;\n };\n }\n\n /**\n * The last view of the document is leaving: keep its editor (and Univer's\n * undo history inside it) for the next view. Call AFTER the view's\n * `attachView` detach. Refused — the caller disposes its editor — while\n * another view is still attached (it carries the document) or one is\n * already kept.\n */\n parkEditor(editor: ParkedDocumentEditor): boolean {\n if (this.closed || this.views.size > 0 || this.parked) return false;\n this.parked = editor;\n return true;\n }\n\n /** The kept editor, handed to the view that mounts next (null when none is kept). */\n takeParkedEditor(): ParkedDocumentEditor | null {\n const editor = this.parked;\n this.parked = null;\n return editor;\n }\n\n private onViewMutation(\n viewId: number,\n info: Readonly<CollabMutationInfo>,\n options?: { fromCollab?: boolean; fromChangeset?: boolean; onlyLocal?: boolean },\n ): void {\n // Our own replay into this view, a collab peer's edit, or a load: not a\n // new local edit (each is accounted where it came from).\n if (this.relaying || options?.fromCollab || options?.fromChangeset) return;\n const view = this.views.get(viewId);\n if (!view || !view.port.editable()) return;\n this.lastEditedView = viewId;\n this.relay(info, viewId);\n for (const listener of this.collabListeners) listener(info as CollabMutationInfo);\n if (isContentMutation(info)) this.markDirty();\n }\n\n /** The unit id a view's document has (what a replayed mutation must be addressed to). */\n private unitIdOf(view: AttachedView): string | null {\n try {\n return view.port.unitId?.() ?? view.port.snapshot()?.id ?? null;\n } catch {\n return null;\n }\n }\n\n /** Replay one mutation into every view except `exceptViewId`, addressed to each view's own unit. */\n private relay(info: Readonly<CollabMutationInfo>, exceptViewId: number | null): void {\n if (this.views.size === 0 || (this.views.size === 1 && exceptViewId !== null)) return;\n this.relaying = true;\n let failure: unknown = null;\n try {\n for (const view of this.views.values()) {\n if (view.id === exceptViewId) continue;\n try {\n const params = info.params as unknown;\n const unitId = this.unitIdOf(view);\n const addressed =\n unitId && params && typeof params === \"object\" && !Array.isArray(params) && typeof (params as { unitId?: unknown }).unitId === \"string\" && (params as { unitId: string }).unitId !== unitId\n ? { ...(params as Record<string, unknown>), unitId }\n : info.params;\n view.port.commandService.syncExecuteCommand(info.id, addressed as never, { onlyLocal: true, fromCollab: true });\n } catch (error) {\n failure ??= error;\n console.error(\n `[document-model] a second view of ${this.documentId} could not apply \"${info.id}\" — it may show an older document until it reopens`,\n error,\n );\n }\n }\n } finally {\n this.relaying = false;\n }\n if (failure !== null) this.applyFailed(info.id, failure);\n }\n\n /** A view could not follow an edit: say so, then show the saved version (unless this tab has unsaved edits of its own). */\n private applyFailed(mutationId: string, error: unknown): void {\n try {\n this.deps.onApplyFailed?.(this.documentId, mutationId, error);\n } catch (notifyError) {\n console.warn(`[document-model] the notice for ${this.documentId}'s failed apply also failed`, notifyError);\n }\n if (this.hasUnsaved()) return;\n void this.deps\n .loadLatest(this.documentId)\n .then((row) => {\n if (row && !this.closed) this.show(row);\n })\n .catch((e) => console.warn(`[document-model] could not re-sync ${this.documentId} after a failed apply`, e));\n }\n\n // ── save (run by the record's one coalesced save — documentModels.ts) ─\n\n private markDirty(): void {\n this.handle.touch();\n }\n\n private saving = false;\n /**\n * The content of the version the server holds (loaded or last written). A\n * save of the same content is no save: attaching, detaching, waking or a\n * flush of an unchanged document never writes a snapshot row.\n */\n private savedFingerprint: string | null = null;\n\n /**\n * Write the body as it is now. Returns false when this tab is a collab peer\n * that does not write (the elected host writes the canonical snapshot; a\n * peer's edits reach it through the room). An explicit Save always writes.\n */\n async write(reason: CommitReason): Promise<boolean> {\n const snapshot = this.latest();\n if (!snapshot) throw new Error(\"There is no open document to save.\");\n if (this.collab && !this.collab.isHost() && reason !== \"manual\") return false;\n const fingerprint = contentFingerprint(snapshot);\n if (reason !== \"manual\" && fingerprint === this.savedFingerprint) return false;\n const origin = reason === \"manual\" ? \"manual\" : \"autosave\";\n this.saving = true;\n try {\n const { id } = await this.deps.save({ documentId: this.documentId, snapshot, origin });\n this.knownSnapshots.add(id);\n this.savedFingerprint = fingerprint;\n } finally {\n this.saving = false;\n }\n if (this.views.size === 0) this.stored = snapshot;\n this.deps.onSaved?.(this.documentId, origin);\n return true;\n }\n\n private hasUnsaved(): boolean {\n return this.handle.hasPending();\n }\n\n hasViews(): boolean {\n return this.views.size > 0;\n }\n\n // ── realtime (snapshot inserts) ─────────────────────────────────────────\n\n /** Opened once per document by the first view that asks; idempotent. */\n connectRealtime(open: (onSnapshot: (snapshotId: string) => void) => () => void): void {\n if (this.realtimeClose) return;\n this.realtimeClose = open((snapshotId) => void this.onRemoteSnapshot(snapshotId));\n }\n\n /**\n * A snapshot was committed (here or elsewhere): show a foreign one in every\n * view. Over unsaved edits it is a CONFLICT — the working copy holds it and\n * no save runs (one would overwrite the collaborator's snapshot) until the\n * person chooses; `overUnsaved` is \"Take theirs\" (`./documentModels.ts`).\n */\n async onRemoteSnapshot(snapshotId: string, { overUnsaved = false }: { overUnsaved?: boolean } = {}): Promise<void> {\n if (this.collab) return; // the room is the live channel; snapshots are checkpoints\n if (this.knownSnapshots.has(snapshotId)) return; // ours, or already shown\n if (this.saving) return; // our own save's echo, ahead of its response\n if (this.hasUnsaved() && !overUnsaved) {\n this.handle.conflict({ ref: snapshotId });\n return;\n }\n const row = await this.deps.loadLatest(this.documentId);\n if (!row || (this.hasUnsaved() && !overUnsaved) || this.knownSnapshots.has(row.id)) return;\n this.show(row);\n }\n\n /**\n * THE content swap (see @ai-matrx/office/univer openEditorContent): a SAVED snapshot\n * replaces the document in every view and the kept editor. It is the saved\n * version from now on, so the pending save compares equal and writes\n * nothing (it would put the old content back over the new). In a collab\n * room the peers reload it and the shared log is emptied.\n */\n replaceContent(row: { id: string; snapshot: DocumentSnapshotData }): void {\n this.show(row);\n this.collab?.announceReplaced(row.id);\n }\n\n /** A peer replaced the document: show the newest save (once). */\n private async onPeerReplaced(): Promise<void> {\n const row = await this.deps.loadLatest(this.documentId);\n if (!row || this.knownSnapshots.has(row.id)) return;\n this.show(row);\n }\n\n private show(row: { id: string; snapshot: DocumentSnapshotData }): void {\n this.knownSnapshots.add(row.id);\n this.savedFingerprint = contentFingerprint(row.snapshot);\n this.stored = row.snapshot;\n this.relaying = true;\n try {\n for (const view of this.views.values()) view.port.remount(row.snapshot);\n this.parked?.remount(row.snapshot);\n } finally {\n this.relaying = false;\n }\n }\n\n // ── collab (one room per document) ──────────────────────────────────────\n\n /**\n * The model's command stream as ONE command service — what the collab room\n * binds to. Local edits from any view go out once; a peer's edit is applied\n * to every view.\n */\n private readonly virtualCommandService: CommandServiceLike = {\n onMutationExecutedForCollab: (listener) => {\n this.collabListeners.add(listener);\n return { dispose: () => this.collabListeners.delete(listener) };\n },\n syncExecuteCommand: (id, params) => {\n const info = { id, type: 2, params } as CollabMutationInfo;\n this.relay(info, null);\n // The host writes what peers typed.\n if (isContentMutation(info)) this.markDirty();\n return true;\n },\n };\n\n /** Join the document's collab room once (the first view that asks). */\n startCollab(factory: DocumentCollabFactory): Promise<void> {\n if (this.collab) return Promise.resolve();\n if (!this.collabStarting) {\n this.collabStarting = factory(\n this.virtualCommandService,\n (awareness) => {\n this.awareness = awareness;\n for (const listener of Array.from(this.awarenessListeners)) listener();\n },\n () => {\n void this.onPeerReplaced().catch((error: unknown) =>\n console.warn(`[document-model] could not load ${this.documentId}'s replaced version`, error),\n );\n },\n )\n .then((handle) => {\n if (!handle) return;\n if (this.views.size === 0) {\n handle.stop();\n return;\n }\n this.collab = handle;\n })\n .finally(() => {\n this.collabStarting = null;\n });\n }\n return this.collabStarting;\n }\n\n isCollabHost(): boolean {\n return this.collab ? this.collab.isHost() : true;\n }\n\n getAwareness(): DocumentAwareness | null {\n return this.awareness;\n }\n\n subscribeAwareness(listener: () => void): () => void {\n this.awarenessListeners.add(listener);\n return () => this.awarenessListeners.delete(listener);\n }\n\n // ── lifecycle (driven by the record's session) ──────────────────────\n\n /** A view arrived on a document with none. */\n wake(): void {\n if (!this.pageClose && this.deps.listenToPage) {\n this.pageClose = this.deps.listenToPage({\n flush: () => void this.handle.flush(),\n hasUnsaved: () => this.hasUnsaved(),\n });\n }\n }\n\n /**\n * The last view left: leave the collab room (presence must not outlive the\n * person). The snapshot channel stays open while the session is kept alive,\n * so a collaborator's save still reaches the body a returning view boots from.\n */\n rest(): void {\n this.collab?.stop();\n this.collab = null;\n this.awareness = null;\n }\n\n private closed = false;\n\n close(): void {\n this.closed = true;\n const parked = this.parked;\n this.parked = null;\n if (parked) {\n try {\n parked.dispose();\n } catch (error) {\n console.warn(`[document-model] could not dispose ${this.documentId}'s kept editor`, error);\n }\n }\n this.realtimeClose?.();\n this.realtimeClose = null;\n this.pageClose?.();\n this.pageClose = null;\n }\n}\n\n/**\n * What a snapshot STORES, as a comparable string: everything but the unit id\n * and Univer's revision counter (both move without the content moving).\n */\nfunction contentFingerprint(snapshot: DocumentSnapshotData): string {\n const { id: _id, rev: _rev, ...content } = snapshot as DocumentSnapshotData & { rev?: unknown };\n return JSON.stringify(content);\n}\n\n/** Mutations that change what a snapshot stores (never scroll / selection) — the editors' one dirty filter. */\nfunction isContentMutation(info: Readonly<CollabMutationInfo>): boolean {\n return isSnapshotMutation({ id: info.id, type: info.type, params: info.params } as ICommandInfo);\n}\n","/**\n * Pure validation for the `matrx-user/documents` write targets.\n *\n * Deliberately OUTSIDE the React updater. `applySurfaceWrite`\n * (`features/surfaces/runtime/surface-writeback.ts`) turns a THROW from a\n * handler into the error envelope the agent reads back. Validating inside a\n * `setState` updater would instead throw during React's render/commit — after\n * the seam has already reported success — so the agent would be told its bad\n * value landed. Validate here, synchronously, before touching any state.\n *\n * `DOCUMENT_NAME_MAX_LENGTH` is the single source of truth for the limit: it is\n * enforced here, interpolated into the manifest target description, AND set as\n * the `maxLength` of the header rename field on `/documents/[id]` — so the\n * contract the agent reads, the rule the handler enforces, and the limit the\n * human's own control applies cannot drift apart. The last of those matters\n * for a non-obvious reason: the blur commit swallows service failures, so\n * before the field was bounded a pasted 300-character title came back as a\n * `varchar(255)` 400 and reverted with nothing on screen to explain it.\n */\n\n/**\n * `workbench.udt_documents.document_name` is `varchar(255) NOT NULL` — a\n * longer value is rejected by Postgres, not merely by taste. Verified against\n * `information_schema.columns` on 2026-08-11.\n */\nexport const DOCUMENT_NAME_MAX_LENGTH = 255;\n\n/**\n * `workbench.udt_documents.description` is unbounded `text`, so this is a\n * product bound rather than a database one: it matches the 2000 the sibling\n * `workbook_description` target enforces, keeping the two mirrored services\n * mirrored in behaviour too. The library renders this as a two-line blurb —\n * anything near the cap is already past what a reader sees.\n */\nexport const DOCUMENT_DESCRIPTION_MAX_LENGTH = 2000;\n\n/** How a rejected value is described back to the agent, without dumping it. */\nfunction describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n if (typeof value === \"object\") return \"an object\";\n return `a ${typeof value}`;\n}\n\n/**\n * Validate an agent-supplied document title and return the exact string to\n * persist. Throws (never coerces) so the agent hears about its own mistake.\n *\n * The `typeof` guard is load-bearing: the inline-tool layer PARSES a\n * JSON-looking argument before the handler ever sees it, so a caller that\n * sends `{\"document_name\":\"...\"}` or `\"\\\"Quoted\\\"\"` arrives here as an object,\n * not text. The message names the fix explicitly, because an agent that only\n * hears \"invalid\" tends to retry by double-encoding — which lands escaped\n * quotes and `\\n` in the user's title box.\n */\nexport function validateDocumentName(value: unknown): string {\n if (typeof value !== \"string\") {\n throw new Error(\n `document_name must be plain text, not JSON and not JSON-encoded — received ${describeValue(\n value,\n )}. Send only the title itself (for example: Q3 Revenue Review) with no surrounding braces, brackets, quotes or escape sequences.`,\n );\n }\n\n const trimmed = value.trim();\n\n if (trimmed.length === 0) {\n throw new Error(\n \"document_name cannot be empty or whitespace — a document must keep a title. Send the title text you want, or leave the current one alone.\",\n );\n }\n\n if (/[\\r\\n]/.test(trimmed)) {\n throw new Error(\n \"document_name must be a SINGLE line — it renders in the page header, and line breaks are not a title. Send one line of plain text (this is a title, not the document body).\",\n );\n }\n\n if (trimmed.length > DOCUMENT_NAME_MAX_LENGTH) {\n throw new Error(\n `document_name must be ${DOCUMENT_NAME_MAX_LENGTH} characters or fewer (received ${trimmed.length}). Shorten it to a title; the document body is not stored in this field.`,\n );\n }\n\n return trimmed;\n}\n\n/**\n * Validate an agent-supplied document description and return the value to\n * persist — `null` for a deliberate clear, so the caller never has to decide\n * whether \"\" means \"empty string\" or \"no description\".\n *\n * Unlike the title this MAY span lines (it is a blurb, not a header), but it\n * carries the same not-JSON guard for the same reason: the inline-tool layer\n * parses a JSON-looking argument before the handler sees it.\n */\nexport function validateDocumentDescription(value: unknown): string | null {\n if (typeof value !== \"string\") {\n throw new Error(\n `document_description must be plain text, not JSON and not JSON-encoded — received ${describeValue(\n value,\n )}. Send only the description prose itself, with no surrounding braces, brackets, quotes or escape sequences.`,\n );\n }\n\n const trimmed = value.trim();\n\n // An empty string is the documented way to CLEAR the description, so it is\n // valid input rather than a bad shape — normalize it to null for the column.\n if (trimmed.length === 0) return null;\n\n if (trimmed.length > DOCUMENT_DESCRIPTION_MAX_LENGTH) {\n throw new Error(\n `document_description must be ${DOCUMENT_DESCRIPTION_MAX_LENGTH} characters or fewer (received ${trimmed.length}). This is the short blurb shown in the documents library, not the document body.`,\n );\n }\n\n return trimmed;\n}\n","/**\n * The zoom that fits a document page inside a narrow editor (a phone), so the\n * page reflows into view instead of scrolling sideways. Never above 100%, and\n * never so small the text is unreadable. Pure — guarded by fit-zoom-ratio.test.ts.\n */\nexport const MIN_FIT_ZOOM = 0.45;\n\nexport function fitZoomRatio(containerWidth: number, pageWidth: number, gutter = 8): number {\n if (!(containerWidth > 0) || !(pageWidth > 0)) return 1;\n const ratio = (containerWidth - gutter * 2) / pageWidth;\n return Math.min(1, Math.max(MIN_FIT_ZOOM, Math.round(ratio * 100) / 100));\n}\n","import type { DocumentSource } from \"../core\";\n\nconst SOURCE_LABELS: Record<DocumentSource, string> = {\n created: \"Created\",\n imported_docx: \"Imported DOCX\",\n imported_md: \"Imported Markdown\",\n imported_txt: \"Imported Text\",\n linked_gdoc: \"Imported from Google Docs\",\n};\n\nexport function documentSourceLabel(source: DocumentSource): string {\n return SOURCE_LABELS[source] ?? source;\n}\n","/** Insert, Emoji and Special characters: searchable sets, a name per entry. */\n\nexport interface Glyph {\n char: string;\n name: string;\n}\n\nconst e = (rows: string): Glyph[] =>\n rows.split(\"|\").map((r) => r.trim()).filter(Boolean).map((r) => {\n const [char, ...name] = r.split(\" \");\n return { char: char!, name: name.join(\" \") };\n });\n\nexport const EMOJI_GROUPS: Array<{ id: string; label: string; items: Glyph[] }> = [\n { id: \"faces\", label: \"Faces\", items: e(\"😀 grinning|😃 smile|😄 happy|😁 beaming|😆 laughing|😅 sweat smile|😂 tears of joy|🤣 rolling|🙂 slight smile|🙃 upside down|😉 wink|😊 blush|😇 angel|🥰 love|😍 heart eyes|🤩 star struck|😘 kiss|😋 yum|😎 cool|🤓 nerd|🤔 thinking|🤨 skeptical|😐 neutral|🙄 eye roll|😬 grimace|😴 sleeping|😢 cry|😭 sobbing|😤 huff|😡 angry|🤯 mind blown|😱 scream|🥳 party|🤗 hug|🫡 salute|😮 surprised|😳 flushed|🥺 pleading|😷 mask|🤒 sick\") },\n { id: \"people\", label: \"Gestures\", items: e(\"👍 thumbs up|👎 thumbs down|👏 clap|🙌 raised hands|🙏 pray thanks|🤝 handshake|💪 strong flex|👌 ok|✌ victory peace|🤞 fingers crossed|👋 wave|✋ raised hand|👆 point up|👉 point right|👈 point left|👇 point down|✍ writing|🤷 shrug|🙋 raise hand|👀 eyes|🧠 brain|❤ heart|💔 broken heart|💯 hundred\") },\n { id: \"objects\", label: \"Objects\", items: e(\"💡 idea light bulb|📌 pin|📎 paperclip|📝 memo note|📅 calendar|📈 chart up|📉 chart down|📊 bar chart|📁 folder|📄 page|📧 email|📞 phone|💻 laptop|📱 mobile|🔒 lock|🔑 key|🔍 search|🔔 bell|⏰ alarm clock|⌛ hourglass|🎯 target|🏆 trophy|🎁 gift|📦 package|🛠 tools|⚙ gear|💰 money bag|💵 dollar|🧾 receipt|📚 books\") },\n { id: \"symbols\", label: \"Marks\", items: e(\"✅ check mark|❌ cross|⭐ star|🌟 glowing star|✨ sparkles|🔥 fire|⚡ lightning|🚀 rocket|🎉 party popper|🎊 confetti|⚠ warning|❓ question|❗ exclamation|➕ plus|➖ minus|🔴 red circle|🟠 orange circle|🟡 yellow circle|🟢 green circle|🔵 blue circle|🟣 purple circle|⬆ up|⬇ down|➡ right|⬅ left|🔁 repeat|✔ heavy check\") },\n { id: \"nature\", label: \"Nature and food\", items: e(\"🌞 sun|🌙 moon|☁ cloud|🌧 rain|❄ snowflake|🌈 rainbow|🌱 seedling|🌳 tree|🌸 blossom|🌍 earth|🐶 dog|🐱 cat|🦊 fox|🐝 bee|🦋 butterfly|🍎 apple|🍕 pizza|🍔 burger|☕ coffee|🍺 beer|🍷 wine|🎂 cake|🍪 cookie|🥑 avocado\") },\n];\n\nconst s = (chars: string, names: string[]): Glyph[] => Array.from(chars).map((char, i) => ({ char, name: names[i] ?? char }));\n\nexport const SPECIAL_GROUPS: Array<{ id: string; label: string; items: Glyph[] }> = [\n { id: \"punctuation\", label: \"Punctuation\", items: s(\"©®™°§¶†‡•…‰№‘’“”«»–—¡¿·\", [\"copyright\", \"registered\", \"trademark\", \"degree\", \"section\", \"pilcrow\", \"dagger\", \"double dagger\", \"bullet\", \"ellipsis\", \"per mille\", \"numero\", \"left single quote\", \"right single quote\", \"left double quote\", \"right double quote\", \"left guillemet\", \"right guillemet\", \"en dash\", \"em dash\", \"inverted exclamation\", \"inverted question\", \"middle dot\"]) },\n { id: \"arrows\", label: \"Arrows\", items: s(\"←↑→↓↔↕⇐⇑⇒⇓⇔↩↪➜➤↖↗↘↙↺↻\", [\"left arrow\", \"up arrow\", \"right arrow\", \"down arrow\", \"left right arrow\", \"up down arrow\", \"left double arrow\", \"up double arrow\", \"right double arrow\", \"down double arrow\", \"left right double arrow\", \"return arrow\", \"hook arrow\", \"heavy arrow\", \"arrowhead\", \"north west arrow\", \"north east arrow\", \"south east arrow\", \"south west arrow\", \"anticlockwise arrow\", \"clockwise arrow\"]) },\n { id: \"math\", label: \"Math\", items: s(\"±×÷≈≠≤≥∞√∑∏∫∂∆∇∈∉∪∩⊂⊃∧∨¬∀∃∅∝∠¼½¾⅓⅔²³\", [\"plus minus\", \"times\", \"divide\", \"approximately\", \"not equal\", \"less or equal\", \"greater or equal\", \"infinity\", \"square root\", \"sum\", \"product\", \"integral\", \"partial\", \"increment\", \"nabla\", \"element of\", \"not element\", \"union\", \"intersection\", \"subset\", \"superset\", \"and\", \"or\", \"not\", \"for all\", \"exists\", \"empty set\", \"proportional\", \"angle\", \"one quarter\", \"one half\", \"three quarters\", \"one third\", \"two thirds\", \"squared\", \"cubed\"]) },\n { id: \"currency\", label: \"Currency\", items: s(\"$€£¥¢₹₩₽₺₿฿₫₪₴₦₱\", [\"dollar\", \"euro\", \"pound\", \"yen\", \"cent\", \"rupee\", \"won\", \"ruble\", \"lira\", \"bitcoin\", \"baht\", \"dong\", \"shekel\", \"hryvnia\", \"naira\", \"peso\"]) },\n { id: \"greek\", label: \"Greek\", items: s(\"αβγδεζηθικλμνξοπρστυφχψωΩΣΔΠΦΨΛΘ\", [\"alpha\", \"beta\", \"gamma\", \"delta\", \"epsilon\", \"zeta\", \"eta\", \"theta\", \"iota\", \"kappa\", \"lambda\", \"mu\", \"nu\", \"xi\", \"omicron\", \"pi\", \"rho\", \"sigma\", \"tau\", \"upsilon\", \"phi\", \"chi\", \"psi\", \"omega\", \"capital omega\", \"capital sigma\", \"capital delta\", \"capital pi\", \"capital phi\", \"capital psi\", \"capital lambda\", \"capital theta\"]) },\n { id: \"latin\", label: \"Accented letters\", items: s(\"àáâãäåæçèéêëìíîïñòóôõöøœùúûüýÿßÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØŒÙÚÛÜÝŸ\", []) },\n { id: \"shapes\", label: \"Shapes\", items: s(\"■□▲△▼▽◆◇●○★☆♠♣♥♦♪♫☑☒\", [\"black square\", \"white square\", \"black up triangle\", \"white up triangle\", \"black down triangle\", \"white down triangle\", \"black diamond\", \"white diamond\", \"black circle\", \"white circle\", \"black star\", \"white star\", \"spade\", \"club\", \"heart suit\", \"diamond suit\", \"eighth note\", \"beamed notes\", \"checked box\", \"crossed box\"]) },\n];\n\nexport function searchSymbols(groups: typeof EMOJI_GROUPS, query: string): Glyph[] {\n const q = query.trim().toLowerCase();\n if (!q) return [];\n const seen = new Set<string>();\n const out: Glyph[] = [];\n for (const g of groups) for (const it of g.items) if ((it.char === q || it.name.toLowerCase().includes(q)) && !seen.has(it.char)) (seen.add(it.char), out.push(it));\n return out;\n}\n","/**\n * The BODY TEXT of a Univer document, as agents read and write it (PURE).\n *\n * `matrx-user/documents` declares the body as a value (`document_body_text`)\n * and a write target (`document_body`). Univer owns the document model, so\n * both go through a small port the editor hands up (`DocumentEditor`\n * `onBodyPort`): the read is the body's data stream as plain text, and a write\n * is ONE changed span, applied through Univer's own facade (`deleteRange` /\n * `insertText`, which run `InsertTextCommand` on the command service). That\n * keeps every agent edit on the one editing path a keystroke takes: undo,\n * autosave snapshots, History and collab all see it, and formatting outside\n * the changed span is left exactly as it was.\n *\n * Univer's data stream encodes a paragraph end as `\\r`, a section break as\n * `\\n`, and custom blocks / tables with control characters below U+0020. The\n * plain text maps a paragraph end to `\\n` and leaves the other control tokens\n * out; `offsets` maps each text position back to its data-stream index so a\n * change found in the text lands at the right place in the stream.\n */\n\n\n/** The shape of a surface write handler (structurally the chat runtime's `SurfaceWriteHandlerEntry`; the package imports no chat). */\nexport interface DocumentBodyWriteHandler {\n validate: (value: unknown) => void;\n apply: (value: unknown) => { summary: string; data?: unknown };\n}\n\n/**\n * A product bound, not a database one (snapshots are jsonb): a body an agent\n * sends larger than this is almost certainly a mistake (a whole file pasted\n * twice), and past it the approval card stops being readable.\n */\nexport const DOCUMENT_BODY_MAX_LENGTH = 200_000;\n\n/** What the editor lends the surface. Each call resolves the live document. */\nexport interface DocumentBodyPort {\n /** The main body's Univer data stream (ends with `\\r\\n`). */\n getDataStream: () => string;\n /** The live document snapshot (body with its text runs), for reading the formatting back out. */\n getSnapshot?: () => unknown;\n /** Delete `[start, end)` of the data stream; false when Univer refused. */\n deleteRange: (start: number, end: number) => boolean;\n /** Insert plain text (a `\\n` starts a new paragraph) at a stream index. */\n insertText: (at: number, text: string) => boolean;\n /** Run a Univer command in the open editor (File menu: find and replace, comments). */\n runCommand?: (commandId: string) => Promise<void>;\n /** File → Page setup: open the page setup dialog in the open editor. */\n openPageSetup?: () => void;\n /** File → Language (and the other Docs tool dialogs): open one in the open editor. */\n openTool?: (id: \"language\") => void;\n}\n\nexport interface BodyText {\n text: string;\n /** `offsets[i]` = the stream index of text position `i`; length is text.length + 1. */\n offsets: number[];\n}\n\nconst PARAGRAPH = \"\\r\";\nconst SECTION = \"\\n\";\n\n/** The body's plain text and where each character sits in the stream. */\nexport function bodyTextOf(dataStream: string): BodyText {\n let text = \"\";\n const offsets: number[] = [];\n for (let i = 0; i < dataStream.length; i++) {\n const ch = dataStream[i];\n if (ch === PARAGRAPH) {\n text += \"\\n\";\n offsets.push(i);\n continue;\n }\n if (ch === SECTION) continue;\n if (ch !== \"\\t\" && ch!.charCodeAt(0) < 0x20) continue;\n text += ch;\n offsets.push(i);\n }\n // Every body ends with its last paragraph mark: it is not text a person\n // typed, and it can never be deleted (Univer protects it). Text that ends\n // there inserts before it.\n if (text.endsWith(\"\\n\")) {\n const end = offsets[offsets.length - 1];\n text = text.slice(0, -1);\n offsets.pop();\n offsets.push(end!);\n } else {\n offsets.push(dataStream.length);\n }\n return { text, offsets };\n}\n\nexport interface BodyEdit {\n /** Stream range to remove (may be empty). */\n start: number;\n end: number;\n /** Plain text to insert at `start` (may be empty). */\n insert: string;\n}\n\n/**\n * The ONE span that turns the current body into `next`: the longest common\n * prefix and suffix stay untouched (with their formatting), only the middle\n * changes. Null when nothing differs.\n */\nexport function planBodyEdit(current: BodyText, next: string): BodyEdit | null {\n const a = current.text;\n const b = next;\n if (a === b) return null;\n let prefix = 0;\n const max = Math.min(a.length, b.length);\n while (prefix < max && a[prefix] === b[prefix]) prefix++;\n let suffix = 0;\n while (\n suffix < max - prefix &&\n a[a.length - 1 - suffix] === b[b.length - 1 - suffix]\n )\n suffix++;\n const removeTo = a.length - suffix;\n return {\n start: current.offsets[prefix]!,\n end: current.offsets[removeTo]!,\n insert: b.slice(prefix, b.length - suffix),\n };\n}\n\n/** Apply `next` as the whole body text through the port. */\nexport function writeBodyText(\n port: DocumentBodyPort,\n next: string,\n): { changed: boolean; removed: number; inserted: number } {\n const current = bodyTextOf(port.getDataStream());\n const edit = planBodyEdit(current, next);\n if (!edit) return { changed: false, removed: 0, inserted: 0 };\n if (edit.end > edit.start && !port.deleteRange(edit.start, edit.end)) {\n throw new Error(\n \"The editor refused to remove the old text (the document may be read-only right now). Nothing was changed.\",\n );\n }\n if (edit.insert.length > 0 && !port.insertText(edit.start, edit.insert)) {\n throw new Error(\n edit.end > edit.start\n ? \"The editor removed the old text but refused the new text. Press Undo in the document to bring the old text back, then try again.\"\n : \"The editor refused the new text (the document may be read-only right now). Nothing was changed.\",\n );\n }\n return {\n changed: true,\n removed: edit.end - edit.start,\n inserted: edit.insert.length,\n };\n}\n\nfunction describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n if (typeof value === \"object\") return \"an object\";\n return `a ${typeof value}`;\n}\n\n/** Shape check for an agent-sent body; throws a sentence the agent can act on. */\nexport function validateDocumentBody(value: unknown): string {\n if (typeof value !== \"string\") {\n throw new Error(\n `document_body must be the document's whole text as a plain string (or an anchored edit {command, old_str, new_str}) — received ${describeValue(\n value,\n )}. Read document_body_text, change it, and send the full new text.`,\n );\n }\n if (value.length > DOCUMENT_BODY_MAX_LENGTH) {\n throw new Error(\n `document_body must be ${DOCUMENT_BODY_MAX_LENGTH} characters or fewer (received ${value.length}). Nothing was changed.`,\n );\n }\n return value.replace(/\\r\\n?/g, \"\\n\");\n}\n\n/**\n * The `document_body` write handler. `getPort` answers the live editor's port\n * (null until Univer has mounted the document); `assertWritable` throws when\n * the record is not loaded or the person may only view it.\n */\nexport function documentBodyWriteHandler(deps: {\n getPort: () => DocumentBodyPort | null;\n assertWritable: (whatItBlocks: string) => void;\n}): DocumentBodyWriteHandler {\n return {\n validate: (value) => {\n validateDocumentBody(value);\n },\n apply: (value) => {\n const next = validateDocumentBody(value);\n deps.assertWritable(\"its text cannot be changed\");\n const port = deps.getPort();\n if (!port) {\n throw new Error(\n \"The document editor has not finished opening yet. Nothing was changed; try again in a moment.\",\n );\n }\n const result = writeBodyText(port, next);\n if (!result.changed) {\n return { summary: \"The document already has exactly this text; nothing changed.\" };\n }\n return {\n summary: `Changed the document's text in the editor (removed ${result.removed} characters, inserted ${result.inserted}); the editor saves it within a few seconds, and Undo in the document reverses it.`,\n data: { removed: result.removed, inserted: result.inserted },\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs.cjs","../src/univer/isSnapshotMutation.ts","../src/univer/collab/covered.ts","../src/docs/document-model.ts","../src/docs/document-write-validation.ts","../src/docs/fit-zoom-ratio.ts","../src/docs/documents-hub-display.ts","../src/docs/doc-symbols.ts","../src/docs/document-body-text.ts"],"names":["e"],"mappings":"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CF,IAAA;AACoB,IAAA;AACG,IAAA;AACf,IAAA;AACM,IAAA;AAChB,EAAA;AAIyB,EAAA;AACH,IAAA;AACG,IAAA;AACX,IAAA;AACK,IAAA;AACZ,EAAA;AACQ,IAAA;AACf,EAAA;AACuB,EAAA;AACzB;AAe6B;AACT,EAAA;AACR,EAAA;AACU,EAAA;AACP,EAAA;AACU,EAAA;AACE,EAAA;AACZ,EAAA;AAEI,EAAA;AAGf,IAAA;AACiB,EAAA;AACZ,EAAA;AACkB,IAAA;AACF,IAAA;AACL,IAAA;AAClB,EAAA;AACF;AAIE;AAGgB,EAAA;AACH,EAAA;AACO,EAAA;AACA,EAAA;AACR,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AACyB,EAAA;AACb,IAAA;AACQ,MAAA;AAGlB,IAAA;AACF,EAAA;AACO,EAAA;AACI,IAAA;AACW,IAAA;AACE,IAAA;AACxB,EAAA;AACF;AAEuB;AACD,EAAA;AACM,EAAA;AACL,EAAA;AACG,EAAA;AAC1B;AAGgB;AACO,EAAA;AACT,IAAA;AACR,MAAA;AACE,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AACmB,EAAA;AACP,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AACqB,EAAA;AACvB;AAOgB;AAIP,EAAA;AACgB,IAAA;AACE,MAAA;AACvB,IAAA;AACkB,IAAA;AACH,MAAA;AACO,MAAA;AACF,MAAA;AACP,MAAA;AACC,QAAA;AACR,UAAA;AACF,QAAA;AACF,MAAA;AACe,MAAA;AACM,MAAA;AACD,QAAA;AACpB,MAAA;AACO,MAAA;AACI,QAAA;AACQ,QAAA;AACnB,MAAA;AACF,IAAA;AACF,EAAA;AACF;ARsc2B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs.cjs","sourcesContent":[null,"/**\n * isSnapshotMutation — the autosave dirty-filter for the Univer editors.\n *\n * D97: `onCommandExecuted` fires for EVERY command — including viewport\n * scrolls and selection moves — so an unfiltered listener marked the doc\n * dirty on mere scrolling and wrote a new snapshot row (version history +\n * collab broadcast) 2.5s later. Only CommandType.MUTATION changes the data\n * that a snapshot persists (Univer's own docs: OPERATION = \"not saved to\n * snapshot, such as modifying scroll position\"). We therefore accept ONLY\n * mutations, and additionally deny-list any mutation whose id marks it as\n * scroll / selection / viewport bookkeeping, belt-and-braces.\n */\nimport type { ICommandInfo } from \"@univerjs/core\";\n\n/**\n * Univer's `CommandType.MUTATION`. A literal, so this filter (and the document\n * model that uses it outside the editor chunk) never pulls Univer's runtime —\n * which touches `window` at import — into a server-rendered chunk.\n */\nconst MUTATION = 2;\n\n/** Non-content command ids that must never mark the document dirty. */\nconst NON_CONTENT_ID_PATTERN =\n /(scroll|selection|set-selections|viewport|zoom|hover|cursor|focus|activate)/i;\n\nexport function isSnapshotMutation(command: ICommandInfo): boolean {\n if (command.type !== MUTATION) return false;\n if (NON_CONTENT_ID_PATTERN.test(command.id)) return false;\n return true;\n}\n\n/**\n * Mutations a SPREADSHEET fires on its own while it opens and recalculates —\n * they change nothing the person typed, so they must never mark the workbook\n * dirty. Observed on a plain open of a workbook with formulas (2026-10-10):\n * `formula.mutation.set-trigger-formula-calculation-start`,\n * `...set-formula-calculation-start`, `...set-formula-calculation-notification`\n * and a `doc.mutation.rich-text-editing` from the internal cell-editor document.\n * Each open then wrote a new snapshot (history bloat, and a false \"both changed\"\n * against Google). A real edit always arrives as a `sheet.mutation.*`.\n */\nconst WORKBOOK_SELF_MUTATION_PATTERN =\n /^(formula\\.mutation\\.[a-z-]*(calculation|trigger)[a-z-]*|doc\\.mutation\\.)/;\n\nexport function isWorkbookSnapshotMutation(command: ICommandInfo): boolean {\n if (!isSnapshotMutation(command)) return false;\n return !WORKBOOK_SELF_MUTATION_PATTERN.test(command.id);\n}\n","/**\n * A saved snapshot names the collab-room entries it already holds, so a seat that boots from it never\n * replays them (2026-10-16: a reload showed the text twice). Pure — no Yjs — so the editors' model can\n * stamp and read it without loading the room.\n */\n\n/** The key a saved snapshot carries its covered room entries under (`stampCovered` / `takeCovered`). */\nexport const COLLAB_COVERED_KEY = \"__matrxCollabCovered\";\n\n/** The snapshot to SAVE: `snapshot` plus the room entries it holds (`WorkbookCollabSession.coveredNow`). */\nexport function stampCovered<T extends object>(snapshot: T, covered: readonly string[]): T {\n return { ...snapshot, [COLLAB_COVERED_KEY]: [...covered] };\n}\n\n/** A LOADED snapshot split into the content to mount and the room entries it already holds. */\nexport function takeCovered<T>(snapshot: T): { snapshot: T; covered: Set<string> } {\n if (!snapshot || typeof snapshot !== \"object\" || !(COLLAB_COVERED_KEY in (snapshot as object))) {\n return { snapshot, covered: new Set() };\n }\n const { [COLLAB_COVERED_KEY]: list, ...rest } = snapshot as Record<string, unknown>;\n return {\n snapshot: rest as T,\n covered: new Set(Array.isArray(list) ? list.filter((e): e is string => typeof e === \"string\") : []),\n };\n}\n","/**\n * THE DOCUMENT MODEL — the engine of one cloud document per tab.\n *\n * Before this, the document lived only inside each `DocumentEditor`'s Univer\n * instance. A remount (a board tile waking, a tile removed and undone, a route\n * change and back) rebuilt the editor from the SERVER copy, so whatever the\n * last save had not carried yet was gone; two mounts of one document were two\n * editors, each saving full snapshots of its own copy (last write wins), each\n * with its own realtime channel and its own collab room.\n *\n * The document is a record on THE one working-copy primitive\n * (`@ai-matrx/kit/working-copy`, kind `udt_document`,\n * `./documentModels.ts`): its save status, dirty and view count are Redux state\n * (`workingCopies[\"udt_document:<id>\"]`), its one coalesced save is the kind's.\n * What Redux cannot hold — Univer's body — is this engine, registered on the\n * record's session under the same key:\n *\n * - BODY. Every local mutation in one view is replayed into every other view\n * of the document (the same mutation stream collab sends between machines),\n * so two views show one document. A new view boots from the engine's latest\n * state — a live view's snapshot, or the snapshot the last view left behind\n * — never the stale server copy while the record's session is alive.\n * - SIDE EFFECTS, ONCE. The snapshot realtime channel, the collab room, and\n * the pagehide / visibility / beforeunload flush are opened once per\n * document and closed once.\n * - THE EDITOR ITSELF, KEPT. Univer keeps its undo / redo history inside the\n * editor instance, so rebuilding the editor on a remount threw the history\n * away. When the last view leaves, its editor (a Univer instance with its\n * own DOM host) is parked here — off the page, alive — and the next view of\n * the document RE-ATTACHES that same instance to its own container instead\n * of booting a new one (`parkEditor` / `takeParkedEditor`). Undo and redo\n * therefore work across hide / show, a remount and a removed tile undone,\n * for as long as the record's session is warm; `close()` disposes it.\n */\n\nimport type { ICommandInfo, IDocumentData } from \"@univerjs/core\";\nimport type { CommitReason, WorkingCopyHandle } from \"@ai-matrx/kit/working-copy\";\nimport type { AwarenessState } from \"@ai-matrx/data/realtime/collab\";\nimport { isSnapshotMutation } from \"../univer\";\nimport { stampCovered, takeCovered } from \"../univer/collab/covered\";\nimport type {\n CollabMutationInfo,\n CommandServiceLike,\n} from \"../univer/collab\";\n\n/** Debounce from the last edit to the save. */\nexport const DOCUMENT_SAVE_DELAY_MS = 2500;\n\nexport type DocumentSnapshotData = Partial<IDocumentData>;\n\n/** What a view lends the model while it is mounted. */\nexport interface DocumentViewPort {\n /** Univer's command service for this view's instance. */\n commandService: CommandServiceLike;\n /** The live document of this view (`doc.save()`), or null if it has none. */\n snapshot: () => DocumentSnapshotData | null;\n /** Replace the whole document in this view (a collaborator's snapshot). */\n remount: (snapshot: DocumentSnapshotData) => void;\n /** Whether this view may change the document (read at edit time). */\n editable: () => boolean;\n /**\n * The Univer unit id of this view's mounted document. Every seat mounts its own unit, so a\n * mutation carries the SENDER's id and must be re-addressed to this view's unit before it is\n * applied. Falls back to the id of `snapshot()` when a view does not state it.\n */\n unitId?: () => string | null;\n}\n\n/** Everything the engine reaches outside itself — injected so it is testable. */\nexport interface DocumentModelDeps {\n /** The newest stored snapshot (its row id + body), or null for a document never saved. */\n loadLatest: (documentId: string) => Promise<{ id: string; snapshot: DocumentSnapshotData } | null>;\n save: (input: {\n documentId: string;\n snapshot: DocumentSnapshotData;\n origin: \"autosave\" | \"manual\";\n }) => Promise<{ id: string; createdBy: string | null }>;\n onSaved?: (documentId: string, origin: \"autosave\" | \"manual\") => void;\n /** Window-level listeners (pagehide, visibilitychange, beforeunload). */\n listenToPage?: (handlers: { flush: () => void; hasUnsaved: () => boolean }) => () => void;\n /** A mutation could not be applied to a view: the app says so (a notice); the model re-syncs from the saved version. */\n onApplyFailed?: (documentId: string, mutationId: string, error: unknown) => void;\n}\n\ninterface AttachedView {\n id: number;\n port: DocumentViewPort;\n dispose: () => void;\n}\n\n/** A collab room (WorkbookCollabSession) bound to the model's command stream. */\nexport interface DocumentCollabHandle {\n stop: () => void;\n isHost: () => boolean;\n /** This tab replaced the whole document and saved it as `snapshotId`: peers reload it. */\n announceReplaced: (snapshotId: string) => void;\n /** The room entries this tab's body holds right now (read with the body that is about to be saved). */\n coveredNow: () => string[];\n /** The saved snapshot `snapshotId` holds `covered`: they leave the room's log (the elected saver only). */\n checkpoint: (snapshotId: string, covered: readonly string[]) => void;\n}\n\n/** The saved snapshot this tab's body started from — what the room must never replay on top of it. */\nexport interface DocumentCollabBase {\n snapshotId: () => string | null;\n covered: () => ReadonlySet<string>;\n /** Show the newest saved snapshot (the room's newest checkpoint is newer than what this tab loaded). */\n reload: () => Promise<void>;\n}\n\n/** Who else is in the document's room (shared by every view of it). */\nexport interface DocumentAwareness {\n states: Map<number, AwarenessState>;\n selfUid: string;\n}\n\n/**\n * An editor instance kept alive between views of the document: its DOM host\n * (re-parented into the next view's container) and the instance behind it,\n * whose undo history survives because the instance does. Opaque to the model\n * apart from what it calls.\n */\nexport interface ParkedDocumentEditor<T = unknown> {\n /** The element the editor renders into; the next view appends it to its container. */\n element: HTMLElement;\n instance: T;\n /** Replace the whole document (a collaborator's snapshot arrived while parked). */\n remount: (snapshot: DocumentSnapshotData) => void;\n /** Throw the instance away (the record's session closed). */\n dispose: () => void;\n}\n\nexport type DocumentCollabFactory = (\n commandService: CommandServiceLike,\n onAwareness: (awareness: DocumentAwareness) => void,\n /** A peer replaced the whole document (history restore, Google update). */\n onReplaced: () => void,\n /** What this tab's body was loaded from; pass it to the session (`base`). */\n base: DocumentCollabBase,\n) => Promise<DocumentCollabHandle | null>;\n\nlet viewIds = 0;\n\nexport class DocumentModel {\n readonly documentId: string;\n private readonly deps: DocumentModelDeps;\n private readonly handle: WorkingCopyHandle;\n private readonly views = new Map<number, AttachedView>();\n private lastEditedView: number | null = null;\n /** The document as the last view left it, or as last loaded / saved. */\n private stored: DocumentSnapshotData | null = null;\n private loading: Promise<DocumentSnapshotData | null> | null = null;\n private relaying = false;\n /** Snapshot rows this tab already holds (loaded or written) — never re-applied. */\n private readonly knownSnapshots = new Set<string>();\n private readonly collabListeners = new Set<(info: CollabMutationInfo) => void>();\n private collab: DocumentCollabHandle | null = null;\n /** The saved snapshot the body started from, and the room entries it already holds. */\n private baseId: string | null = null;\n private baseCovered: ReadonlySet<string> = new Set();\n private collabStarting: Promise<void> | null = null;\n private awareness: DocumentAwareness | null = null;\n private readonly awarenessListeners = new Set<() => void>();\n private realtimeClose: (() => void) | null = null;\n private pageClose: (() => void) | null = null;\n /** The last view's editor, alive and off the page until a view takes it back. */\n private parked: ParkedDocumentEditor | null = null;\n\n constructor(handle: WorkingCopyHandle, deps: DocumentModelDeps) {\n this.documentId = handle.id;\n this.handle = handle;\n this.deps = deps;\n }\n\n // ── body ────────────────────────────────────────────────────────────────\n\n /** The newest state of the body this tab holds, without a network read. */\n latest(): DocumentSnapshotData | null {\n const preferred = this.lastEditedView !== null ? this.views.get(this.lastEditedView) : undefined;\n for (const view of preferred ? [preferred, ...this.views.values()] : this.views.values()) {\n try {\n const snapshot = view.port.snapshot();\n if (snapshot) return snapshot;\n } catch (error) {\n console.warn(`[document-model] a view of ${this.documentId} could not report its document`, error);\n }\n }\n return this.stored;\n }\n\n /**\n * What a NEW view should boot from: the model's latest state, or (only when\n * this tab holds none) the newest server snapshot. Null = an empty document.\n * Re-read `latest()` synchronously right before mounting — another view may\n * have typed while the server read was in flight.\n */\n async openingSnapshot(createEmpty: () => DocumentSnapshotData): Promise<DocumentSnapshotData> {\n const held = this.latest();\n if (held) return held;\n if (!this.loading) {\n this.loading = this.deps.loadLatest(this.documentId).then(\n (row) => {\n const loaded = row ? this.takeBase(row) : null;\n if (row && loaded) {\n this.knownSnapshots.add(row.id);\n this.savedFingerprint = contentFingerprint(loaded);\n }\n // A document never saved opens as ONE empty document for every view\n // (one unit id), so their edits replay into each other.\n if (!this.stored) this.stored = loaded ?? createEmpty();\n return this.latest() ?? this.stored;\n },\n (error: unknown) => {\n this.loading = null;\n throw error;\n },\n );\n }\n return (await this.loading) ?? createEmpty();\n }\n\n /** A view's Univer instance is ready: it shares the document from now on. */\n attachView(port: DocumentViewPort): () => void {\n const id = ++viewIds;\n const subscription = port.commandService.onMutationExecutedForCollab((info, options) =>\n this.onViewMutation(id, info, options),\n );\n this.views.set(id, { id, port, dispose: () => subscription.dispose() });\n // Nothing unsaved: what this view mounted IS the saved version (Univer and\n // the snapshot repair may restate it — page geometry, defaults — without\n // anyone changing a word). That is the baseline a save compares against.\n if (!this.handle.hasPending()) {\n try {\n const mounted = port.snapshot();\n if (mounted) this.savedFingerprint = contentFingerprint(mounted);\n } catch (error) {\n console.warn(`[document-model] could not read ${this.documentId}'s mounted document`, error);\n }\n }\n let detached = false;\n return () => {\n if (detached) return;\n detached = true;\n const view = this.views.get(id);\n if (!view) return;\n // Whatever this view shows is the newest body when it is the last one:\n // keep it, so a remount (or the flush on the way out) reads it.\n if (this.views.size === 1) {\n try {\n const snapshot = port.snapshot();\n if (snapshot) this.stored = snapshot;\n } catch (error) {\n console.warn(`[document-model] could not keep ${this.documentId}'s last view's document`, error);\n }\n }\n view.dispose();\n this.views.delete(id);\n if (this.lastEditedView === id) this.lastEditedView = null;\n };\n }\n\n /**\n * The last view of the document is leaving: keep its editor (and Univer's\n * undo history inside it) for the next view. Call AFTER the view's\n * `attachView` detach. Refused — the caller disposes its editor — while\n * another view is still attached (it carries the document) or one is\n * already kept.\n */\n parkEditor(editor: ParkedDocumentEditor): boolean {\n if (this.closed || this.views.size > 0 || this.parked) return false;\n this.parked = editor;\n return true;\n }\n\n /** The kept editor, handed to the view that mounts next (null when none is kept). */\n takeParkedEditor(): ParkedDocumentEditor | null {\n const editor = this.parked;\n this.parked = null;\n return editor;\n }\n\n private onViewMutation(\n viewId: number,\n info: Readonly<CollabMutationInfo>,\n options?: { fromCollab?: boolean; fromChangeset?: boolean; onlyLocal?: boolean },\n ): void {\n // Our own replay into this view, a collab peer's edit, or a load: not a\n // new local edit (each is accounted where it came from).\n if (this.relaying || options?.fromCollab || options?.fromChangeset) return;\n const view = this.views.get(viewId);\n if (!view || !view.port.editable()) return;\n this.lastEditedView = viewId;\n this.relay(info, viewId);\n for (const listener of this.collabListeners) listener(info as CollabMutationInfo);\n if (isContentMutation(info)) this.markDirty();\n }\n\n /** The unit id a view's document has (what a replayed mutation must be addressed to). */\n private unitIdOf(view: AttachedView): string | null {\n try {\n return view.port.unitId?.() ?? view.port.snapshot()?.id ?? null;\n } catch {\n return null;\n }\n }\n\n /** Replay one mutation into every view except `exceptViewId`, addressed to each view's own unit. */\n private relay(info: Readonly<CollabMutationInfo>, exceptViewId: number | null): void {\n if (this.views.size === 0 || (this.views.size === 1 && exceptViewId !== null)) return;\n this.relaying = true;\n let failure: unknown = null;\n try {\n for (const view of this.views.values()) {\n if (view.id === exceptViewId) continue;\n try {\n const params = info.params as unknown;\n const unitId = this.unitIdOf(view);\n const addressed =\n unitId && params && typeof params === \"object\" && !Array.isArray(params) && typeof (params as { unitId?: unknown }).unitId === \"string\" && (params as { unitId: string }).unitId !== unitId\n ? { ...(params as Record<string, unknown>), unitId }\n : info.params;\n view.port.commandService.syncExecuteCommand(info.id, addressed as never, { onlyLocal: true, fromCollab: true });\n } catch (error) {\n failure ??= error;\n console.error(\n `[document-model] a second view of ${this.documentId} could not apply \"${info.id}\" — it may show an older document until it reopens`,\n error,\n );\n }\n }\n } finally {\n this.relaying = false;\n }\n if (failure !== null) this.applyFailed(info.id, failure);\n }\n\n /** A view could not follow an edit: say so, then show the saved version (unless this tab has unsaved edits of its own). */\n private applyFailed(mutationId: string, error: unknown): void {\n try {\n this.deps.onApplyFailed?.(this.documentId, mutationId, error);\n } catch (notifyError) {\n console.warn(`[document-model] the notice for ${this.documentId}'s failed apply also failed`, notifyError);\n }\n if (this.hasUnsaved()) return;\n void this.deps\n .loadLatest(this.documentId)\n .then((row) => {\n if (row && !this.closed) this.show(row);\n })\n .catch((e) => console.warn(`[document-model] could not re-sync ${this.documentId} after a failed apply`, e));\n }\n\n // ── save (run by the record's one coalesced save — documentModels.ts) ─\n\n private markDirty(): void {\n this.handle.touch();\n }\n\n private saving = false;\n /**\n * The content of the version the server holds (loaded or last written). A\n * save of the same content is no save: attaching, detaching, waking or a\n * flush of an unchanged document never writes a snapshot row.\n */\n private savedFingerprint: string | null = null;\n\n /**\n * Write the body as it is now. Returns false when this tab is a collab peer\n * that does not write (the elected host writes the canonical snapshot; a\n * peer's edits reach it through the room). An explicit Save always writes.\n */\n async write(reason: CommitReason): Promise<boolean> {\n const snapshot = this.latest();\n if (!snapshot) throw new Error(\"There is no open document to save.\");\n if (this.collab && !this.collab.isHost() && reason !== \"manual\") return false;\n const fingerprint = contentFingerprint(snapshot);\n if (reason !== \"manual\" && fingerprint === this.savedFingerprint) return false;\n const origin = reason === \"manual\" ? \"manual\" : \"autosave\";\n // The room entries this body holds, read in the same step as the body: the saved row names them,\n // so a tab that reloads from it never replays them on top of it.\n const collab = this.collab;\n const covered = collab ? collab.coveredNow() : null;\n this.saving = true;\n try {\n const { id } = await this.deps.save({\n documentId: this.documentId,\n snapshot: covered ? stampCovered(snapshot, covered) : snapshot,\n origin,\n });\n this.knownSnapshots.add(id);\n this.savedFingerprint = fingerprint;\n if (collab && covered && collab.isHost()) collab.checkpoint(id, covered);\n } finally {\n this.saving = false;\n }\n if (this.views.size === 0) this.stored = snapshot;\n this.deps.onSaved?.(this.documentId, origin);\n return true;\n }\n\n private hasUnsaved(): boolean {\n return this.handle.hasPending();\n }\n\n hasViews(): boolean {\n return this.views.size > 0;\n }\n\n // ── realtime (snapshot inserts) ─────────────────────────────────────────\n\n /** Opened once per document by the first view that asks; idempotent. */\n connectRealtime(open: (onSnapshot: (snapshotId: string) => void) => () => void): void {\n if (this.realtimeClose) return;\n this.realtimeClose = open((snapshotId) => void this.onRemoteSnapshot(snapshotId));\n }\n\n /**\n * A snapshot was committed (here or elsewhere): show a foreign one in every\n * view. Over unsaved edits it is a CONFLICT — the working copy holds it and\n * no save runs (one would overwrite the collaborator's snapshot) until the\n * person chooses; `overUnsaved` is \"Take theirs\" (`./documentModels.ts`).\n */\n async onRemoteSnapshot(snapshotId: string, { overUnsaved = false }: { overUnsaved?: boolean } = {}): Promise<void> {\n if (this.collab) return; // the room is the live channel; snapshots are checkpoints\n if (this.knownSnapshots.has(snapshotId)) return; // ours, or already shown\n if (this.saving) return; // our own save's echo, ahead of its response\n if (this.hasUnsaved() && !overUnsaved) {\n this.handle.conflict({ ref: snapshotId });\n return;\n }\n const row = await this.deps.loadLatest(this.documentId);\n if (!row || (this.hasUnsaved() && !overUnsaved) || this.knownSnapshots.has(row.id)) return;\n this.show(row);\n }\n\n /**\n * THE content swap (see @ai-matrx/office/univer openEditorContent): a SAVED snapshot\n * replaces the document in every view and the kept editor. It is the saved\n * version from now on, so the pending save compares equal and writes\n * nothing (it would put the old content back over the new). In a collab\n * room the peers reload it and the shared log is emptied.\n */\n replaceContent(row: { id: string; snapshot: DocumentSnapshotData }): void {\n this.show(row);\n this.collab?.announceReplaced(row.id);\n }\n\n /** A peer replaced the document: show the newest save (once). */\n private async onPeerReplaced(): Promise<void> {\n const row = await this.deps.loadLatest(this.documentId);\n if (!row || this.knownSnapshots.has(row.id)) return;\n this.show(row);\n }\n\n /** A loaded row becomes the base: its content (what views mount) and the room entries it holds. */\n private takeBase(row: { id: string; snapshot: DocumentSnapshotData }): DocumentSnapshotData {\n const { snapshot, covered } = takeCovered(row.snapshot);\n this.baseId = row.id;\n this.baseCovered = covered;\n return snapshot;\n }\n\n private show(row: { id: string; snapshot: DocumentSnapshotData }): void {\n const snapshot = this.takeBase(row);\n this.knownSnapshots.add(row.id);\n this.savedFingerprint = contentFingerprint(snapshot);\n this.stored = snapshot;\n this.relaying = true;\n try {\n for (const view of this.views.values()) view.port.remount(snapshot);\n this.parked?.remount(snapshot);\n } finally {\n this.relaying = false;\n }\n }\n\n // ── collab (one room per document) ──────────────────────────────────────\n\n /**\n * The model's command stream as ONE command service — what the collab room\n * binds to. Local edits from any view go out once; a peer's edit is applied\n * to every view.\n */\n private readonly virtualCommandService: CommandServiceLike = {\n onMutationExecutedForCollab: (listener) => {\n this.collabListeners.add(listener);\n return { dispose: () => this.collabListeners.delete(listener) };\n },\n syncExecuteCommand: (id, params) => {\n const info = { id, type: 2, params } as CollabMutationInfo;\n this.relay(info, null);\n // The host writes what peers typed.\n if (isContentMutation(info)) this.markDirty();\n return true;\n },\n };\n\n /** Join the document's collab room once (the first view that asks). */\n startCollab(factory: DocumentCollabFactory): Promise<void> {\n if (this.collab) return Promise.resolve();\n if (!this.collabStarting) {\n this.collabStarting = factory(\n this.virtualCommandService,\n (awareness) => {\n this.awareness = awareness;\n for (const listener of Array.from(this.awarenessListeners)) listener();\n },\n () => {\n void this.onPeerReplaced().catch((error: unknown) =>\n console.warn(`[document-model] could not load ${this.documentId}'s replaced version`, error),\n );\n },\n {\n snapshotId: () => this.baseId,\n covered: () => this.baseCovered,\n reload: async () => {\n const row = await this.deps.loadLatest(this.documentId);\n if (row && !this.closed) this.show(row);\n },\n },\n )\n .then((handle) => {\n if (!handle) return;\n if (this.views.size === 0) {\n handle.stop();\n return;\n }\n this.collab = handle;\n })\n .finally(() => {\n this.collabStarting = null;\n });\n }\n return this.collabStarting;\n }\n\n isCollabHost(): boolean {\n return this.collab ? this.collab.isHost() : true;\n }\n\n getAwareness(): DocumentAwareness | null {\n return this.awareness;\n }\n\n subscribeAwareness(listener: () => void): () => void {\n this.awarenessListeners.add(listener);\n return () => this.awarenessListeners.delete(listener);\n }\n\n // ── lifecycle (driven by the record's session) ──────────────────────\n\n /** A view arrived on a document with none. */\n wake(): void {\n if (!this.pageClose && this.deps.listenToPage) {\n this.pageClose = this.deps.listenToPage({\n flush: () => void this.handle.flush(),\n hasUnsaved: () => this.hasUnsaved(),\n });\n }\n }\n\n /**\n * The last view left: leave the collab room (presence must not outlive the\n * person). The snapshot channel stays open while the session is kept alive,\n * so a collaborator's save still reaches the body a returning view boots from.\n */\n rest(): void {\n this.collab?.stop();\n this.collab = null;\n this.awareness = null;\n }\n\n private closed = false;\n\n close(): void {\n this.closed = true;\n const parked = this.parked;\n this.parked = null;\n if (parked) {\n try {\n parked.dispose();\n } catch (error) {\n console.warn(`[document-model] could not dispose ${this.documentId}'s kept editor`, error);\n }\n }\n this.realtimeClose?.();\n this.realtimeClose = null;\n this.pageClose?.();\n this.pageClose = null;\n }\n}\n\n/**\n * What a snapshot STORES, as a comparable string: everything but the unit id\n * and Univer's revision counter (both move without the content moving).\n */\nfunction contentFingerprint(snapshot: DocumentSnapshotData): string {\n const { id: _id, rev: _rev, ...content } = snapshot as DocumentSnapshotData & { rev?: unknown };\n return JSON.stringify(content);\n}\n\n/** Mutations that change what a snapshot stores (never scroll / selection) — the editors' one dirty filter. */\nfunction isContentMutation(info: Readonly<CollabMutationInfo>): boolean {\n return isSnapshotMutation({ id: info.id, type: info.type, params: info.params } as ICommandInfo);\n}\n","/**\n * Pure validation for the `matrx-user/documents` write targets.\n *\n * Deliberately OUTSIDE the React updater. `applySurfaceWrite`\n * (`features/surfaces/runtime/surface-writeback.ts`) turns a THROW from a\n * handler into the error envelope the agent reads back. Validating inside a\n * `setState` updater would instead throw during React's render/commit — after\n * the seam has already reported success — so the agent would be told its bad\n * value landed. Validate here, synchronously, before touching any state.\n *\n * `DOCUMENT_NAME_MAX_LENGTH` is the single source of truth for the limit: it is\n * enforced here, interpolated into the manifest target description, AND set as\n * the `maxLength` of the header rename field on `/documents/[id]` — so the\n * contract the agent reads, the rule the handler enforces, and the limit the\n * human's own control applies cannot drift apart. The last of those matters\n * for a non-obvious reason: the blur commit swallows service failures, so\n * before the field was bounded a pasted 300-character title came back as a\n * `varchar(255)` 400 and reverted with nothing on screen to explain it.\n */\n\n/**\n * `workbench.udt_documents.document_name` is `varchar(255) NOT NULL` — a\n * longer value is rejected by Postgres, not merely by taste. Verified against\n * `information_schema.columns` on 2026-08-11.\n */\nexport const DOCUMENT_NAME_MAX_LENGTH = 255;\n\n/**\n * `workbench.udt_documents.description` is unbounded `text`, so this is a\n * product bound rather than a database one: it matches the 2000 the sibling\n * `workbook_description` target enforces, keeping the two mirrored services\n * mirrored in behaviour too. The library renders this as a two-line blurb —\n * anything near the cap is already past what a reader sees.\n */\nexport const DOCUMENT_DESCRIPTION_MAX_LENGTH = 2000;\n\n/** How a rejected value is described back to the agent, without dumping it. */\nfunction describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n if (typeof value === \"object\") return \"an object\";\n return `a ${typeof value}`;\n}\n\n/**\n * Validate an agent-supplied document title and return the exact string to\n * persist. Throws (never coerces) so the agent hears about its own mistake.\n *\n * The `typeof` guard is load-bearing: the inline-tool layer PARSES a\n * JSON-looking argument before the handler ever sees it, so a caller that\n * sends `{\"document_name\":\"...\"}` or `\"\\\"Quoted\\\"\"` arrives here as an object,\n * not text. The message names the fix explicitly, because an agent that only\n * hears \"invalid\" tends to retry by double-encoding — which lands escaped\n * quotes and `\\n` in the user's title box.\n */\nexport function validateDocumentName(value: unknown): string {\n if (typeof value !== \"string\") {\n throw new Error(\n `document_name must be plain text, not JSON and not JSON-encoded — received ${describeValue(\n value,\n )}. Send only the title itself (for example: Q3 Revenue Review) with no surrounding braces, brackets, quotes or escape sequences.`,\n );\n }\n\n const trimmed = value.trim();\n\n if (trimmed.length === 0) {\n throw new Error(\n \"document_name cannot be empty or whitespace — a document must keep a title. Send the title text you want, or leave the current one alone.\",\n );\n }\n\n if (/[\\r\\n]/.test(trimmed)) {\n throw new Error(\n \"document_name must be a SINGLE line — it renders in the page header, and line breaks are not a title. Send one line of plain text (this is a title, not the document body).\",\n );\n }\n\n if (trimmed.length > DOCUMENT_NAME_MAX_LENGTH) {\n throw new Error(\n `document_name must be ${DOCUMENT_NAME_MAX_LENGTH} characters or fewer (received ${trimmed.length}). Shorten it to a title; the document body is not stored in this field.`,\n );\n }\n\n return trimmed;\n}\n\n/**\n * Validate an agent-supplied document description and return the value to\n * persist — `null` for a deliberate clear, so the caller never has to decide\n * whether \"\" means \"empty string\" or \"no description\".\n *\n * Unlike the title this MAY span lines (it is a blurb, not a header), but it\n * carries the same not-JSON guard for the same reason: the inline-tool layer\n * parses a JSON-looking argument before the handler sees it.\n */\nexport function validateDocumentDescription(value: unknown): string | null {\n if (typeof value !== \"string\") {\n throw new Error(\n `document_description must be plain text, not JSON and not JSON-encoded — received ${describeValue(\n value,\n )}. Send only the description prose itself, with no surrounding braces, brackets, quotes or escape sequences.`,\n );\n }\n\n const trimmed = value.trim();\n\n // An empty string is the documented way to CLEAR the description, so it is\n // valid input rather than a bad shape — normalize it to null for the column.\n if (trimmed.length === 0) return null;\n\n if (trimmed.length > DOCUMENT_DESCRIPTION_MAX_LENGTH) {\n throw new Error(\n `document_description must be ${DOCUMENT_DESCRIPTION_MAX_LENGTH} characters or fewer (received ${trimmed.length}). This is the short blurb shown in the documents library, not the document body.`,\n );\n }\n\n return trimmed;\n}\n","/**\n * The zoom that fits a document page inside a narrow editor (a phone), so the\n * page reflows into view instead of scrolling sideways. Never above 100%, and\n * never so small the text is unreadable. Pure — guarded by fit-zoom-ratio.test.ts.\n */\nexport const MIN_FIT_ZOOM = 0.45;\n\nexport function fitZoomRatio(containerWidth: number, pageWidth: number, gutter = 8): number {\n if (!(containerWidth > 0) || !(pageWidth > 0)) return 1;\n const ratio = (containerWidth - gutter * 2) / pageWidth;\n return Math.min(1, Math.max(MIN_FIT_ZOOM, Math.round(ratio * 100) / 100));\n}\n","import type { DocumentSource } from \"../core\";\n\nconst SOURCE_LABELS: Record<DocumentSource, string> = {\n created: \"Created\",\n imported_docx: \"Imported DOCX\",\n imported_md: \"Imported Markdown\",\n imported_txt: \"Imported Text\",\n linked_gdoc: \"Imported from Google Docs\",\n};\n\nexport function documentSourceLabel(source: DocumentSource): string {\n return SOURCE_LABELS[source] ?? source;\n}\n","/** Insert, Emoji and Special characters: searchable sets, a name per entry. */\n\nexport interface Glyph {\n char: string;\n name: string;\n}\n\nconst e = (rows: string): Glyph[] =>\n rows.split(\"|\").map((r) => r.trim()).filter(Boolean).map((r) => {\n const [char, ...name] = r.split(\" \");\n return { char: char!, name: name.join(\" \") };\n });\n\nexport const EMOJI_GROUPS: Array<{ id: string; label: string; items: Glyph[] }> = [\n { id: \"faces\", label: \"Faces\", items: e(\"😀 grinning|😃 smile|😄 happy|😁 beaming|😆 laughing|😅 sweat smile|😂 tears of joy|🤣 rolling|🙂 slight smile|🙃 upside down|😉 wink|😊 blush|😇 angel|🥰 love|😍 heart eyes|🤩 star struck|😘 kiss|😋 yum|😎 cool|🤓 nerd|🤔 thinking|🤨 skeptical|😐 neutral|🙄 eye roll|😬 grimace|😴 sleeping|😢 cry|😭 sobbing|😤 huff|😡 angry|🤯 mind blown|😱 scream|🥳 party|🤗 hug|🫡 salute|😮 surprised|😳 flushed|🥺 pleading|😷 mask|🤒 sick\") },\n { id: \"people\", label: \"Gestures\", items: e(\"👍 thumbs up|👎 thumbs down|👏 clap|🙌 raised hands|🙏 pray thanks|🤝 handshake|💪 strong flex|👌 ok|✌ victory peace|🤞 fingers crossed|👋 wave|✋ raised hand|👆 point up|👉 point right|👈 point left|👇 point down|✍ writing|🤷 shrug|🙋 raise hand|👀 eyes|🧠 brain|❤ heart|💔 broken heart|💯 hundred\") },\n { id: \"objects\", label: \"Objects\", items: e(\"💡 idea light bulb|📌 pin|📎 paperclip|📝 memo note|📅 calendar|📈 chart up|📉 chart down|📊 bar chart|📁 folder|📄 page|📧 email|📞 phone|💻 laptop|📱 mobile|🔒 lock|🔑 key|🔍 search|🔔 bell|⏰ alarm clock|⌛ hourglass|🎯 target|🏆 trophy|🎁 gift|📦 package|🛠 tools|⚙ gear|💰 money bag|💵 dollar|🧾 receipt|📚 books\") },\n { id: \"symbols\", label: \"Marks\", items: e(\"✅ check mark|❌ cross|⭐ star|🌟 glowing star|✨ sparkles|🔥 fire|⚡ lightning|🚀 rocket|🎉 party popper|🎊 confetti|⚠ warning|❓ question|❗ exclamation|➕ plus|➖ minus|🔴 red circle|🟠 orange circle|🟡 yellow circle|🟢 green circle|🔵 blue circle|🟣 purple circle|⬆ up|⬇ down|➡ right|⬅ left|🔁 repeat|✔ heavy check\") },\n { id: \"nature\", label: \"Nature and food\", items: e(\"🌞 sun|🌙 moon|☁ cloud|🌧 rain|❄ snowflake|🌈 rainbow|🌱 seedling|🌳 tree|🌸 blossom|🌍 earth|🐶 dog|🐱 cat|🦊 fox|🐝 bee|🦋 butterfly|🍎 apple|🍕 pizza|🍔 burger|☕ coffee|🍺 beer|🍷 wine|🎂 cake|🍪 cookie|🥑 avocado\") },\n];\n\nconst s = (chars: string, names: string[]): Glyph[] => Array.from(chars).map((char, i) => ({ char, name: names[i] ?? char }));\n\nexport const SPECIAL_GROUPS: Array<{ id: string; label: string; items: Glyph[] }> = [\n { id: \"punctuation\", label: \"Punctuation\", items: s(\"©®™°§¶†‡•…‰№‘’“”«»–—¡¿·\", [\"copyright\", \"registered\", \"trademark\", \"degree\", \"section\", \"pilcrow\", \"dagger\", \"double dagger\", \"bullet\", \"ellipsis\", \"per mille\", \"numero\", \"left single quote\", \"right single quote\", \"left double quote\", \"right double quote\", \"left guillemet\", \"right guillemet\", \"en dash\", \"em dash\", \"inverted exclamation\", \"inverted question\", \"middle dot\"]) },\n { id: \"arrows\", label: \"Arrows\", items: s(\"←↑→↓↔↕⇐⇑⇒⇓⇔↩↪➜➤↖↗↘↙↺↻\", [\"left arrow\", \"up arrow\", \"right arrow\", \"down arrow\", \"left right arrow\", \"up down arrow\", \"left double arrow\", \"up double arrow\", \"right double arrow\", \"down double arrow\", \"left right double arrow\", \"return arrow\", \"hook arrow\", \"heavy arrow\", \"arrowhead\", \"north west arrow\", \"north east arrow\", \"south east arrow\", \"south west arrow\", \"anticlockwise arrow\", \"clockwise arrow\"]) },\n { id: \"math\", label: \"Math\", items: s(\"±×÷≈≠≤≥∞√∑∏∫∂∆∇∈∉∪∩⊂⊃∧∨¬∀∃∅∝∠¼½¾⅓⅔²³\", [\"plus minus\", \"times\", \"divide\", \"approximately\", \"not equal\", \"less or equal\", \"greater or equal\", \"infinity\", \"square root\", \"sum\", \"product\", \"integral\", \"partial\", \"increment\", \"nabla\", \"element of\", \"not element\", \"union\", \"intersection\", \"subset\", \"superset\", \"and\", \"or\", \"not\", \"for all\", \"exists\", \"empty set\", \"proportional\", \"angle\", \"one quarter\", \"one half\", \"three quarters\", \"one third\", \"two thirds\", \"squared\", \"cubed\"]) },\n { id: \"currency\", label: \"Currency\", items: s(\"$€£¥¢₹₩₽₺₿฿₫₪₴₦₱\", [\"dollar\", \"euro\", \"pound\", \"yen\", \"cent\", \"rupee\", \"won\", \"ruble\", \"lira\", \"bitcoin\", \"baht\", \"dong\", \"shekel\", \"hryvnia\", \"naira\", \"peso\"]) },\n { id: \"greek\", label: \"Greek\", items: s(\"αβγδεζηθικλμνξοπρστυφχψωΩΣΔΠΦΨΛΘ\", [\"alpha\", \"beta\", \"gamma\", \"delta\", \"epsilon\", \"zeta\", \"eta\", \"theta\", \"iota\", \"kappa\", \"lambda\", \"mu\", \"nu\", \"xi\", \"omicron\", \"pi\", \"rho\", \"sigma\", \"tau\", \"upsilon\", \"phi\", \"chi\", \"psi\", \"omega\", \"capital omega\", \"capital sigma\", \"capital delta\", \"capital pi\", \"capital phi\", \"capital psi\", \"capital lambda\", \"capital theta\"]) },\n { id: \"latin\", label: \"Accented letters\", items: s(\"àáâãäåæçèéêëìíîïñòóôõöøœùúûüýÿßÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØŒÙÚÛÜÝŸ\", []) },\n { id: \"shapes\", label: \"Shapes\", items: s(\"■□▲△▼▽◆◇●○★☆♠♣♥♦♪♫☑☒\", [\"black square\", \"white square\", \"black up triangle\", \"white up triangle\", \"black down triangle\", \"white down triangle\", \"black diamond\", \"white diamond\", \"black circle\", \"white circle\", \"black star\", \"white star\", \"spade\", \"club\", \"heart suit\", \"diamond suit\", \"eighth note\", \"beamed notes\", \"checked box\", \"crossed box\"]) },\n];\n\nexport function searchSymbols(groups: typeof EMOJI_GROUPS, query: string): Glyph[] {\n const q = query.trim().toLowerCase();\n if (!q) return [];\n const seen = new Set<string>();\n const out: Glyph[] = [];\n for (const g of groups) for (const it of g.items) if ((it.char === q || it.name.toLowerCase().includes(q)) && !seen.has(it.char)) (seen.add(it.char), out.push(it));\n return out;\n}\n","/**\n * The BODY TEXT of a Univer document, as agents read and write it (PURE).\n *\n * `matrx-user/documents` declares the body as a value (`document_body_text`)\n * and a write target (`document_body`). Univer owns the document model, so\n * both go through a small port the editor hands up (`DocumentEditor`\n * `onBodyPort`): the read is the body's data stream as plain text, and a write\n * is ONE changed span, applied through Univer's own facade (`deleteRange` /\n * `insertText`, which run `InsertTextCommand` on the command service). That\n * keeps every agent edit on the one editing path a keystroke takes: undo,\n * autosave snapshots, History and collab all see it, and formatting outside\n * the changed span is left exactly as it was.\n *\n * Univer's data stream encodes a paragraph end as `\\r`, a section break as\n * `\\n`, and custom blocks / tables with control characters below U+0020. The\n * plain text maps a paragraph end to `\\n` and leaves the other control tokens\n * out; `offsets` maps each text position back to its data-stream index so a\n * change found in the text lands at the right place in the stream.\n */\n\n\n/** The shape of a surface write handler (structurally the chat runtime's `SurfaceWriteHandlerEntry`; the package imports no chat). */\nexport interface DocumentBodyWriteHandler {\n validate: (value: unknown) => void;\n apply: (value: unknown) => { summary: string; data?: unknown };\n}\n\n/**\n * A product bound, not a database one (snapshots are jsonb): a body an agent\n * sends larger than this is almost certainly a mistake (a whole file pasted\n * twice), and past it the approval card stops being readable.\n */\nexport const DOCUMENT_BODY_MAX_LENGTH = 200_000;\n\n/** What the editor lends the surface. Each call resolves the live document. */\nexport interface DocumentBodyPort {\n /** The main body's Univer data stream (ends with `\\r\\n`). */\n getDataStream: () => string;\n /** The live document snapshot (body with its text runs), for reading the formatting back out. */\n getSnapshot?: () => unknown;\n /** Delete `[start, end)` of the data stream; false when Univer refused. */\n deleteRange: (start: number, end: number) => boolean;\n /** Insert plain text (a `\\n` starts a new paragraph) at a stream index. */\n insertText: (at: number, text: string) => boolean;\n /** Run a Univer command in the open editor (File menu: find and replace, comments). */\n runCommand?: (commandId: string) => Promise<void>;\n /** File → Page setup: open the page setup dialog in the open editor. */\n openPageSetup?: () => void;\n /** File → Language (and the other Docs tool dialogs): open one in the open editor. */\n openTool?: (id: \"language\") => void;\n}\n\nexport interface BodyText {\n text: string;\n /** `offsets[i]` = the stream index of text position `i`; length is text.length + 1. */\n offsets: number[];\n}\n\nconst PARAGRAPH = \"\\r\";\nconst SECTION = \"\\n\";\n\n/** The body's plain text and where each character sits in the stream. */\nexport function bodyTextOf(dataStream: string): BodyText {\n let text = \"\";\n const offsets: number[] = [];\n for (let i = 0; i < dataStream.length; i++) {\n const ch = dataStream[i];\n if (ch === PARAGRAPH) {\n text += \"\\n\";\n offsets.push(i);\n continue;\n }\n if (ch === SECTION) continue;\n if (ch !== \"\\t\" && ch!.charCodeAt(0) < 0x20) continue;\n text += ch;\n offsets.push(i);\n }\n // Every body ends with its last paragraph mark: it is not text a person\n // typed, and it can never be deleted (Univer protects it). Text that ends\n // there inserts before it.\n if (text.endsWith(\"\\n\")) {\n const end = offsets[offsets.length - 1];\n text = text.slice(0, -1);\n offsets.pop();\n offsets.push(end!);\n } else {\n offsets.push(dataStream.length);\n }\n return { text, offsets };\n}\n\nexport interface BodyEdit {\n /** Stream range to remove (may be empty). */\n start: number;\n end: number;\n /** Plain text to insert at `start` (may be empty). */\n insert: string;\n}\n\n/**\n * The ONE span that turns the current body into `next`: the longest common\n * prefix and suffix stay untouched (with their formatting), only the middle\n * changes. Null when nothing differs.\n */\nexport function planBodyEdit(current: BodyText, next: string): BodyEdit | null {\n const a = current.text;\n const b = next;\n if (a === b) return null;\n let prefix = 0;\n const max = Math.min(a.length, b.length);\n while (prefix < max && a[prefix] === b[prefix]) prefix++;\n let suffix = 0;\n while (\n suffix < max - prefix &&\n a[a.length - 1 - suffix] === b[b.length - 1 - suffix]\n )\n suffix++;\n const removeTo = a.length - suffix;\n return {\n start: current.offsets[prefix]!,\n end: current.offsets[removeTo]!,\n insert: b.slice(prefix, b.length - suffix),\n };\n}\n\n/** Apply `next` as the whole body text through the port. */\nexport function writeBodyText(\n port: DocumentBodyPort,\n next: string,\n): { changed: boolean; removed: number; inserted: number } {\n const current = bodyTextOf(port.getDataStream());\n const edit = planBodyEdit(current, next);\n if (!edit) return { changed: false, removed: 0, inserted: 0 };\n if (edit.end > edit.start && !port.deleteRange(edit.start, edit.end)) {\n throw new Error(\n \"The editor refused to remove the old text (the document may be read-only right now). Nothing was changed.\",\n );\n }\n if (edit.insert.length > 0 && !port.insertText(edit.start, edit.insert)) {\n throw new Error(\n edit.end > edit.start\n ? \"The editor removed the old text but refused the new text. Press Undo in the document to bring the old text back, then try again.\"\n : \"The editor refused the new text (the document may be read-only right now). Nothing was changed.\",\n );\n }\n return {\n changed: true,\n removed: edit.end - edit.start,\n inserted: edit.insert.length,\n };\n}\n\nfunction describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n if (typeof value === \"object\") return \"an object\";\n return `a ${typeof value}`;\n}\n\n/** Shape check for an agent-sent body; throws a sentence the agent can act on. */\nexport function validateDocumentBody(value: unknown): string {\n if (typeof value !== \"string\") {\n throw new Error(\n `document_body must be the document's whole text as a plain string (or an anchored edit {command, old_str, new_str}) — received ${describeValue(\n value,\n )}. Read document_body_text, change it, and send the full new text.`,\n );\n }\n if (value.length > DOCUMENT_BODY_MAX_LENGTH) {\n throw new Error(\n `document_body must be ${DOCUMENT_BODY_MAX_LENGTH} characters or fewer (received ${value.length}). Nothing was changed.`,\n );\n }\n return value.replace(/\\r\\n?/g, \"\\n\");\n}\n\n/**\n * The `document_body` write handler. `getPort` answers the live editor's port\n * (null until Univer has mounted the document); `assertWritable` throws when\n * the record is not loaded or the person may only view it.\n */\nexport function documentBodyWriteHandler(deps: {\n getPort: () => DocumentBodyPort | null;\n assertWritable: (whatItBlocks: string) => void;\n}): DocumentBodyWriteHandler {\n return {\n validate: (value) => {\n validateDocumentBody(value);\n },\n apply: (value) => {\n const next = validateDocumentBody(value);\n deps.assertWritable(\"its text cannot be changed\");\n const port = deps.getPort();\n if (!port) {\n throw new Error(\n \"The document editor has not finished opening yet. Nothing was changed; try again in a moment.\",\n );\n }\n const result = writeBodyText(port, next);\n if (!result.changed) {\n return { summary: \"The document already has exactly this text; nothing changed.\" };\n }\n return {\n summary: `Changed the document's text in the editor (removed ${result.removed} characters, inserted ${result.inserted}); the editor saves it within a few seconds, and Undo in the document reverses it.`,\n data: { removed: result.removed, inserted: result.inserted },\n };\n },\n };\n}\n"]}
package/dist/docs.d.cts CHANGED
@@ -158,6 +158,11 @@ type CollabMutationInfo = {
158
158
  params: MutationParams;
159
159
  /** Who made it (the person's uid), so a peer can tell its own other tab's edit from a stranger's. */
160
160
  uid?: string;
161
+ /**
162
+ * The entry's identity in the room (`<clientId>:<seq>`). A saved snapshot names the entries it holds
163
+ * (`stampCovered`), so a seat that boots from that snapshot never replays them a second time.
164
+ */
165
+ eid?: string;
161
166
  };
162
167
  type ExecuteOptions = {
163
168
  /** Per-Univer convention: when true, the command runs locally only and is
@@ -258,6 +263,17 @@ interface DocumentCollabHandle {
258
263
  isHost: () => boolean;
259
264
  /** This tab replaced the whole document and saved it as `snapshotId`: peers reload it. */
260
265
  announceReplaced: (snapshotId: string) => void;
266
+ /** The room entries this tab's body holds right now (read with the body that is about to be saved). */
267
+ coveredNow: () => string[];
268
+ /** The saved snapshot `snapshotId` holds `covered`: they leave the room's log (the elected saver only). */
269
+ checkpoint: (snapshotId: string, covered: readonly string[]) => void;
270
+ }
271
+ /** The saved snapshot this tab's body started from — what the room must never replay on top of it. */
272
+ interface DocumentCollabBase {
273
+ snapshotId: () => string | null;
274
+ covered: () => ReadonlySet<string>;
275
+ /** Show the newest saved snapshot (the room's newest checkpoint is newer than what this tab loaded). */
276
+ reload: () => Promise<void>;
261
277
  }
262
278
  /** Who else is in the document's room (shared by every view of it). */
263
279
  interface DocumentAwareness {
@@ -281,7 +297,9 @@ interface ParkedDocumentEditor<T = unknown> {
281
297
  }
282
298
  type DocumentCollabFactory = (commandService: CommandServiceLike, onAwareness: (awareness: DocumentAwareness) => void,
283
299
  /** A peer replaced the whole document (history restore, Google update). */
284
- onReplaced: () => void) => Promise<DocumentCollabHandle | null>;
300
+ onReplaced: () => void,
301
+ /** What this tab's body was loaded from; pass it to the session (`base`). */
302
+ base: DocumentCollabBase) => Promise<DocumentCollabHandle | null>;
285
303
  declare class DocumentModel {
286
304
  readonly documentId: string;
287
305
  private readonly deps;
@@ -296,6 +314,9 @@ declare class DocumentModel {
296
314
  private readonly knownSnapshots;
297
315
  private readonly collabListeners;
298
316
  private collab;
317
+ /** The saved snapshot the body started from, and the room entries it already holds. */
318
+ private baseId;
319
+ private baseCovered;
299
320
  private collabStarting;
300
321
  private awareness;
301
322
  private readonly awarenessListeners;
@@ -372,6 +393,8 @@ declare class DocumentModel {
372
393
  }): void;
373
394
  /** A peer replaced the document: show the newest save (once). */
374
395
  private onPeerReplaced;
396
+ /** A loaded row becomes the base: its content (what views mount) and the room entries it holds. */
397
+ private takeBase;
375
398
  private show;
376
399
  /**
377
400
  * The model's command stream as ONE command service — what the collab room
@@ -579,4 +602,4 @@ declare function documentBodyWriteHandler(deps: {
579
602
  assertWritable: (whatItBlocks: string) => void;
580
603
  }): DocumentBodyWriteHandler;
581
604
 
582
- export { type BodyEdit, type BodyText, type CreateDocumentArgs, DOCUMENT_BODY_MAX_LENGTH, DOCUMENT_DESCRIPTION_MAX_LENGTH, DOCUMENT_NAME_MAX_LENGTH, DOCUMENT_SAVE_DELAY_MS, type DocsHostPorts, type DocsSupabase, type DocumentAwareness, type DocumentBodyPort, type DocumentBodyWriteHandler, type DocumentCollabFactory, type DocumentCollabHandle, DocumentModel, type DocumentModelDeps, type DocumentSnapshotData, type DocumentViewPort, EMOJI_GROUPS, type Glyph, MIN_FIT_ZOOM, type ParkedDocumentEditor, SPECIAL_GROUPS, type SaveDocumentSnapshotArgs, bodyTextOf, configureDocsHost, copyDocument, createDocument, deleteDocument, discardFailedDocument, documentBodyWriteHandler, documentSourceLabel, fitZoomRatio, getDocsHost, getDocument, getLatestDocumentSnapshot, listAccessibleDocuments, listDocumentSnapshots, listTrashedDocuments, moveDocument, planBodyEdit, renameDocument, restoreDocument, sanitizeUniverDocSnapshot, saveDocumentSnapshot, searchSymbols, updateDocumentDescription, validateDocumentBody, validateDocumentDescription, validateDocumentName, writeBodyText };
605
+ export { type BodyEdit, type BodyText, type CreateDocumentArgs, DOCUMENT_BODY_MAX_LENGTH, DOCUMENT_DESCRIPTION_MAX_LENGTH, DOCUMENT_NAME_MAX_LENGTH, DOCUMENT_SAVE_DELAY_MS, type DocsHostPorts, type DocsSupabase, type DocumentAwareness, type DocumentBodyPort, type DocumentBodyWriteHandler, type DocumentCollabBase, type DocumentCollabFactory, type DocumentCollabHandle, DocumentModel, type DocumentModelDeps, type DocumentSnapshotData, type DocumentViewPort, EMOJI_GROUPS, type Glyph, MIN_FIT_ZOOM, type ParkedDocumentEditor, SPECIAL_GROUPS, type SaveDocumentSnapshotArgs, bodyTextOf, configureDocsHost, copyDocument, createDocument, deleteDocument, discardFailedDocument, documentBodyWriteHandler, documentSourceLabel, fitZoomRatio, getDocsHost, getDocument, getLatestDocumentSnapshot, listAccessibleDocuments, listDocumentSnapshots, listTrashedDocuments, moveDocument, planBodyEdit, renameDocument, restoreDocument, sanitizeUniverDocSnapshot, saveDocumentSnapshot, searchSymbols, updateDocumentDescription, validateDocumentBody, validateDocumentDescription, validateDocumentName, writeBodyText };