@ai-matrx/office 0.7.0 → 0.8.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.
@@ -1,5 +1,8 @@
1
1
  import * as Y from 'yjs';
2
2
  import { Awareness } from 'y-protocols/awareness';
3
+ import { OpSessionNotice, OpSession, OpTransport, OpPresence, OpSessionIncident, OpSessionStatus, AwarenessState } from '@ai-matrx/data/realtime/collab';
4
+ import { IDocumentData } from '@univerjs/core';
5
+ import { CommitReason } from '@ai-matrx/kit/working-copy';
3
6
 
4
7
  /**
5
8
  * A saved snapshot names the collab-room entries it already holds, so a seat that boots from it never
@@ -210,4 +213,252 @@ declare const SENDER_VIEW_PARAMS: readonly string[];
210
213
  declare function stripSenderView(params: MutationParams): MutationParams;
211
214
  declare function isCellEditorScratch(info: Pick<CollabMutationInfo, "id">): boolean;
212
215
 
213
- export { COLLAB_COVERED_KEY, type CollabBase, type CollabMutationInfo, type CollabProviderLike, type CommandServiceLike, SENDER_VIEW_PARAMS, WorkbookCollabSession, type WorkbookCollabSessionOptions, isCellEditorScratch, stampCovered, stripSenderView, takeCovered };
216
+ type Act = Record<string, any> & {
217
+ t: "i" | "r" | "d";
218
+ len: number;
219
+ };
220
+ /** One `lost` entry: what a transform dropped from either side (R1), for the author's notice. */
221
+ interface DocsLost {
222
+ rule: string;
223
+ side: "insert" | "delete";
224
+ len: number;
225
+ text?: string;
226
+ }
227
+ /** The travelling shape of a Docs JSON op: text-x edits normalised (R1 zones, R5, no oldBody). */
228
+ declare function normalizeDocsOp<T>(op: T): T;
229
+ /** R6: a retain whose style lost every key to the other side is a plain retain. */
230
+ declare function dropEmptyStyleRetains<T>(op: T): T;
231
+ /** x rewritten to apply after y over one text-x body; `side` is x's side. Drops land in `lost` (R1). */
232
+ declare function transformTextX(xIn: Act[], yIn: Act[], side: "left" | "right", lost?: DocsLost[]): Act[];
233
+ /** A Docs operation: an ot-json1 op over the whole Univer snapshot (what `rich-text-editing` carries as `actions`). */
234
+ type DocsOp = unknown[] | null;
235
+ interface DocsTransformResult {
236
+ op: DocsOp;
237
+ lost: DocsLost[];
238
+ }
239
+ /**
240
+ * `T(x, y, side)` for Docs: ot-json1 `transformNoConflict` (never throws on DROP_COLLISION / RM_UNEXPECTED_CONTENT /
241
+ * BLACKHOLE, §3) with our text-x transform registered only for the duration of one call, so Univer's own apply is
242
+ * untouched even when the bundler shares one ot-json1 instance with `@univerjs/core`. R6 runs on the result.
243
+ */
244
+ declare function transformDocsOp(x: DocsOp, y: DocsOp, side: "left" | "right"): DocsTransformResult;
245
+ /** `a` then `b` as one op (TextX is the subtype every reader composes with). */
246
+ declare function composeDocsOps(a: DocsOp, b: DocsOp): DocsOp;
247
+ declare function isNoopDocsOp(op: DocsOp): boolean;
248
+ /** Apply an op to a plain snapshot (tests, the fold). Univer's own JSONX, so TextX applies as the editor applies. */
249
+ declare function applyDocsOp<T>(doc: T, op: DocsOp): T;
250
+ /** The inverse of `op` against the state it applied to. JSONX.invertWithDoc MUTATES its op: invert a copy (C0). */
251
+ declare function invertDocsOp(op: DocsOp, preDoc: unknown): DocsOp;
252
+ /** Part of `proto` (§6.4): Univer 1.0.4 + this transformer, rules R1–R6. */
253
+ declare const DOCS_TRANSFORM_VERSION = "univer-1.0.4+docs-ot-r6";
254
+
255
+ /**
256
+ * The canonical Docs state hash (COLLAB-DESIGN §7.2; C0 item 6, `spikes/c0-collab/lib.mjs`).
257
+ *
258
+ * SHA-256 of the canonical snapshot: keys sorted, empty values dropped, the unit id and seat-local fields
259
+ * removed (revision counter, collab stamps, layout state, selections), adjacent equal text runs merged, split
260
+ * pieces of one custom range or decoration folded, and `paragraphId` dropped (Univer's TextX.apply mints a
261
+ * random one per seat, C0 surprise 3). Header, footer and note bodies get the same rules.
262
+ *
263
+ * Two seats with equal content hash equal; one character more hashes differently. Browser and Node: the
264
+ * digest is Web Crypto's.
265
+ */
266
+ /** Never content: the collab stamps, revision counters, seat-local view state. */
267
+ declare const DOC_LOCAL_KEYS: string[];
268
+ /** The canonical Docs snapshot: content only, keys sorted, runs normalised, local fields removed. */
269
+ declare function canonicalDocsSnapshot(snapshot: unknown): unknown;
270
+ /** The canonical form as the string the hash digests (tests compare these directly). */
271
+ declare const canonicalDocsString: (snapshot: unknown) => string;
272
+ /** SHA-256 hex of the canonical snapshot (§7.2). */
273
+ declare function docsStateHash(snapshot: unknown): Promise<string>;
274
+ /**
275
+ * Ids every seat agrees on for a snapshot that lacks them (C0 surprise 5): a raw snapshot (an import, markdown)
276
+ * loaded on two seats would otherwise get two random `sectionId`s / `paragraphId`s. Missing ids become
277
+ * positional ones; ids already present are kept. Applied to every snapshot a room loads.
278
+ */
279
+ declare function stampDeterministicIds<T>(snapshot: T): T;
280
+
281
+ /**
282
+ * The Docs co-editing room on the ordered log (COLLAB-DESIGN §6, unit C3): the `OpDocumentAdapter` for
283
+ * `openOpSession` over a document's editors, plus saves, restore and the saver choice.
284
+ *
285
+ * - CAPTURE (§6.3). The document model hands over every local `doc.mutation.rich-text-editing` with the applied
286
+ * params (the handler's own rewrite, C0 surprise 6), the pre-state cloned in `beforeCommandExecuted`, and the
287
+ * outermost command id. `onlyLocal` / `fromCollab` never arrive here (the model filters them). Only `segmentId`
288
+ * and `actions` travel; the inverse is computed from the APPLIED actions on a COPY (`invertWithDoc` mutates its
289
+ * op). Each mutation goes to the session as it happens, so a remote apply can never land between a local
290
+ * mutation and its hand-over; the command id rides along as the change's label.
291
+ * - APPLY (§6.7). `syncExecuteCommand(id, { unitId: own, segmentId, actions, textRanges: null,
292
+ * noNeedSetTextRange: true }, { onlyLocal: true, fromCollab: true })` through the model (every view of the
293
+ * document in this tab). A throw, `false`, or actions the handler emptied fail the apply: the session rebuilds,
294
+ * never skips. While an IME composition is open, remote applies QUEUE (acks continue); a local op captured
295
+ * meanwhile is rebased over the queue (`L' = T(L, Q, 'left')`, `Q' = T(Q, L, 'right')`), and the queue drains
296
+ * at `compositionend`.
297
+ * - UNDO (§6.9). Each applied remote op rebases every stored undo and redo item of this seat
298
+ * (`T(item, R1, 'left')`); `textRanges` and `layoutState` are stripped from rebased items; an item whose rebase
299
+ * is a barrier, throws or comes back empty is dropped. Undo therefore only ever reverts this person's changes.
300
+ * - SAVES (§7.3). Only a quiet seat whose hash matches every peer at its rev saves, and only the chosen saver
301
+ * (the lowest client id among seats publishing a quiet hash) saves on its own; a manual save always tries.
302
+ * - RESTORE (§20). A whole-document replace goes through `collab_replace`: the snapshot is loaded into this seat's
303
+ * editor first, so the stored snapshot and its hash are the Univer-normalised form every seat will load.
304
+ */
305
+
306
+ declare const RICH_TEXT_EDITING = "doc.mutation.rich-text-editing";
307
+ /**
308
+ * The room's protocol (§6.4): Univer 1.0.4 + transformer `DOCS_TRANSFORM_VERSION`. Any Univer or transformer
309
+ * change raises it; a room at another proto opens read-only for this build.
310
+ */
311
+ declare const DOCS_COLLAB_PROTO = 1;
312
+ /** One local mutation as the document model hands it over. */
313
+ interface DocsLocalMutation {
314
+ id: string;
315
+ params: unknown;
316
+ /** The document before this mutation (cloned in `beforeCommandExecuted`), when the view could supply it. */
317
+ pre: unknown | null;
318
+ /** The outermost command this mutation belongs to (the depth tracker with its try/finally guard). */
319
+ cmd: string | null;
320
+ /** Whether the mutation changes what a snapshot stores. */
321
+ content: boolean;
322
+ }
323
+ /** What the document model lends the room (implemented by `DocumentModel`). */
324
+ interface DocsOtPort {
325
+ onLocal(listener: (m: DocsLocalMutation) => void): () => void;
326
+ /** Apply one committed mutation to every view of the document; throws when any view could not. */
327
+ applyRemote(id: string, params: Record<string, unknown>): void;
328
+ /** The document as the editor holds it now (`doc.save()`), or null when no view is mounted. */
329
+ snapshot(): unknown | null;
330
+ /** Replace the document in every view (and the kept editor). */
331
+ load(snapshot: unknown): void;
332
+ /** Univer's undo/redo service of each mounted view and the unit it holds. */
333
+ undoTargets(): Array<{
334
+ service: unknown;
335
+ unitId: string;
336
+ }>;
337
+ /** Ask for a save (the record's one coalesced save). */
338
+ markDirty(): void;
339
+ }
340
+ type DocsOtNotice = OpSessionNotice<DocsOp> | {
341
+ kind: "not_shared";
342
+ mutationId: string;
343
+ } | {
344
+ kind: "not_live";
345
+ };
346
+ interface OpenDocsOtRoomOptions {
347
+ port: DocsOtPort;
348
+ transport: OpTransport;
349
+ clientId: string;
350
+ presence?: OpPresence;
351
+ /** `collab_replace` for this room (restore, whole import). */
352
+ replace: (req: {
353
+ snapshot: unknown;
354
+ stateHash: string;
355
+ clientId: string;
356
+ }) => Promise<{
357
+ rev: number;
358
+ ref: string;
359
+ }>;
360
+ onNotice?: (n: DocsOtNotice) => void;
361
+ onIncident?: (i: OpSessionIncident) => void;
362
+ onStatus?: (s: OpSessionStatus) => void;
363
+ /** Watch IME composition here (default: `document`). */
364
+ compositionTarget?: Pick<Document, "addEventListener" | "removeEventListener"> | null;
365
+ }
366
+ interface DocsOtRoom {
367
+ session: OpSession<DocsOp>;
368
+ /** The record's save: a checkpoint at a quiet point (§7.3). False when this seat does not write now. */
369
+ write(reason: "manual" | string): Promise<boolean>;
370
+ /** Restore / whole import through `collab_replace` (§20). */
371
+ replace(snapshot: unknown): Promise<void>;
372
+ /** This seat is the one that saves on its own. */
373
+ isSaver(): boolean;
374
+ stop(): void;
375
+ }
376
+ interface UndoItem {
377
+ unitID?: string;
378
+ undoMutations?: Array<{
379
+ id: string;
380
+ params: Record<string, unknown>;
381
+ }>;
382
+ redoMutations?: Array<{
383
+ id: string;
384
+ params: Record<string, unknown>;
385
+ }>;
386
+ }
387
+ /** The Univer 1.0.4 fields the undo rebase reads; a forcing test fails if they move (Univer is pinned). */
388
+ declare function undoStacksOf(service: unknown): {
389
+ undo: Map<string, UndoItem[]>;
390
+ redo: Map<string, UndoItem[]>;
391
+ } | null;
392
+ /** §6.9: rebase one stack over a remote op; items that cannot follow are dropped. Returns how many were dropped. */
393
+ declare function rebaseUndoStack(items: UndoItem[], remote: DocsOp): number;
394
+ declare function openDocsOtRoom(o: OpenDocsOtRoomOptions): DocsOtRoom;
395
+
396
+ /**
397
+ * THE DOCUMENT MODEL — the engine of one cloud document per tab.
398
+ *
399
+ * Before this, the document lived only inside each `DocumentEditor`'s Univer
400
+ * instance. A remount (a board tile waking, a tile removed and undone, a route
401
+ * change and back) rebuilt the editor from the SERVER copy, so whatever the
402
+ * last save had not carried yet was gone; two mounts of one document were two
403
+ * editors, each saving full snapshots of its own copy (last write wins), each
404
+ * with its own realtime channel and its own collab room.
405
+ *
406
+ * The document is a record on THE one working-copy primitive
407
+ * (`@ai-matrx/kit/working-copy`, kind `udt_document`,
408
+ * `./documentModels.ts`): its save status, dirty and view count are Redux state
409
+ * (`workingCopies["udt_document:<id>"]`), its one coalesced save is the kind's.
410
+ * What Redux cannot hold — Univer's body — is this engine, registered on the
411
+ * record's session under the same key:
412
+ *
413
+ * - BODY. Every local mutation in one view is replayed into every other view
414
+ * of the document (the same mutation stream collab sends between machines),
415
+ * so two views show one document. A new view boots from the engine's latest
416
+ * state — a live view's snapshot, or the snapshot the last view left behind
417
+ * — never the stale server copy while the record's session is alive.
418
+ * - SIDE EFFECTS, ONCE. The snapshot realtime channel, the collab room, and
419
+ * the pagehide / visibility / beforeunload flush are opened once per
420
+ * document and closed once.
421
+ * - THE EDITOR ITSELF, KEPT. Univer keeps its undo / redo history inside the
422
+ * editor instance, so rebuilding the editor on a remount threw the history
423
+ * away. When the last view leaves, its editor (a Univer instance with its
424
+ * own DOM host) is parked here — off the page, alive — and the next view of
425
+ * the document RE-ATTACHES that same instance to its own container instead
426
+ * of booting a new one (`parkEditor` / `takeParkedEditor`). Undo and redo
427
+ * therefore work across hide / show, a remount and a removed tile undone,
428
+ * for as long as the record's session is warm; `close()` disposes it.
429
+ */
430
+
431
+ type DocumentSnapshotData = Partial<IDocumentData>;
432
+ /** The document's room on the ordered log (COLLAB-DESIGN §6, C3), bound through `startOtRoom`. */
433
+ interface DocumentOtHandle {
434
+ stop: () => void;
435
+ write: (reason: CommitReason) => Promise<boolean>;
436
+ replace: (snapshot: DocumentSnapshotData) => Promise<void>;
437
+ isSaver: () => boolean;
438
+ }
439
+ type DocumentOtFactory = (port: DocsOtPort, onAwareness: (awareness: DocumentAwareness) => void) => Promise<DocumentOtHandle | null>;
440
+ /** Who else is in the document's room (shared by every view of it). */
441
+ interface DocumentAwareness {
442
+ states: Map<number, AwarenessState>;
443
+ selfUid: string;
444
+ }
445
+
446
+ /** `collab.ot_rooms` (§12.2): which documents without a room open one. */
447
+ type OtRoomsKnob = "off" | "new" | "all";
448
+ /** Whether this document opens on the ordered log: its room exists, or the knob opens one. */
449
+ declare function documentUsesOrderedRoom(documentId: string, knob: OtRoomsKnob, isNewDocument: () => Promise<boolean>): Promise<boolean>;
450
+ interface DocumentOtFactoryOptions {
451
+ documentId: string;
452
+ uid: string;
453
+ /** One per open tab; never reused. */
454
+ clientId: string;
455
+ displayName?: string;
456
+ readAntiEntropyIntervalMs?: () => Promise<number>;
457
+ onNotice?: (n: DocsOtNotice) => void;
458
+ onIncident?: (i: OpSessionIncident) => void;
459
+ onStatus?: (s: OpSessionStatus) => void;
460
+ }
461
+ /** The `DocumentModel.startOtRoom` factory for one document. */
462
+ declare function documentOtFactory(o: DocumentOtFactoryOptions): DocumentOtFactory;
463
+
464
+ export { COLLAB_COVERED_KEY, type CollabBase, type CollabMutationInfo, type CollabProviderLike, type CommandServiceLike, DOCS_COLLAB_PROTO, DOCS_TRANSFORM_VERSION, DOC_LOCAL_KEYS, type DocsLocalMutation, type DocsLost, type DocsOp, type DocsOtNotice, type DocsOtPort, type DocsOtRoom, type DocsTransformResult, type DocumentOtFactoryOptions, type OpenDocsOtRoomOptions, type OtRoomsKnob, RICH_TEXT_EDITING, SENDER_VIEW_PARAMS, WorkbookCollabSession, type WorkbookCollabSessionOptions, applyDocsOp, canonicalDocsSnapshot, canonicalDocsString, composeDocsOps, docsStateHash, documentOtFactory, documentUsesOrderedRoom, dropEmptyStyleRetains, invertDocsOp, isCellEditorScratch, isNoopDocsOp, normalizeDocsOp, openDocsOtRoom, rebaseUndoStack, stampCovered, stampDeterministicIds, stripSenderView, takeCovered, transformDocsOp, transformTextX, undoStacksOf };
@@ -1,5 +1,8 @@
1
1
  import * as Y from 'yjs';
2
2
  import { Awareness } from 'y-protocols/awareness';
3
+ import { OpSessionNotice, OpSession, OpTransport, OpPresence, OpSessionIncident, OpSessionStatus, AwarenessState } from '@ai-matrx/data/realtime/collab';
4
+ import { IDocumentData } from '@univerjs/core';
5
+ import { CommitReason } from '@ai-matrx/kit/working-copy';
3
6
 
4
7
  /**
5
8
  * A saved snapshot names the collab-room entries it already holds, so a seat that boots from it never
@@ -210,4 +213,252 @@ declare const SENDER_VIEW_PARAMS: readonly string[];
210
213
  declare function stripSenderView(params: MutationParams): MutationParams;
211
214
  declare function isCellEditorScratch(info: Pick<CollabMutationInfo, "id">): boolean;
212
215
 
213
- export { COLLAB_COVERED_KEY, type CollabBase, type CollabMutationInfo, type CollabProviderLike, type CommandServiceLike, SENDER_VIEW_PARAMS, WorkbookCollabSession, type WorkbookCollabSessionOptions, isCellEditorScratch, stampCovered, stripSenderView, takeCovered };
216
+ type Act = Record<string, any> & {
217
+ t: "i" | "r" | "d";
218
+ len: number;
219
+ };
220
+ /** One `lost` entry: what a transform dropped from either side (R1), for the author's notice. */
221
+ interface DocsLost {
222
+ rule: string;
223
+ side: "insert" | "delete";
224
+ len: number;
225
+ text?: string;
226
+ }
227
+ /** The travelling shape of a Docs JSON op: text-x edits normalised (R1 zones, R5, no oldBody). */
228
+ declare function normalizeDocsOp<T>(op: T): T;
229
+ /** R6: a retain whose style lost every key to the other side is a plain retain. */
230
+ declare function dropEmptyStyleRetains<T>(op: T): T;
231
+ /** x rewritten to apply after y over one text-x body; `side` is x's side. Drops land in `lost` (R1). */
232
+ declare function transformTextX(xIn: Act[], yIn: Act[], side: "left" | "right", lost?: DocsLost[]): Act[];
233
+ /** A Docs operation: an ot-json1 op over the whole Univer snapshot (what `rich-text-editing` carries as `actions`). */
234
+ type DocsOp = unknown[] | null;
235
+ interface DocsTransformResult {
236
+ op: DocsOp;
237
+ lost: DocsLost[];
238
+ }
239
+ /**
240
+ * `T(x, y, side)` for Docs: ot-json1 `transformNoConflict` (never throws on DROP_COLLISION / RM_UNEXPECTED_CONTENT /
241
+ * BLACKHOLE, §3) with our text-x transform registered only for the duration of one call, so Univer's own apply is
242
+ * untouched even when the bundler shares one ot-json1 instance with `@univerjs/core`. R6 runs on the result.
243
+ */
244
+ declare function transformDocsOp(x: DocsOp, y: DocsOp, side: "left" | "right"): DocsTransformResult;
245
+ /** `a` then `b` as one op (TextX is the subtype every reader composes with). */
246
+ declare function composeDocsOps(a: DocsOp, b: DocsOp): DocsOp;
247
+ declare function isNoopDocsOp(op: DocsOp): boolean;
248
+ /** Apply an op to a plain snapshot (tests, the fold). Univer's own JSONX, so TextX applies as the editor applies. */
249
+ declare function applyDocsOp<T>(doc: T, op: DocsOp): T;
250
+ /** The inverse of `op` against the state it applied to. JSONX.invertWithDoc MUTATES its op: invert a copy (C0). */
251
+ declare function invertDocsOp(op: DocsOp, preDoc: unknown): DocsOp;
252
+ /** Part of `proto` (§6.4): Univer 1.0.4 + this transformer, rules R1–R6. */
253
+ declare const DOCS_TRANSFORM_VERSION = "univer-1.0.4+docs-ot-r6";
254
+
255
+ /**
256
+ * The canonical Docs state hash (COLLAB-DESIGN §7.2; C0 item 6, `spikes/c0-collab/lib.mjs`).
257
+ *
258
+ * SHA-256 of the canonical snapshot: keys sorted, empty values dropped, the unit id and seat-local fields
259
+ * removed (revision counter, collab stamps, layout state, selections), adjacent equal text runs merged, split
260
+ * pieces of one custom range or decoration folded, and `paragraphId` dropped (Univer's TextX.apply mints a
261
+ * random one per seat, C0 surprise 3). Header, footer and note bodies get the same rules.
262
+ *
263
+ * Two seats with equal content hash equal; one character more hashes differently. Browser and Node: the
264
+ * digest is Web Crypto's.
265
+ */
266
+ /** Never content: the collab stamps, revision counters, seat-local view state. */
267
+ declare const DOC_LOCAL_KEYS: string[];
268
+ /** The canonical Docs snapshot: content only, keys sorted, runs normalised, local fields removed. */
269
+ declare function canonicalDocsSnapshot(snapshot: unknown): unknown;
270
+ /** The canonical form as the string the hash digests (tests compare these directly). */
271
+ declare const canonicalDocsString: (snapshot: unknown) => string;
272
+ /** SHA-256 hex of the canonical snapshot (§7.2). */
273
+ declare function docsStateHash(snapshot: unknown): Promise<string>;
274
+ /**
275
+ * Ids every seat agrees on for a snapshot that lacks them (C0 surprise 5): a raw snapshot (an import, markdown)
276
+ * loaded on two seats would otherwise get two random `sectionId`s / `paragraphId`s. Missing ids become
277
+ * positional ones; ids already present are kept. Applied to every snapshot a room loads.
278
+ */
279
+ declare function stampDeterministicIds<T>(snapshot: T): T;
280
+
281
+ /**
282
+ * The Docs co-editing room on the ordered log (COLLAB-DESIGN §6, unit C3): the `OpDocumentAdapter` for
283
+ * `openOpSession` over a document's editors, plus saves, restore and the saver choice.
284
+ *
285
+ * - CAPTURE (§6.3). The document model hands over every local `doc.mutation.rich-text-editing` with the applied
286
+ * params (the handler's own rewrite, C0 surprise 6), the pre-state cloned in `beforeCommandExecuted`, and the
287
+ * outermost command id. `onlyLocal` / `fromCollab` never arrive here (the model filters them). Only `segmentId`
288
+ * and `actions` travel; the inverse is computed from the APPLIED actions on a COPY (`invertWithDoc` mutates its
289
+ * op). Each mutation goes to the session as it happens, so a remote apply can never land between a local
290
+ * mutation and its hand-over; the command id rides along as the change's label.
291
+ * - APPLY (§6.7). `syncExecuteCommand(id, { unitId: own, segmentId, actions, textRanges: null,
292
+ * noNeedSetTextRange: true }, { onlyLocal: true, fromCollab: true })` through the model (every view of the
293
+ * document in this tab). A throw, `false`, or actions the handler emptied fail the apply: the session rebuilds,
294
+ * never skips. While an IME composition is open, remote applies QUEUE (acks continue); a local op captured
295
+ * meanwhile is rebased over the queue (`L' = T(L, Q, 'left')`, `Q' = T(Q, L, 'right')`), and the queue drains
296
+ * at `compositionend`.
297
+ * - UNDO (§6.9). Each applied remote op rebases every stored undo and redo item of this seat
298
+ * (`T(item, R1, 'left')`); `textRanges` and `layoutState` are stripped from rebased items; an item whose rebase
299
+ * is a barrier, throws or comes back empty is dropped. Undo therefore only ever reverts this person's changes.
300
+ * - SAVES (§7.3). Only a quiet seat whose hash matches every peer at its rev saves, and only the chosen saver
301
+ * (the lowest client id among seats publishing a quiet hash) saves on its own; a manual save always tries.
302
+ * - RESTORE (§20). A whole-document replace goes through `collab_replace`: the snapshot is loaded into this seat's
303
+ * editor first, so the stored snapshot and its hash are the Univer-normalised form every seat will load.
304
+ */
305
+
306
+ declare const RICH_TEXT_EDITING = "doc.mutation.rich-text-editing";
307
+ /**
308
+ * The room's protocol (§6.4): Univer 1.0.4 + transformer `DOCS_TRANSFORM_VERSION`. Any Univer or transformer
309
+ * change raises it; a room at another proto opens read-only for this build.
310
+ */
311
+ declare const DOCS_COLLAB_PROTO = 1;
312
+ /** One local mutation as the document model hands it over. */
313
+ interface DocsLocalMutation {
314
+ id: string;
315
+ params: unknown;
316
+ /** The document before this mutation (cloned in `beforeCommandExecuted`), when the view could supply it. */
317
+ pre: unknown | null;
318
+ /** The outermost command this mutation belongs to (the depth tracker with its try/finally guard). */
319
+ cmd: string | null;
320
+ /** Whether the mutation changes what a snapshot stores. */
321
+ content: boolean;
322
+ }
323
+ /** What the document model lends the room (implemented by `DocumentModel`). */
324
+ interface DocsOtPort {
325
+ onLocal(listener: (m: DocsLocalMutation) => void): () => void;
326
+ /** Apply one committed mutation to every view of the document; throws when any view could not. */
327
+ applyRemote(id: string, params: Record<string, unknown>): void;
328
+ /** The document as the editor holds it now (`doc.save()`), or null when no view is mounted. */
329
+ snapshot(): unknown | null;
330
+ /** Replace the document in every view (and the kept editor). */
331
+ load(snapshot: unknown): void;
332
+ /** Univer's undo/redo service of each mounted view and the unit it holds. */
333
+ undoTargets(): Array<{
334
+ service: unknown;
335
+ unitId: string;
336
+ }>;
337
+ /** Ask for a save (the record's one coalesced save). */
338
+ markDirty(): void;
339
+ }
340
+ type DocsOtNotice = OpSessionNotice<DocsOp> | {
341
+ kind: "not_shared";
342
+ mutationId: string;
343
+ } | {
344
+ kind: "not_live";
345
+ };
346
+ interface OpenDocsOtRoomOptions {
347
+ port: DocsOtPort;
348
+ transport: OpTransport;
349
+ clientId: string;
350
+ presence?: OpPresence;
351
+ /** `collab_replace` for this room (restore, whole import). */
352
+ replace: (req: {
353
+ snapshot: unknown;
354
+ stateHash: string;
355
+ clientId: string;
356
+ }) => Promise<{
357
+ rev: number;
358
+ ref: string;
359
+ }>;
360
+ onNotice?: (n: DocsOtNotice) => void;
361
+ onIncident?: (i: OpSessionIncident) => void;
362
+ onStatus?: (s: OpSessionStatus) => void;
363
+ /** Watch IME composition here (default: `document`). */
364
+ compositionTarget?: Pick<Document, "addEventListener" | "removeEventListener"> | null;
365
+ }
366
+ interface DocsOtRoom {
367
+ session: OpSession<DocsOp>;
368
+ /** The record's save: a checkpoint at a quiet point (§7.3). False when this seat does not write now. */
369
+ write(reason: "manual" | string): Promise<boolean>;
370
+ /** Restore / whole import through `collab_replace` (§20). */
371
+ replace(snapshot: unknown): Promise<void>;
372
+ /** This seat is the one that saves on its own. */
373
+ isSaver(): boolean;
374
+ stop(): void;
375
+ }
376
+ interface UndoItem {
377
+ unitID?: string;
378
+ undoMutations?: Array<{
379
+ id: string;
380
+ params: Record<string, unknown>;
381
+ }>;
382
+ redoMutations?: Array<{
383
+ id: string;
384
+ params: Record<string, unknown>;
385
+ }>;
386
+ }
387
+ /** The Univer 1.0.4 fields the undo rebase reads; a forcing test fails if they move (Univer is pinned). */
388
+ declare function undoStacksOf(service: unknown): {
389
+ undo: Map<string, UndoItem[]>;
390
+ redo: Map<string, UndoItem[]>;
391
+ } | null;
392
+ /** §6.9: rebase one stack over a remote op; items that cannot follow are dropped. Returns how many were dropped. */
393
+ declare function rebaseUndoStack(items: UndoItem[], remote: DocsOp): number;
394
+ declare function openDocsOtRoom(o: OpenDocsOtRoomOptions): DocsOtRoom;
395
+
396
+ /**
397
+ * THE DOCUMENT MODEL — the engine of one cloud document per tab.
398
+ *
399
+ * Before this, the document lived only inside each `DocumentEditor`'s Univer
400
+ * instance. A remount (a board tile waking, a tile removed and undone, a route
401
+ * change and back) rebuilt the editor from the SERVER copy, so whatever the
402
+ * last save had not carried yet was gone; two mounts of one document were two
403
+ * editors, each saving full snapshots of its own copy (last write wins), each
404
+ * with its own realtime channel and its own collab room.
405
+ *
406
+ * The document is a record on THE one working-copy primitive
407
+ * (`@ai-matrx/kit/working-copy`, kind `udt_document`,
408
+ * `./documentModels.ts`): its save status, dirty and view count are Redux state
409
+ * (`workingCopies["udt_document:<id>"]`), its one coalesced save is the kind's.
410
+ * What Redux cannot hold — Univer's body — is this engine, registered on the
411
+ * record's session under the same key:
412
+ *
413
+ * - BODY. Every local mutation in one view is replayed into every other view
414
+ * of the document (the same mutation stream collab sends between machines),
415
+ * so two views show one document. A new view boots from the engine's latest
416
+ * state — a live view's snapshot, or the snapshot the last view left behind
417
+ * — never the stale server copy while the record's session is alive.
418
+ * - SIDE EFFECTS, ONCE. The snapshot realtime channel, the collab room, and
419
+ * the pagehide / visibility / beforeunload flush are opened once per
420
+ * document and closed once.
421
+ * - THE EDITOR ITSELF, KEPT. Univer keeps its undo / redo history inside the
422
+ * editor instance, so rebuilding the editor on a remount threw the history
423
+ * away. When the last view leaves, its editor (a Univer instance with its
424
+ * own DOM host) is parked here — off the page, alive — and the next view of
425
+ * the document RE-ATTACHES that same instance to its own container instead
426
+ * of booting a new one (`parkEditor` / `takeParkedEditor`). Undo and redo
427
+ * therefore work across hide / show, a remount and a removed tile undone,
428
+ * for as long as the record's session is warm; `close()` disposes it.
429
+ */
430
+
431
+ type DocumentSnapshotData = Partial<IDocumentData>;
432
+ /** The document's room on the ordered log (COLLAB-DESIGN §6, C3), bound through `startOtRoom`. */
433
+ interface DocumentOtHandle {
434
+ stop: () => void;
435
+ write: (reason: CommitReason) => Promise<boolean>;
436
+ replace: (snapshot: DocumentSnapshotData) => Promise<void>;
437
+ isSaver: () => boolean;
438
+ }
439
+ type DocumentOtFactory = (port: DocsOtPort, onAwareness: (awareness: DocumentAwareness) => void) => Promise<DocumentOtHandle | null>;
440
+ /** Who else is in the document's room (shared by every view of it). */
441
+ interface DocumentAwareness {
442
+ states: Map<number, AwarenessState>;
443
+ selfUid: string;
444
+ }
445
+
446
+ /** `collab.ot_rooms` (§12.2): which documents without a room open one. */
447
+ type OtRoomsKnob = "off" | "new" | "all";
448
+ /** Whether this document opens on the ordered log: its room exists, or the knob opens one. */
449
+ declare function documentUsesOrderedRoom(documentId: string, knob: OtRoomsKnob, isNewDocument: () => Promise<boolean>): Promise<boolean>;
450
+ interface DocumentOtFactoryOptions {
451
+ documentId: string;
452
+ uid: string;
453
+ /** One per open tab; never reused. */
454
+ clientId: string;
455
+ displayName?: string;
456
+ readAntiEntropyIntervalMs?: () => Promise<number>;
457
+ onNotice?: (n: DocsOtNotice) => void;
458
+ onIncident?: (i: OpSessionIncident) => void;
459
+ onStatus?: (s: OpSessionStatus) => void;
460
+ }
461
+ /** The `DocumentModel.startOtRoom` factory for one document. */
462
+ declare function documentOtFactory(o: DocumentOtFactoryOptions): DocumentOtFactory;
463
+
464
+ export { COLLAB_COVERED_KEY, type CollabBase, type CollabMutationInfo, type CollabProviderLike, type CommandServiceLike, DOCS_COLLAB_PROTO, DOCS_TRANSFORM_VERSION, DOC_LOCAL_KEYS, type DocsLocalMutation, type DocsLost, type DocsOp, type DocsOtNotice, type DocsOtPort, type DocsOtRoom, type DocsTransformResult, type DocumentOtFactoryOptions, type OpenDocsOtRoomOptions, type OtRoomsKnob, RICH_TEXT_EDITING, SENDER_VIEW_PARAMS, WorkbookCollabSession, type WorkbookCollabSessionOptions, applyDocsOp, canonicalDocsSnapshot, canonicalDocsString, composeDocsOps, docsStateHash, documentOtFactory, documentUsesOrderedRoom, dropEmptyStyleRetains, invertDocsOp, isCellEditorScratch, isNoopDocsOp, normalizeDocsOp, openDocsOtRoom, rebaseUndoStack, stampCovered, stampDeterministicIds, stripSenderView, takeCovered, transformDocsOp, transformTextX, undoStacksOf };