@fileverse-dev/ddoc 6.0.0-drop-in.2 → 6.0.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.
Files changed (52) hide show
  1. package/README.md +18 -89
  2. package/dist/ddoc-persistence-C2ay3lLb.mjs +370 -0
  3. package/dist/headless-editor-utils.es.js +1 -1
  4. package/dist/{index-8MQH4TSF.mjs → index-D9uM5S2Y.mjs} +30 -30
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.es.js +12496 -12445
  7. package/dist/{markdown-source-pane-CXO7aIOp.mjs → markdown-source-pane-DWliRVLH.mjs} +287 -287
  8. package/dist/package/components/editor-toolbar.d.ts +1 -2
  9. package/dist/package/components/fullscreen-toolbar.d.ts +2 -2
  10. package/dist/package/components/mobile-toolbar.d.ts +1 -2
  11. package/dist/package/components/tabs/document-mobile-tab-panel.d.ts +1 -3
  12. package/dist/package/components/tabs/document-tabs-sidebar.d.ts +1 -2
  13. package/dist/package/components/toc/document-outline.d.ts +1 -1
  14. package/dist/package/components/toc/toc.d.ts +1 -1
  15. package/dist/package/components/toc/types.d.ts +0 -7
  16. package/dist/package/hooks/use-doc-schema-version.d.ts +3 -1
  17. package/dist/package/hooks/use-export-headless-editor-content.d.ts +6 -2
  18. package/dist/package/hooks/use-focus-mode.d.ts +3 -1
  19. package/dist/package/hooks/use-headless-editor.d.ts +12 -4
  20. package/dist/package/hooks/use-tab-editor.d.ts +1 -4
  21. package/dist/package/hooks/use-tab-manager.d.ts +3 -1
  22. package/dist/package/hooks/use-yjs-setup.d.ts +7 -1
  23. package/dist/package/persistence/compaction.d.ts +27 -0
  24. package/dist/package/persistence/content-fingerprint.d.ts +27 -0
  25. package/dist/package/persistence/ddoc-persistence.d.ts +96 -0
  26. package/dist/package/persistence/index.d.ts +2 -0
  27. package/dist/package/persistence/provider-lifecycle.d.ts +37 -0
  28. package/dist/package/sync-local/SyncManager.d.ts +8 -1
  29. package/dist/package/sync-local/server-clock.d.ts +26 -0
  30. package/dist/package/sync-local/socketClient.d.ts +1 -1
  31. package/dist/package/sync-local/types/index.d.ts +2 -0
  32. package/dist/package/types.d.ts +89 -43
  33. package/dist/package/use-ddoc-editor.d.ts +1 -3
  34. package/dist/package/utils/focus-mode-canvas-click.d.ts +2 -0
  35. package/dist/package/utils/responsive.d.ts +1 -3
  36. package/dist/package/utils/schema-version.d.ts +2 -0
  37. package/dist/package/utils/storage-adapter.d.ts +30 -0
  38. package/dist/persistence.d.ts +1 -0
  39. package/dist/persistence.es.js +10 -0
  40. package/dist/{split-view-toolbar-BnfwwwTs.mjs → split-view-toolbar-CbVFNI6B.mjs} +1 -1
  41. package/dist/style.css +1 -1
  42. package/dist/tab-utils-CAcIp-Dt.mjs +313 -0
  43. package/dist/{use-headless-editor-Buqsz3UI.mjs → use-headless-editor-C1g_Z67w.mjs} +11127 -11430
  44. package/package.json +7 -2
  45. package/tailwind.preset.cjs +0 -42
  46. package/dist/package/components/editor-navbar.d.ts +0 -12
  47. package/dist/package/hooks/use-canvas-metrics.d.ts +0 -16
  48. package/dist/package/hooks/use-controllable-state.d.ts +0 -3
  49. package/dist/package/hooks/use-editor-media-query.d.ts +0 -4
  50. package/dist/package/utils/container-query-support.d.ts +0 -1
  51. package/dist/package/utils/editor-width-store.d.ts +0 -6
  52. package/dist/package/utils/stats-publisher.d.ts +0 -8
@@ -4,7 +4,7 @@ import { FontDescriptor, IpfsImageFetchPayload, IpfsImageUploadResponse } from '
4
4
  import { Tab } from './tabs/utils/tab-utils';
5
5
 
6
6
  import * as Y from 'yjs';
7
- declare const MemoizedTiptapToolBar: React.MemoExoticComponent<({ editor, onError, isPresentationMode, setIsPresentationMode, enableCollaboration, zoomLevel, setZoomLevel, isNavbarVisible, setIsNavbarVisible, showNavbarToggle, ipfsImageUploadFn, onMarkdownExport, onMarkdownImport, onPdfExport, onHtmlExport, onTxtExport, onOdtExport, onDocxImport, ipfsImageFetchFn, fetchV1ImageFn, isConnected, tabs, ydoc, onRegisterExportTrigger, toggleFocusMode, isSplitView, onToggleSplitView, fonts: consumerFonts, renderToolbarLeading, }: {
7
+ declare const MemoizedTiptapToolBar: React.MemoExoticComponent<({ editor, onError, isPresentationMode, setIsPresentationMode, enableCollaboration, zoomLevel, setZoomLevel, isNavbarVisible, setIsNavbarVisible, ipfsImageUploadFn, onMarkdownExport, onMarkdownImport, onPdfExport, onHtmlExport, onTxtExport, onOdtExport, onDocxImport, ipfsImageFetchFn, fetchV1ImageFn, isConnected, tabs, ydoc, onRegisterExportTrigger, toggleFocusMode, isSplitView, onToggleSplitView, fonts: consumerFonts, renderToolbarLeading, }: {
8
8
  editor: Editor | null;
9
9
  onError?: (errorString: string) => void;
10
10
  isPresentationMode?: boolean;
@@ -14,7 +14,6 @@ declare const MemoizedTiptapToolBar: React.MemoExoticComponent<({ editor, onErro
14
14
  setZoomLevel: (zoom: string) => void;
15
15
  isNavbarVisible: boolean;
16
16
  setIsNavbarVisible: React.Dispatch<React.SetStateAction<boolean>>;
17
- showNavbarToggle?: boolean;
18
17
  ipfsImageUploadFn?: (file: File) => Promise<IpfsImageUploadResponse>;
19
18
  onMarkdownExport?: () => void;
20
19
  onMarkdownImport?: () => void;
@@ -4,8 +4,8 @@ import { DdocProps } from '../types';
4
4
  type FullScreenToolbarProps = {
5
5
  dropdownOpen: boolean;
6
6
  setDropdownOpen: Dispatch<SetStateAction<boolean>>;
7
- zoomLevel: string;
8
- setZoomLevel: Dispatch<SetStateAction<string>>;
7
+ zoomLevel: DdocProps['zoomLevel'];
8
+ setZoomLevel: DdocProps['setZoomLevel'];
9
9
  showTOC: DdocProps['showTOC'];
10
10
  setShowTOC: DdocProps['setShowTOC'];
11
11
  toggleFocusMode: () => Promise<void>;
@@ -2,13 +2,12 @@ import { default as React } from 'react';
2
2
  import { Editor } from '@tiptap/react';
3
3
  import { FontDescriptor, IpfsImageFetchPayload, IpfsImageUploadResponse } from '../types';
4
4
 
5
- declare const MemoizedMobileToolbar: React.MemoExoticComponent<({ editor, onError, isKeyboardVisible, isNavbarVisible, setIsNavbarVisible, showNavbarToggle, ipfsImageUploadFn, ipfsImageFetchFn, fetchV1ImageFn, fonts, }: {
5
+ declare const MemoizedMobileToolbar: React.MemoExoticComponent<({ editor, onError, isKeyboardVisible, isNavbarVisible, setIsNavbarVisible, ipfsImageUploadFn, ipfsImageFetchFn, fetchV1ImageFn, fonts, }: {
6
6
  editor: Editor | null;
7
7
  onError?: (errorString: string) => void;
8
8
  isKeyboardVisible: boolean;
9
9
  isNavbarVisible: boolean;
10
10
  setIsNavbarVisible: React.Dispatch<React.SetStateAction<boolean>>;
11
- showNavbarToggle?: boolean;
12
11
  ipfsImageUploadFn?: (file: File) => Promise<IpfsImageUploadResponse>;
13
12
  ipfsImageFetchFn?: (_data: IpfsImageFetchPayload) => Promise<{
14
13
  url: string;
@@ -25,7 +25,5 @@ export interface DocumentMobileTabPanelProps {
25
25
  isVersionHistoryMode: boolean;
26
26
  isConnected?: boolean;
27
27
  isFocusMode?: boolean;
28
- layout?: DocumentOutlineProps['layout'];
29
- tabPanelSlot?: HTMLElement | null;
30
28
  }
31
- export declare const DocumentMobileTabPanel: ({ tabs, activeTabId, setActiveTabId, editor, items, setItems, orientation, renameTab, createTab, duplicateTab, orderTab, deleteTab, tabCommentCounts, isPreviewMode, tabConfig, isVersionHistoryMode, isConnected, isFocusMode, layout, tabPanelSlot, }: DocumentMobileTabPanelProps) => import("react/jsx-runtime").JSX.Element | null;
29
+ export declare const DocumentMobileTabPanel: ({ tabs, activeTabId, setActiveTabId, editor, items, setItems, orientation, renameTab, createTab, duplicateTab, orderTab, deleteTab, tabCommentCounts, isPreviewMode, tabConfig, isVersionHistoryMode, isConnected, isFocusMode, }: DocumentMobileTabPanelProps) => import("react/jsx-runtime").JSX.Element;
@@ -32,10 +32,9 @@ export interface DocumentTabsSidebarProps {
32
32
  deleteTab?: (tabId: string) => void;
33
33
  isConnected?: boolean;
34
34
  isFocusMode?: boolean;
35
- layout?: DocumentOutlineProps['layout'];
36
35
  }
37
36
  export declare const DocumentTabsSidebar: ({ tabSectionContainer, ...rest }: DocumentTabsSidebarProps) => import("react/jsx-runtime").JSX.Element | null;
38
- export declare const TabSidebar: ({ tabs, activeTabId, setActiveTabId, showTOC, setShowTOC, hasToC, isPreviewMode, editor, items, setItems, orientation, createTab, renameTab, duplicateTab, orderTab, tabCommentCounts, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode, layout, }: DocumentTabsSidebarProps) => import("react/jsx-runtime").JSX.Element;
37
+ export declare const TabSidebar: ({ tabs, activeTabId, setActiveTabId, showTOC, setShowTOC, hasToC, isPreviewMode, editor, items, setItems, orientation, createTab, renameTab, duplicateTab, orderTab, tabCommentCounts, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode, }: DocumentTabsSidebarProps) => import("react/jsx-runtime").JSX.Element;
39
38
  export declare const DdocTab: ({ tab, tabIndex, tabCount, handleEmojiChange, handleNameChange, onClick, editor, tocItem, setTocItems, orientation, activeTabId, duplicateTab, commentCount, moveTabUp, moveTabDown, isPreviewMode, isVersionHistoryMode, tabConfig, onDelete, showOutline, onShowOutlineChange, isConnected, }: {
40
39
  tab: Tab;
41
40
  tabIndex: number;
@@ -1,3 +1,3 @@
1
1
  import { DocumentOutlineProps } from './types';
2
2
 
3
- export declare const DocumentOutline: ({ editor, hasToC, items, setItems, showTOC, setShowTOC, isPreviewMode, orientation, tabs, setTabs, activeTabId, setActiveTabId, createTab, renameTab, duplicateTab, orderTab, ydoc, tabCommentCounts, tabSectionContainer, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode, layout, tabPanelSlot, }: DocumentOutlineProps) => import("react/jsx-runtime").JSX.Element;
3
+ export declare const DocumentOutline: ({ editor, hasToC, items, setItems, showTOC, setShowTOC, isPreviewMode, orientation, tabs, setTabs, activeTabId, setActiveTabId, createTab, renameTab, duplicateTab, orderTab, ydoc, tabCommentCounts, tabSectionContainer, isVersionHistoryMode, tabConfig, deleteTab, isConnected, isFocusMode, }: DocumentOutlineProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { ToCProps, ToCItemProps } from './types';
2
2
 
3
- export declare const ToCItem: import('react').MemoExoticComponent<({ item, onItemClick, index, orientation, isMobile, isWide, }: ToCItemProps) => import("react/jsx-runtime").JSX.Element>;
3
+ export declare const ToCItem: import('react').MemoExoticComponent<({ item, onItemClick, index, orientation, }: ToCItemProps) => import("react/jsx-runtime").JSX.Element>;
4
4
  export declare const ToCEmptyState: import('react').MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
5
5
  export declare const ToC: import('react').MemoExoticComponent<({ items, editor, setItems, orientation }: ToCProps) => import("react/jsx-runtime").JSX.Element>;
@@ -22,10 +22,6 @@ export type ToCItemProps = {
22
22
  onItemRemove: (e: React.MouseEvent, id: string) => void;
23
23
  index: number;
24
24
  orientation?: 'portrait' | 'landscape';
25
- /** At or below 1280px: the item is in the mobile list. */
26
- isMobile?: boolean;
27
- /** At or above 1280px: the item takes the rail's width caps. */
28
- isWide?: boolean;
29
25
  };
30
26
  export interface DocumentOutlineProps {
31
27
  editor: Editor;
@@ -58,7 +54,4 @@ export interface DocumentOutlineProps {
58
54
  deleteTab?: (tabId: string) => void;
59
55
  isConnected?: boolean;
60
56
  isFocusMode?: boolean;
61
- /** 'contained': sized from the editor canvas. Only DdocEditor passes it. */
62
- layout?: 'viewport' | 'contained';
63
- tabPanelSlot?: HTMLElement | null;
64
57
  }
@@ -1,11 +1,13 @@
1
+ import { SchemaStampGuardEvent } from '../types';
1
2
  import * as Y from 'yjs';
2
3
  interface UseDocSchemaVersionArgs {
3
4
  ydoc: Y.Doc;
4
5
  isNewDdoc: boolean;
5
6
  isContentResolved: boolean;
6
7
  preferredSchemaVersion?: number;
8
+ onSchemaStampGuard?: (event: SchemaStampGuardEvent) => void;
7
9
  }
8
- export declare const useDocSchemaVersion: ({ ydoc, isNewDdoc, isContentResolved, preferredSchemaVersion, }: UseDocSchemaVersionArgs) => {
10
+ export declare const useDocSchemaVersion: ({ ydoc, isNewDdoc, isContentResolved, preferredSchemaVersion, onSchemaStampGuard, }: UseDocSchemaVersionArgs) => {
9
11
  docSchemaVersion: number;
10
12
  isSchemaUnsupported: boolean;
11
13
  };
@@ -33,6 +33,10 @@ export declare const useExportHeadlessEditorContent: (props?: UseHeadlessEditorP
33
33
  downloadContentAsTxt: (content: string | string[] | JSONContent, title: string) => Promise<void>;
34
34
  mergeYjsUpdates: (contents: string[]) => string;
35
35
  handleMarkdownContent: typeof import('../extensions/mardown-paste-handler').handleMarkdownContent;
36
- getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse>) => Promise<string | null>;
37
- getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse>) => Promise<string | null>;
36
+ getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse>, options?: {
37
+ schemaVersion?: number;
38
+ }) => Promise<string | null>;
39
+ getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<import('../types').IpfsImageUploadResponse>, options?: {
40
+ schemaVersion?: number;
41
+ }) => Promise<string | null>;
38
42
  };
@@ -1,7 +1,9 @@
1
1
  type UseFocusModeOptions = {
2
- /** @deprecated Use onFocusModeChange. Still fires on every change. */
2
+ /** Notify callback — fires on every toggle in both modes (existing API). */
3
3
  onFocusMode?: (isFocusMode: boolean) => void;
4
+ /** Controlled value (D6). Omit for the legacy internal-state behavior. */
4
5
  isFocusMode?: boolean;
6
+ /** Controlled setter — receives the next value instead of internal mutation. */
5
7
  onFocusModeChange?: (isFocusMode: boolean) => void;
6
8
  };
7
9
  export declare const useFocusMode: ({ onFocusMode, isFocusMode: controlledValue, onFocusModeChange, }?: UseFocusModeOptions) => {
@@ -53,8 +53,12 @@ export declare const createHeadlessEditorRuntime: (props?: UseHeadlessEditorProp
53
53
  downloadContentAsTxt: (content: string | string[] | JSONContent, title: string) => Promise<void>;
54
54
  mergeYjsUpdates: (contents: string[]) => string;
55
55
  handleMarkdownContent: typeof handleMarkdownContent;
56
- getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>) => Promise<string | null>;
57
- getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>) => Promise<string | null>;
56
+ getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
57
+ schemaVersion?: number;
58
+ }) => Promise<string | null>;
59
+ getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
60
+ schemaVersion?: number;
61
+ }) => Promise<string | null>;
58
62
  };
59
63
  export declare const useHeadlessEditor: (props?: UseHeadlessEditorProps) => {
60
64
  setContent: (initialContent: string | string[] | JSONContent, editor: Editor, ydoc: Y.Doc) => void;
@@ -76,6 +80,10 @@ export declare const useHeadlessEditor: (props?: UseHeadlessEditorProps) => {
76
80
  downloadContentAsTxt: (content: string | string[] | JSONContent, title: string) => Promise<void>;
77
81
  mergeYjsUpdates: (contents: string[]) => string;
78
82
  handleMarkdownContent: typeof handleMarkdownContent;
79
- getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>) => Promise<string | null>;
80
- getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>) => Promise<string | null>;
83
+ getYjsContentFromMarkdown: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
84
+ schemaVersion?: number;
85
+ }) => Promise<string | null>;
86
+ getYjsContentFromDocx: (file: File, ipfsImageUploadFn: (file: File) => Promise<IpfsImageUploadResponse>, options?: {
87
+ schemaVersion?: number;
88
+ }) => Promise<string | null>;
81
89
  };
@@ -4,14 +4,12 @@ import { AnyExtension, Editor } from '@tiptap/react';
4
4
  import { CommentAnchor } from '../extensions/comment/comment-decoration-plugin';
5
5
  import { DBlockRuntimeStateRef } from '../extensions/d-block/dblock-runtime';
6
6
  import { ToCItemType } from '../components/toc/types';
7
- import { EditorWidthStore } from '../utils/editor-width-store';
8
7
  import { CollabConnectionConfig, CollaborationProps } from '../sync-local/types';
9
8
 
10
9
  import * as Y from 'yjs';
11
10
  export declare const TAB_EDITOR_ATTRIBUTES: Record<string, string>;
12
11
  interface UseTabEditorArgs {
13
12
  ydoc: Y.Doc;
14
- editorWidthStore?: EditorWidthStore;
15
13
  isVersionMode?: boolean;
16
14
  hasTabState?: boolean;
17
15
  versionId?: string;
@@ -38,7 +36,6 @@ interface UseTabEditorArgs {
38
36
  setWordCount?: DdocProps['setWordCount'];
39
37
  setSelectedWordCount?: DdocProps['setSelectedWordCount'];
40
38
  setPageCount?: DdocProps['setPageCount'];
41
- onStatsChange?: DdocProps['onStatsChange'];
42
39
  setIsContentLoading: Dispatch<SetStateAction<boolean>>;
43
40
  setIsCollabContentLoading: Dispatch<SetStateAction<boolean>>;
44
41
  unFocused?: boolean;
@@ -61,7 +58,7 @@ interface UseTabEditorArgs {
61
58
  dBlockRuntimeStateRef?: DBlockRuntimeStateRef;
62
59
  docSchemaVersion?: number;
63
60
  }
64
- export declare const useTabEditor: ({ ydoc, editorWidthStore, isVersionMode, hasTabState, versionId, isPreviewMode, viewerMode, initialContent, collaboration, isReady, awareness, disableInlineComment, isFocusMode, onCommentInteraction, onError, ipfsImageUploadFn, metadataProxyUrl, onCopyHeadingLink, ipfsImageFetchFn, fetchV1ImageFn, isConnected, activeModel, maxTokens, isAIAgentEnabled, setCharacterCount, setWordCount, setSelectedWordCount, setPageCount, onStatsChange, setIsContentLoading, setIsCollabContentLoading, unFocused, zoomLevel, isPresentationMode, onInvalidContentError, ignoreCorruptedData, onConnect, onDisconnect, isIndexeddbSynced, hasCollabContentInitialised, initialiseYjsIndexedDbProvider, externalExtensions, isContentLoading, activeTabId, tabIds, theme, editorRef, initialCommentAnchors, dBlockRuntimeStateRef, docSchemaVersion, }: UseTabEditorArgs) => {
61
+ export declare const useTabEditor: ({ ydoc, isVersionMode, hasTabState, versionId, isPreviewMode, viewerMode, initialContent, collaboration, isReady, awareness, disableInlineComment, isFocusMode, onCommentInteraction, onError, ipfsImageUploadFn, metadataProxyUrl, onCopyHeadingLink, ipfsImageFetchFn, fetchV1ImageFn, isConnected, activeModel, maxTokens, isAIAgentEnabled, setCharacterCount, setWordCount, setSelectedWordCount, setPageCount, setIsContentLoading, setIsCollabContentLoading, unFocused, zoomLevel, isPresentationMode, onInvalidContentError, ignoreCorruptedData, onConnect, onDisconnect, isIndexeddbSynced, hasCollabContentInitialised, initialiseYjsIndexedDbProvider, externalExtensions, isContentLoading, activeTabId, tabIds, theme, editorRef, initialCommentAnchors, dBlockRuntimeStateRef, docSchemaVersion, }: UseTabEditorArgs) => {
65
62
  editor: Editor | null;
66
63
  cachedEditorEntries: import('./use-tab-editor-cache').CachedTabEditorRenderEntry[];
67
64
  ref: import('react').RefObject<HTMLDivElement>;
@@ -7,6 +7,8 @@ interface UseTabManagerArgs {
7
7
  initialContent: DdocProps['initialContent'];
8
8
  enableCollaboration?: boolean;
9
9
  isDDocOwner: boolean;
10
+ /** Overrides the derived "brand-new document" (see DdocProps.isNewDocument). */
11
+ isNewDocument?: boolean;
10
12
  createDefaultTabIfMissing: boolean;
11
13
  shouldSyncActiveTab: boolean;
12
14
  defaultTabId?: string;
@@ -16,7 +18,7 @@ interface UseTabManagerArgs {
16
18
  flushPendingUpdate?: () => void;
17
19
  }
18
20
  export declare const getNewTabId: () => string;
19
- export declare const useTabManager: ({ ydoc, initialContent, enableCollaboration, isDDocOwner, createDefaultTabIfMissing, shouldSyncActiveTab, defaultTabId, preferFirstTabOnInit, onVersionHistoryActiveTabChange, flushPendingUpdate, getEditor, }: UseTabManagerArgs) => {
21
+ export declare const useTabManager: ({ ydoc, initialContent, enableCollaboration, isDDocOwner, isNewDocument, createDefaultTabIfMissing, shouldSyncActiveTab, defaultTabId, preferFirstTabOnInit, onVersionHistoryActiveTabChange, flushPendingUpdate, getEditor, }: UseTabManagerArgs) => {
20
22
  tabs: Tab[];
21
23
  hasTabState: boolean;
22
24
  activeTabId: string;
@@ -3,13 +3,19 @@ import { DdocProps } from '../types';
3
3
  import * as Y from 'yjs';
4
4
  interface UseYjsSetupArgs {
5
5
  onChange?: DdocProps['onChange'];
6
+ onLocalChange?: DdocProps['onLocalChange'];
6
7
  enableIndexeddbSync?: boolean;
7
8
  ddocId?: string;
8
9
  collaboration?: CollaborationProps;
9
10
  onCollaboratorChange?: DdocProps['onCollaboratorChange'];
10
11
  onIndexedDbError?: (error: Error) => void;
12
+ /**
13
+ * How long to wait for y-indexeddb before checking whether the store is
14
+ * corrupt. Internal; tests shorten it.
15
+ */
16
+ indexeddbStallCheckMs?: number;
11
17
  }
12
- export declare const useYjsSetup: ({ onChange, enableIndexeddbSync, ddocId, collaboration, onCollaboratorChange, onIndexedDbError, }: UseYjsSetupArgs) => {
18
+ export declare const useYjsSetup: ({ onChange, onLocalChange, enableIndexeddbSync, ddocId, collaboration, onCollaboratorChange, onIndexedDbError, indexeddbStallCheckMs, }: UseYjsSetupArgs) => {
13
19
  ydoc: Y.Doc;
14
20
  onConnect: (connectConfig: import('../types').CollabConnectionConfig) => void;
15
21
  onDisconnect: () => void;
@@ -0,0 +1,27 @@
1
+ import { IndexeddbPersistence } from 'y-indexeddb';
2
+
3
+ /**
4
+ * Below this many already-replayed rows the store is left alone. A normal
5
+ * open leaves two or three (the previous compacted row, the last session's
6
+ * edits and y-indexeddb's attach row), so rewriting the full doc then would
7
+ * buy little.
8
+ */
9
+ export declare const DEFAULT_COMPACTION_MIN_ROWS = 3;
10
+ /** Is this one of compaction's own transactions? */
11
+ export declare const isCompactionTransaction: (transaction: IDBTransaction) => boolean;
12
+ /**
13
+ * y-indexeddb appends a copy of the doc every time a provider attaches, and
14
+ * only trims after 500 rows. The editor applies the consumer's initialContent
15
+ * before attaching, so each open stores a FULL copy of the doc and every load
16
+ * replays them all.
17
+ *
18
+ * During sync y-indexeddb applies every stored row to the doc and then sets
19
+ * `_dbref` to one past the last key. Every row below `_dbref` is therefore
20
+ * already in `persistence.doc`, so those rows are replaced with a single
21
+ * `encodeStateAsUpdate(doc)` without reading or merging them. Rows at or above
22
+ * `_dbref` (later edits, writes from another tab) are never touched.
23
+ *
24
+ * Runs in ONE readwrite transaction: any failure rolls back to the original
25
+ * rows. Returns the number of rows replaced (0 when nothing was compacted).
26
+ */
27
+ export declare const compactDdocPersistence: (persistence: IndexeddbPersistence, minRows?: number) => Promise<number>;
@@ -0,0 +1,27 @@
1
+ import * as Y from 'yjs';
2
+ type ClockRange = [start: number, end: number];
3
+ /**
4
+ * Join ranges that touch. Input is sorted by start, as the store keeps it.
5
+ * Lossless: the result covers exactly the same clocks, written one way, so
6
+ * the description does not depend on how Yjs split or joined items.
7
+ */
8
+ export declare const mergeRanges: (ranges: ClockRange[]) => ClockRange[];
9
+ /**
10
+ * A canonical description of a doc's content: for every writer, which clock
11
+ * ranges it inserted and which of those are deleted, leaving out the
12
+ * active-tab pointer.
13
+ *
14
+ * Every content edit (text, formatting, a tab rename or reorder, any root a
15
+ * later package version adds) inserts or deletes a struct, so it changes this.
16
+ * The active tab is per-user UI state written with the `'self'` origin
17
+ * (use-tab-manager.ts) and is skipped, so a tab switch does not. The rule
18
+ * lists what to skip rather than what to include, so a mistake can only make
19
+ * an unchanged doc look changed, never hide an edit.
20
+ *
21
+ * Ranges are merged so the result does not depend on how Yjs split or joined
22
+ * items: the same state gives the same description however it was produced.
23
+ */
24
+ export declare const describeDdocContent: (doc: Y.Doc) => string;
25
+ /** SHA-256 of `describeDdocContent`, base64 (44 characters). */
26
+ export declare const fingerprintDdocContent: (doc: Y.Doc) => Promise<string>;
27
+ export {};
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Read, merge into, delete and fingerprint a dDoc's y-indexeddb database
3
+ * without mounting an editor. The editor's IndexeddbPersistence names the
4
+ * database after the ddocId and stores Yjs updates in the `updates` store;
5
+ * these functions read and write that same layout.
6
+ *
7
+ * Nothing here imports React or the editor, so it is safe to call from app
8
+ * services and workers.
9
+ */
10
+ export declare const DEFAULT_DDOC_PERSISTENCE_TIMEOUT_MS = 8000;
11
+ /**
12
+ * `name` of the Error passed to `onIndexedDbError` when the editor repaired a
13
+ * corrupt store on load. It is a notice, not a failed save: the store is
14
+ * working again, but the unreadable rows it dropped are gone.
15
+ */
16
+ export declare const DDOC_STORE_REPAIRED = "DdocStoreRepaired";
17
+ export type DdocContentStatus = 'available' | 'empty' | 'missing' | 'corrupt' | 'timed-out' | 'unavailable';
18
+ export type DdocContentSnapshot = {
19
+ ddocId: string;
20
+ status: DdocContentStatus;
21
+ /** base64 Yjs update of the whole doc; null unless available or empty. */
22
+ encodedState: string | null;
23
+ error?: Error;
24
+ };
25
+ export type DdocContentMergeResult = DdocContentSnapshot & {
26
+ /** True when the merge wrote something the database did not have. */
27
+ changed: boolean;
28
+ /** True when the database did not exist and this merge created it. */
29
+ created: boolean;
30
+ };
31
+ export type DdocContentDeleteResult = {
32
+ ddocId: string;
33
+ status: 'deleted' | 'blocked' | 'unavailable';
34
+ error?: Error;
35
+ };
36
+ export type DdocPersistenceOptions = {
37
+ timeoutMs?: number;
38
+ };
39
+ /**
40
+ * Read the doc's database as stored: every row replayed in key order, the
41
+ * same thing the editor loads on open. Never creates a database. Every call
42
+ * reads afresh: a read already under way may have started before the latest
43
+ * edit was written, so its result is never handed to a later caller.
44
+ */
45
+ export declare const readDdocContent: (ddocId: string, options?: DdocPersistenceOptions) => Promise<DdocContentSnapshot>;
46
+ /**
47
+ * Merge a full Yjs state into the doc's database, creating the database if
48
+ * it does not exist. Only the part the database does not already hold is
49
+ * written, as one row, in one readwrite transaction; merging state that is
50
+ * already stored writes nothing.
51
+ *
52
+ * An editor open in another tab sees the new row on its next load. An editor
53
+ * open in this tab does not (its Y.Doc is in memory), so callers with a
54
+ * mounted editor merge into its live doc instead.
55
+ *
56
+ * Expects a complete state (`Y.encodeStateAsUpdate` of a doc). An update whose
57
+ * new parts all depend on updates the store does not have yet changes nothing
58
+ * visible, and is not stored.
59
+ */
60
+ export declare const mergeDdocContent: (ddocId: string, encodedState: string, options?: DdocPersistenceOptions) => Promise<DdocContentMergeResult>;
61
+ export type DdocContentRepairResult = {
62
+ ddocId: string;
63
+ /**
64
+ * `repaired`: bad rows were dropped. `healthy`: nothing to drop, nothing
65
+ * written. `missing`: no database. `corrupt`: the database has no `updates`
66
+ * store, so there are no rows to keep.
67
+ */
68
+ status: 'repaired' | 'healthy' | 'missing' | 'corrupt' | 'timed-out' | 'unavailable';
69
+ droppedRows: number;
70
+ error?: Error;
71
+ };
72
+ /**
73
+ * Drop the stored rows that are not valid Yjs updates. One row y-indexeddb
74
+ * cannot apply makes its replay throw and never finish, so an editor waiting
75
+ * for it never loads. Each row is checked on its own; only the bad ones are
76
+ * deleted, in one readwrite transaction, and the good rows are left exactly as
77
+ * they are. Their content is lost, but it was already unreadable. Writes
78
+ * nothing when every row is good. Never creates a database.
79
+ */
80
+ export declare const repairDdocContent: (ddocId: string, options?: DdocPersistenceOptions) => Promise<DdocContentRepairResult>;
81
+ /**
82
+ * Delete the doc's database. Resolves `blocked` when another connection
83
+ * (an editor in another tab, for example) still holds it after the timeout;
84
+ * the browser keeps the delete queued and completes it once that connection
85
+ * closes. A missing database counts as deleted.
86
+ */
87
+ export declare const deleteDdocContent: (ddocId: string, options?: DdocPersistenceOptions) => Promise<DdocContentDeleteResult>;
88
+ /**
89
+ * Short, stable fingerprint of a doc's content (44 characters). Changes on
90
+ * every content edit, including delete-only and formatting-only edits; does
91
+ * not change when only the active tab changes. The same content gives the
92
+ * same fingerprint however the state was produced. See
93
+ * `describeDdocContent` for the rule. Uses `crypto.subtle`, which browsers
94
+ * only provide on secure (https or localhost) pages.
95
+ */
96
+ export declare const getDdocContentFingerprint: (encodedState: string) => Promise<string>;
@@ -0,0 +1,2 @@
1
+ export { DDOC_STORE_REPAIRED, DEFAULT_DDOC_PERSISTENCE_TIMEOUT_MS, deleteDdocContent, getDdocContentFingerprint, mergeDdocContent, readDdocContent, repairDdocContent, } from './ddoc-persistence';
2
+ export type { DdocContentDeleteResult, DdocContentMergeResult, DdocContentRepairResult, DdocContentSnapshot, DdocContentStatus, DdocPersistenceOptions, } from './ddoc-persistence';
@@ -0,0 +1,37 @@
1
+ import { IndexeddbPersistence } from 'y-indexeddb';
2
+
3
+ /**
4
+ * y-indexeddb's `whenSynced` resolves only on its 'synced' event. If
5
+ * `indexedDB.open` rejects, it never settles and nothing reports the failure.
6
+ * Reject on an open failure so the caller's error path runs.
7
+ */
8
+ export declare const waitForIndexeddbSync: (persistence: IndexeddbPersistence) => Promise<void>;
9
+ /**
10
+ * Like `waitForIndexeddbSync`, but resolves `stalled` if the provider has not
11
+ * synced after `stallMs`, so the caller can check why. The underlying wait is
12
+ * not cancelled; wait again to keep waiting.
13
+ */
14
+ export declare const waitForIndexeddbSyncOrStall: (persistence: IndexeddbPersistence, stallMs: number) => Promise<"synced" | "stalled">;
15
+ type WatchOptions = {
16
+ /** Called for every failed write and for a lost connection. */
17
+ onError: (error: Error) => void;
18
+ /** Called once the provider has been detached from the doc. */
19
+ onDetached: () => void;
20
+ };
21
+ /**
22
+ * Watch a synced provider's connection for the failures y-indexeddb itself
23
+ * ignores:
24
+ *
25
+ * - A failed write aborts its transaction, and `abort` bubbles to the
26
+ * database. y-indexeddb never looks, so quota errors and the like are lost.
27
+ * - The browser can close the connection (site data cleared, storage
28
+ * pressure: `close`), and a delete from another tab waits on it
29
+ * (`versionchange`). After either, y-indexeddb's update handler throws
30
+ * InvalidStateError into the next edit. Destroy the provider instead: it
31
+ * unsubscribes from doc updates before closing, so editing continues in
32
+ * memory, and a pending delete can proceed.
33
+ *
34
+ * Returns a function that removes the listeners.
35
+ */
36
+ export declare const watchIndexeddbConnection: (persistence: IndexeddbPersistence, { onError, onDetached }: WatchOptions) => (() => void);
37
+ export {};
@@ -57,9 +57,10 @@ export declare class SyncManager {
57
57
  private ydoc;
58
58
  private callbacksRef;
59
59
  private onLocalUpdate?;
60
+ private onLocalChange?;
60
61
  constructor(config: SyncManagerConfig, onCollabStateChange: (state: CollabState) => void);
61
62
  /** Called by useSyncManager on every render to keep refs fresh */
62
- updateRefs(_services: CollabServices | undefined, callbacks: CollabCallbacks | undefined, onLocalUpdate?: (updatedDocContent: string, updateChunk: string) => void): void;
63
+ updateRefs(_services: CollabServices | undefined, callbacks: CollabCallbacks | undefined, onLocalUpdate?: (updatedDocContent: string, updateChunk: string) => void, onLocalChange?: () => void): void;
63
64
  get isConnected(): boolean;
64
65
  get isReady(): boolean;
65
66
  get isResuming(): boolean;
@@ -135,6 +136,12 @@ export declare class SyncManager {
135
136
  private processNextUpdate;
136
137
  private handleRemoteContentUpdate;
137
138
  private applyRemoteYjsUpdate;
139
+ /**
140
+ * Tell the host that a collaborator's update changed the doc. The full
141
+ * document is encoded only for a host that asked for it (`onLocalUpdate`);
142
+ * `onLocalChange` gets the signal without that cost.
143
+ */
144
+ private notifyHostOfRemoteChange;
138
145
  private applyQueuedRemoteContents;
139
146
  private withRetry;
140
147
  private disconnectInternal;
@@ -0,0 +1,26 @@
1
+ import type * as ucans from '@ucans/ucans';
2
+ /**
3
+ * The collab server's clock, learned from its handshake. The server checks auth UCAN nbf/exp
4
+ * against its own clock, so tokens built from a skewed device clock get a 401.
5
+ */
6
+ export declare class ServerClock {
7
+ private readonly wallNow;
8
+ private readonly monoNow;
9
+ private anchor;
10
+ constructor(wallNow?: () => number, monoNow?: (() => number) | null);
11
+ /**
12
+ * Time since the handshake is measured on the monotonic clock, so a device clock change
13
+ * mid-session cannot skew it. A missing or implausible stamp (older server) falls back to the
14
+ * device clock. Delivery delay and sleep only make the estimate trail the server, which the
15
+ * token window absorbs.
16
+ */
17
+ sync(serverTimeMs: unknown): void;
18
+ nowSeconds(): number;
19
+ ucanWindow(): {
20
+ notBefore: number;
21
+ expiration: number;
22
+ };
23
+ /** Whether a cached UCAN can still be presented: active, and not expiring within the margin. */
24
+ isUsable(ucan: ucans.Ucan): boolean;
25
+ private elapsedBase;
26
+ }
@@ -74,6 +74,7 @@ export declare class SocketClient {
74
74
  private contractAddress?;
75
75
  private ownerUcan?;
76
76
  private collaborationUcan?;
77
+ private serverClock;
77
78
  private ownerAddress?;
78
79
  private ddocId?;
79
80
  private editLock?;
@@ -130,7 +131,6 @@ export declare class SocketClient {
130
131
  terminateSession: () => Promise<void>;
131
132
  flushBeacon(mergedUpdate: string): void;
132
133
  private getCollaborationKeyPair;
133
- private isUcanValid;
134
134
  private getOwnerToken;
135
135
  private buildSessionToken;
136
136
  private _authenticate;
@@ -199,6 +199,8 @@ export interface SyncManagerConfig {
199
199
  services?: CollabServices;
200
200
  callbacks?: CollabCallbacks;
201
201
  onLocalUpdate?: (updatedDocContent: Data['editorJSONData'], updateChunk: string) => void;
202
+ /** Like `onLocalUpdate` but without arguments, so no full-document encode. */
203
+ onLocalChange?: () => void;
202
204
  /** Origins to ignore in the ydoc update handler (e.g. IndexedDB provider) */
203
205
  ignoredOrigins?: Array<{
204
206
  current: unknown;