> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ace Editor

> Setup Multiplayer Editing for Ace Editor.

The `@veltdev/ace-crdt-react` and `@veltdev/ace-crdt` libraries connect Ace Editor to a Velt CRDT text Store backed by [Yjs](https://docs.yjs.dev/). Shared content lives in one `Y.Text`; cursors, selections, and attention highlights use transient awareness state.

## Prerequisites

* Node.js (v14 or higher)
* A Velt account with an API key ([sign up](https://console.velt.dev/))
* React (v18 or higher) when using the React wrapper
* Optional: TypeScript for type safety

## Setup

### Step 1: Install Dependencies

> [tab] React / Next.js
>
> ```bash theme={null}
> npm install @veltdev/ace-crdt-react @veltdev/ace-crdt react-ace ace-builds @veltdev/react @veltdev/types react react-dom
> ```

> [tab] Other Frameworks
>
> ```bash theme={null}
> npm install @veltdev/ace-crdt ace-builds @veltdev/client @veltdev/crdt @veltdev/types yjs y-protocols
> ```

The current base wrapper peer range targets `ace-builds@^1.44.0`. Keep one copy of `@veltdev/crdt`, `yjs`, and `y-protocols` in the final application bundle.

### Step 2: Setup Velt

Initialize Velt, authenticate a user, and set stable document identity before constructing the collaboration manager. Follow the [Velt Setup Docs](https://velt.dev/docs/get-started/quickstart.md) for the production token endpoint used by `authProvider`.

> [tab] React / Next.js
>
> ```tsx theme={null}
> import { useEffect, useState } from 'react';
> import {
>   VeltProvider,
>   useCurrentUser,
>   useSetDocuments,
> } from '@veltdev/react';
>
> const DOCUMENT_ID = 'shared-code-file';
> const USER = {
>   userId: 'ada',
>   name: 'Ada Lovelace',
>   email: 'ada@example.com',
>   organizationId: 'my-org-id',
> };
>
> const authProvider = {
>   user: USER,
>   retryConfig: { retryCount: 3, retryDelay: 1000 },
>   generateToken: async () => {
>     const response = await fetch('/api/velt/token', {
>       method: 'POST',
>       headers: { 'Content-Type': 'application/json' },
>       body: JSON.stringify(USER),
>     });
>     const { token } = await response.json();
>     return token;
>   },
> };
>
> function DocumentScope() {
>   const user = useCurrentUser();
>   const { setDocuments } = useSetDocuments();
>   const [documentReady, setDocumentReady] = useState(false);
>
>   useEffect(() => {
>     if (!user) return;
>     setDocuments([{
>       id: DOCUMENT_ID,
>       metadata: { documentName: 'Shared Ace file' },
>     }]);
>     setDocumentReady(true);
>   }, [user, setDocuments]);
>
>   return user && documentReady
>     ? <CollaborativeEditor />
>     : <p>Preparing collaboration...</p>;
> }
>
> <VeltProvider apiKey="YOUR_VELT_API_KEY" authProvider={authProvider}>
>   <DocumentScope />
> </VeltProvider>
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> import { initVelt } from '@veltdev/client';
>
> const DOCUMENT_ID = 'shared-code-file';
> const client = await initVelt('YOUR_VELT_API_KEY');
>
> await client.identify({
>   userId: 'ada',
>   name: 'Ada Lovelace',
>   email: 'ada@example.com',
>   organizationId: 'my-org-id',
> });
> await client.setDocument(DOCUMENT_ID, {
>   documentName: 'Shared Ace file',
> });
>
> let resolveVeltReady!: () => void;
> const veltReady = new Promise<void>((resolve) => {
>   resolveVeltReady = resolve;
> });
> const initSubscription = client.getVeltInitState().subscribe((isReady) => {
>   if (isReady) resolveVeltReady();
> });
> await veltReady;
> initSubscription.unsubscribe();
> ```

All users editing the same file must use the same Velt document ID and Ace `editorId`.

### Step 3: Create the Ace Editor

The host needs stable dimensions. Import each mode and theme before selecting it.

```html theme={null}
<div id="ace-editor" class="ace-host"></div>
```

```css theme={null}
.ace-host {
  width: 100%;
  height: 520px;
  min-height: 360px;
}
```

> [tab] React / Next.js
>
> `AceCrdtEditor` creates a `react-ace` editor. When using the hook with an editor your application owns, create Ace normally and pass the instance to the hook:
>
> ```tsx theme={null}
> import ace from 'ace-builds/src-noconflict/ace';
> import 'ace-builds/src-noconflict/mode-typescript';
> import 'ace-builds/src-noconflict/theme-textmate';
>
> const editor = ace.edit(hostElement);
> editor.setTheme('ace/theme/textmate');
> editor.session.setMode('ace/mode/typescript');
> editor.session.setUseWorker(false);
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> import ace from 'ace-builds/src-noconflict/ace';
> import 'ace-builds/src-noconflict/mode-typescript';
> import 'ace-builds/src-noconflict/theme-textmate';
>
> const host = document.querySelector<HTMLElement>('#ace-editor')!;
> const editor = ace.edit(host);
> editor.setTheme('ace/theme/textmate');
> editor.session.setMode('ace/mode/typescript');
> editor.session.setUseWorker(false);
> editor.setOptions({
>   fontSize: 14,
>   tabSize: 2,
>   useSoftTabs: true,
>   showPrintMargin: false,
>   wrap: true,
> });
> ```

### Step 4: Initialize Collaboration

Ace markers require a real Ace `Range`. The factory below also widens a collapsed cursor by one column so the remote caret stays visible.

> [tab] React Component
>
> ```tsx theme={null}
> import ace from 'ace-builds/src-noconflict/ace';
> import { AceCrdtEditor } from '@veltdev/ace-crdt-react';
>
> const Range = ace.require('ace/range').Range;
>
> function CollaborativeEditor() {
>   return (
>     <div className="ace-host">
>       <AceCrdtEditor
>         documentId="shared-code-file"
>         mode="typescript"
>         theme="textmate"
>         initialContent={'export const greeting = "Hello";\n'}
>         cursorData={{
>           name: 'Ada Lovelace',
>           email: 'ada@example.com',
>           color: '#0f766e',
>           colorLight: '#0f766e33',
>         }}
>         rangeFactory={(start, end) => {
>           const visibleEnd = start.row === end.row && start.column === end.column
>             ? { row: end.row, column: end.column + 1 }
>             : end;
>           return new Range(
>             start.row,
>             start.column,
>             visibleEnd.row,
>             visibleEnd.column,
>           );
>         }}
>         onError={(error) => console.error('Ace collaboration:', error)}
>       />
>     </div>
>   );
> }
> ```
>
> Do not pass `value` or `defaultValue` to `AceCrdtEditor`. The shared `Y.Text` is the content source of truth.

> [tab] React Hook
>
> ```tsx theme={null}
> import ace from 'ace-builds/src-noconflict/ace';
> import { useCollaboration } from '@veltdev/ace-crdt-react';
>
> const Range = ace.require('ace/range').Range;
> const collaboration = useCollaboration({
>   editorId: 'shared-code-file',
>   editor,
>   initialContent: '// Start writing here\n',
>   cursorData: { name: 'Ada', color: '#0f766e' },
>   rangeFactory: (start, end) => {
>     const visibleEnd = start.row === end.row && start.column === end.column
>       ? { row: end.row, column: end.column + 1 }
>       : end;
>     return new Range(start.row, start.column, visibleEnd.row, visibleEnd.column);
>   },
>   onError: (error) => console.error('Ace collaboration:', error),
> });
> ```
>
> Passing `editor` creates and binds collaboration once. Do not also call `attachEditor(editor)`.
>
> If Ace is created after the hook renders, omit `editor` from the options and pass it through the returned callback instead:
>
> ```tsx theme={null}
> const collaboration = useCollaboration({
>   editorId: 'shared-code-file',
>   rangeFactory,
> });
>
> collaboration.editorRef(editor);
>
> // Before replacing or destroying Ace:
> collaboration.editorRef(null);
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> import { createCollaboration } from '@veltdev/ace-crdt';
>
> const Range = ace.require('ace/range').Range;
> const manager = await createCollaboration({
>   editorId: DOCUMENT_ID,
>   veltClient: client,
>   editor,
>   initialContent: '// Start writing here\n',
>   cursorData: {
>     name: 'Ada Lovelace',
>     email: 'ada@example.com',
>     color: '#0f766e',
>     colorLight: '#0f766e33',
>   },
>   rangeFactory: (start, end) => {
>     const visibleEnd = start.row === end.row && start.column === end.column
>       ? { row: end.row, column: end.column + 1 }
>       : end;
>     return new Range(
>       start.row,
>       start.column,
>       visibleEnd.row,
>       visibleEnd.column,
>     );
>   },
>   onError: (error) => console.error('Ace collaboration:', error),
> });
> ```

#### React Context Provider (Optional)

React applications can expose one hook-managed collaboration value through context:

```tsx theme={null}
import { AceCrdtProvider } from '@veltdev/ace-crdt-react';

<AceCrdtProvider
  config={{
    editorId: 'shared-code-file',
    editor,
    rangeFactory,
  }}
>
  {(collaboration) => (
    <button onClick={() => collaboration.saveVersion('Checkpoint')}>
      Save version
    </button>
  )}
</AceCrdtProvider>
```

`CollaborationProvider` is an alias of `AceCrdtProvider`. Use one provider or one hook for an editor, not both.

### Step 5: Add Remote Marker Styles

The manager injects per-user marker colors and labels dynamically. Keep the editor sized and add a shared shape rule:

```css theme={null}
.ace-host {
  width: 100%;
  height: 520px;
  min-height: 360px;
}

.ace_marker-layer [class*="velt-ace-remote-marker"] {
  border-radius: 2px;
}
```

Cursor markers receive a visible two-pixel left border; selections and highlights receive a border plus a translucent background. Do not override those injected borders with `border: none`.

### Step 6: Monitor Status and Remote Selections

> [tab] React / Next.js
>
> The component render function and hook expose reactive `status`, `synced`, `remoteSelections`, and `stats` values:
>
> ```tsx theme={null}
> <AceCrdtEditor documentId="shared-code-file" rangeFactory={rangeFactory}>
>   {(collaboration) => (
>     <p>
>       {collaboration.status} · {collaboration.synced ? 'Synced' : 'Syncing'}
>       {' · '}{collaboration.remoteSelections.length} remote selections
>     </p>
>   )}
> </AceCrdtEditor>
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> const offStatus = manager.onStatusChange((status) => {
>   document.querySelector('#status')!.textContent = status;
> });
> const offSynced = manager.onSynced((synced) => {
>   document.querySelector('#synced')!.textContent = synced ? 'Yes' : 'No';
> });
> const offRemote = manager.onRemoteSelectionsChange((selections) => {
>   console.log('Remote cursor, selection, and highlight state:', selections);
> });
> ```

Ace cursor and selection events are published automatically after attachment, and awareness changes trigger remote marker rendering. Custom UI can publish explicitly:

```ts theme={null}
manager.publishCurrentSelection('cursor');
manager.publishCurrentSelection('selection');
manager.addHighlight({
  start: { row: 0, column: 0 },
  end: { row: 0, column: 10 },
}, '#f59e0b');
```

Cursor, selection, and highlight awareness is ephemeral. It is not part of the shared file or a saved version.

### Step 7: Initial Content and Force Reset

Initial content is plain text and is applied only when the shared text is new and uninitialized:

```ts theme={null}
const initialContent = [
  'function add(a, b) {',
  '  return a + b;',
  '}',
].join('\n');
```

Use a reset only for an explicit destructive action:

```ts theme={null}
await manager.forceReset('// Reset file\n');
```

> [!WARNING]
>
> `forceResetInitialContent` and `forceReset()` replace the shared file for every collaborator.

### Step 8: Work with Shared Text

```ts theme={null}
const currentValue = manager.getValue();
manager.setValue('const answer = 42;', 'toolbar-set');
manager.insertText(' // note');
manager.flushEditorToStore('manual-save');
```

`insertText()` accepts an optional character index. Without one, the manager uses the current Ace cursor when available.

### Step 9: Configure Collaborative Undo and Redo

The manager creates one Yjs `UndoManager` for the shared text.

> [tab] React / Next.js
>
> ```tsx theme={null}
> <button onClick={() => collaboration.undo()}>Undo</button>
> <button onClick={() => collaboration.redo()}>Redo</button>
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> const undoManager = manager.getUndoManager();
>
> undoManager?.undo();
> manager.flushEditorToStore('undo');
>
> undoManager?.redo();
> manager.flushEditorToStore('redo');
> ```

Use these controls as the user-facing collaborative history. Mixing Ace's local history commands with Yjs undo can produce a history that does not match the shared document.

### Step 10: Save and Restore Versions

```ts theme={null}
const versionId = await manager.saveVersion('Before refactor');
const versions = await manager.getVersions();
await manager.restoreVersion(versionId);
```

Saving flushes Ace before the checkpoint. Restoring updates the Store and reapplies the shared text to Ace.

### Step 11: Subscribe to CRDT Events

> [tab] React / Next.js
>
> ```tsx theme={null}
> import { useVeltClient } from '@veltdev/react';
>
> const { client } = useVeltClient();
>
> useEffect(() => {
>   if (!client) return;
>   const subscription = client.getCrdtElement()
>     .on('updateData')
>     .subscribe((event) => console.log('CRDT update:', event));
>   return () => subscription.unsubscribe();
> }, [client]);
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> const crdtSubscription = client.getCrdtElement()
>   .on('updateData')
>   .subscribe((event) => console.log('CRDT update:', event));
> ```

### Step 12: Attach or Replace an Editor (Alternative)

Use the attach-later lifecycle only when the Store must initialize before Ace:

> [tab] React / Next.js
>
> ```tsx theme={null}
> const collaboration = useCollaboration({
>   editorId: 'shared-code-file',
>   initializeWithoutEditor: true,
>   rangeFactory,
> });
>
> collaboration.attachEditor(editor, { rangeFactory });
> collaboration.detachEditor();
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> const manager = await createCollaboration({
>   editorId: DOCUMENT_ID,
>   veltClient: client,
>   rangeFactory,
> });
>
> manager.attachEditor(editor, { rangeFactory });
> manager.detachEditor();
> ```

This replaces passing `editor` to `createCollaboration()`. Do not use both paths in one initialization flow.

### Step 13: Custom Encryption (Optional)

> [tab] React / Next.js
>
> ```tsx theme={null}
> <VeltProvider
>   apiKey="YOUR_VELT_API_KEY"
>   authProvider={authProvider}
>   encryptionProvider={encryptionProvider}
> >
>   <DocumentScope />
> </VeltProvider>
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> client.setEncryptionProvider(encryptionProvider);
> ```

See [setEncryptionProvider()](https://velt.dev/docs/api-reference/sdk/api/api-methods.md#setencryptionprovider-encryptionprovider) and [VeltEncryptionProvider](https://velt.dev/docs/api-reference/sdk/models/data-models.md#veltencryptionprovider).

### Step 14: Clean Up

> [tab] React / Next.js
>
> `useCollaboration()` and `AceCrdtEditor` unsubscribe and destroy their collaboration manager on unmount. When an app owns the Ace instance, use an explicit manager-first cleanup before destroying Ace:
>
> ```tsx theme={null}
> useEffect(() => {
>   return () => {
>     collaboration.destroy();
>     editor.destroy();
>     editor.container.remove();
>   };
> }, [collaboration.destroy, editor]);
> ```
>
> The hook's later unmount cleanup is idempotent, so the explicit `destroy()` safely preserves the required order.

> [tab] Other Frameworks
>
> Unsubscribe first, destroy collaboration so it releases Ace listeners and markers, then destroy Ace and remove its DOM:
>
> ```ts theme={null}
> offStatus();
> offSynced();
> offRemote();
> crdtSubscription.unsubscribe();
> manager.destroy();
> editor.destroy();
> editor.container.remove();
> ```

## Notes

* **Plain-text model**: Ace modes and themes are local presentation; only text is stored in `Y.Text`.
* **One binding**: Passing `editor` to the factory attaches it automatically.
* **Range factory**: Use Ace's verified `Range` constructor for remote markers.
* **Automatic selections**: The manager attaches Ace selection listeners and publishes cursor/selection state through awareness.
* **Advanced escape hatches**: `getDoc()`, `getYText()`, `getProvider()`, `getAwareness()`, and `getUndoManager()` are for diagnostics or advanced integrations. `getText()` and `getYText()` return the same shared `Y.Text`; internally, `getYText()` delegates to `getText()`. Do not create a second Y.Doc, provider, or shared text.
* **Production authentication**: Use a server-backed React `authProvider` and never expose privileged server tokens in browser code.

## Testing and Debugging

1. Open the same Velt document and Ace `editorId` as two different authenticated users in separate browser profiles.
2. Type and delete concurrently at different positions.
3. Verify visible cursor carets, selected ranges, attention highlights, Yjs undo/redo, versions, reconnect behavior, and persistence.
4. Repeat with the production mode, theme, worker, and bundler configuration.

Common issues:

* Mode or theme is missing: import the matching `ace-builds/src-noconflict` module before creating Ace.
* Text does not sync: remove React `value`/`defaultValue` and confirm both clients use the same IDs.
* Markers do not appear: provide `cursorData`, the complete `rangeFactory`, a sized host, and marker CSS.
* Duplicate Yjs warning: deduplicate `yjs` and `@veltdev/crdt` in the application bundle.

> [!TIP]
>
> Use the [VeltCrdtStoreMap debugging interface](https://velt.dev/docs/realtime-collaboration/crdt/setup/core.md#veltcrdtstoremap) to inspect shared text and provider status.

## Complete Example

> [tab] React / Next.js
>
> ```tsx Complete App.tsx expandable lines theme={null}
> import { useEffect, useMemo, useState } from 'react';
> import ace from 'ace-builds/src-noconflict/ace';
> import 'ace-builds/src-noconflict/mode-typescript';
> import 'ace-builds/src-noconflict/theme-textmate';
> import {
>   VeltProvider,
>   useCurrentUser,
>   useSetDocuments,
> } from '@veltdev/react';
> import { AceCrdtEditor } from '@veltdev/ace-crdt-react';
> import './styles.css';
>
> const DOCUMENT_ID = 'shared-code-file';
> const USER = {
>   userId: 'ada',
>   organizationId: 'my-org-id',
>   name: 'Ada Lovelace',
>   email: 'ada@example.com',
> };
> const Range = ace.require('ace/range').Range;
>
> function CollaborativeEditor() {
>   const user = useCurrentUser();
>   const { setDocuments } = useSetDocuments();
>   const [documentReady, setDocumentReady] = useState(false);
>
>   useEffect(() => {
>     if (!user) return;
>     setDocuments([{
>       id: DOCUMENT_ID,
>       metadata: { documentName: 'Shared Ace file' },
>     }]);
>     setDocumentReady(true);
>   }, [user, setDocuments]);
>
>   if (!user || !documentReady) return <p>Preparing collaboration...</p>;
>
>   return (
>     <main>
>       <div className="ace-host">
>         <AceCrdtEditor
>           documentId={DOCUMENT_ID}
>           mode="typescript"
>           theme="textmate"
>           initialContent={'export const greeting = "Hello";\n'}
>           cursorData={{
>             name: user.name,
>             email: user.email,
>             color: '#0f766e',
>             colorLight: '#0f766e33',
>           }}
>           rangeFactory={(start, end) => {
>             const visibleEnd = start.row === end.row && start.column === end.column
>               ? { row: end.row, column: end.column + 1 }
>               : end;
>             return new Range(
>               start.row,
>               start.column,
>               visibleEnd.row,
>               visibleEnd.column,
>             );
>           }}
>           onStatusChange={(status) => console.log('Status:', status)}
>           onError={console.error}
>         >
>           {(collaboration) => (
>             <nav>
>               <button onClick={() => collaboration.undo()}>Undo</button>
>               <button onClick={() => collaboration.redo()}>Redo</button>
>               <button onClick={() => collaboration.saveVersion('Checkpoint')}>
>                 Save version
>               </button>
>               <span>{collaboration.synced ? 'Synced' : collaboration.status}</span>
>             </nav>
>           )}
>         </AceCrdtEditor>
>       </div>
>     </main>
>   );
> }
>
> export default function App() {
>   const authProvider = useMemo(() => ({
>     user: USER,
>     retryConfig: { retryCount: 3, retryDelay: 1000 },
>     generateToken: async () => {
>       const response = await fetch('/api/velt/token', {
>         method: 'POST',
>         headers: { 'Content-Type': 'application/json' },
>         body: JSON.stringify(USER),
>       });
>       const { token } = await response.json();
>       return token;
>     },
>   }), []);
>
>   return (
>     <VeltProvider apiKey="YOUR_VELT_API_KEY" authProvider={authProvider}>
>       <CollaborativeEditor />
>     </VeltProvider>
>   );
> }
> ```
>
> ```css Complete styles.css expandable lines theme={null}
> .ace-host {
>   width: 100%;
>   height: 520px;
>   min-height: 360px;
> }
>
> .ace_marker-layer [class*="velt-ace-remote-marker"] {
>   border-radius: 2px;
> }
> ```

> [tab] Other Frameworks
>
> ```html Complete index.html expandable lines theme={null}
> <p>Status: <span id="status">connecting</span></p>
> <p>Synced: <span id="synced">No</span></p>
> <button id="undo">Undo</button>
> <button id="redo">Redo</button>
> <button id="save-version">Save version</button>
> <div id="ace-editor" class="ace-host"></div>
> ```
>
> ```ts Complete app.ts expandable lines theme={null}
> import ace from 'ace-builds/src-noconflict/ace';
> import 'ace-builds/src-noconflict/mode-typescript';
> import 'ace-builds/src-noconflict/theme-textmate';
> import { initVelt } from '@veltdev/client';
> import {
>   createCollaboration,
>   type CollaborationManager,
> } from '@veltdev/ace-crdt';
> import './styles.css';
>
> const DOCUMENT_ID = 'shared-code-file';
> const client = await initVelt('YOUR_VELT_API_KEY');
>
> await client.identify({
>   userId: 'ada',
>   name: 'Ada Lovelace',
>   email: 'ada@example.com',
>   organizationId: 'my-org-id',
> });
> await client.setDocument(DOCUMENT_ID, {
>   documentName: 'Shared Ace file',
> });
>
> let resolveVeltReady!: () => void;
> const veltReady = new Promise<void>((resolve) => {
>   resolveVeltReady = resolve;
> });
> const initSubscription = client.getVeltInitState().subscribe((isReady) => {
>   if (isReady) resolveVeltReady();
> });
> await veltReady;
> initSubscription.unsubscribe();
>
> const host = document.querySelector<HTMLElement>('#ace-editor')!;
> const editor = ace.edit(host);
> editor.setTheme('ace/theme/textmate');
> editor.session.setMode('ace/mode/typescript');
> editor.session.setUseWorker(false);
>
> const Range = ace.require('ace/range').Range;
> let manager: CollaborationManager | null = await createCollaboration({
>   editorId: DOCUMENT_ID,
>   veltClient: client,
>   editor,
>   initialContent: 'export const greeting = "Hello";\n',
>   cursorData: {
>     name: 'Ada Lovelace',
>     email: 'ada@example.com',
>     color: '#0f766e',
>     colorLight: '#0f766e33',
>   },
>   rangeFactory: (start, end) => {
>     const visibleEnd = start.row === end.row && start.column === end.column
>       ? { row: end.row, column: end.column + 1 }
>       : end;
>     return new Range(
>       start.row,
>       start.column,
>       visibleEnd.row,
>       visibleEnd.column,
>     );
>   },
>   onError: console.error,
> });
>
> document.querySelector('#status')!.textContent = manager.status;
> document.querySelector('#synced')!.textContent = manager.synced ? 'Yes' : 'No';
>
> const offStatus = manager.onStatusChange((status) => {
>   document.querySelector('#status')!.textContent = status;
> });
> const offSynced = manager.onSynced((synced) => {
>   document.querySelector('#synced')!.textContent = synced ? 'Yes' : 'No';
> });
> const offRemote = manager.onRemoteSelectionsChange((selections) => {
>   console.log('Remote selections:', selections);
> });
> const crdtSubscription = client.getCrdtElement()
>   .on('updateData')
>   .subscribe((event) => console.log('CRDT update:', event));
>
> document.querySelector('#undo')!.addEventListener('click', () => {
>   manager?.getUndoManager()?.undo();
>   manager?.flushEditorToStore('undo');
> });
> document.querySelector('#redo')!.addEventListener('click', () => {
>   manager?.getUndoManager()?.redo();
>   manager?.flushEditorToStore('redo');
> });
> document.querySelector('#save-version')!.addEventListener('click', () => {
>   void manager?.saveVersion('Manual checkpoint');
> });
>
> let cleanedUp = false;
> function cleanup() {
>   if (cleanedUp) return;
>   cleanedUp = true;
>   offStatus();
>   offSynced();
>   offRemote();
>   crdtSubscription.unsubscribe();
>   manager?.destroy();
>   manager = null;
>   editor.destroy();
>   editor.container.remove();
> }
>
> window.addEventListener('pagehide', cleanup, { once: true });
> ```
>
> ```css Complete styles.css expandable lines theme={null}
> .ace-host {
>   width: 100%;
>   height: 520px;
>   min-height: 360px;
> }
>
> .ace_marker-layer [class*="velt-ace-remote-marker"] {
>   border-radius: 2px;
> }
> ```

## How It Works

1. The app initializes Velt, authenticates the user, sets the document, and creates one Ace editor.
2. The manager creates a Velt text Store, shared `Y.Text`, provider, awareness bridge, and Yjs `UndoManager`.
3. Ace deltas are converted to character offsets and applied as Yjs insert/delete transactions.
4. Remote Yjs updates are applied back to Ace without echoing into a second local transaction.
5. Ace selection events publish awareness state; the range factory converts remote row/column data into visible Ace markers.
6. Cleanup removes subscriptions and markers before the app destroys Ace and its DOM.

## APIs

### React: AceCrdtEditor

| Prop group | Important props |
| - | - |
| Collaboration | `editorId`, `documentId`, `initialContent`, `forceResetInitialContent`, `cursorData`, `rangeFactory`, `debounceMs`, `enabled` |
| Ace | `mode`, `theme`, `name`, `width`, `height`, `setOptions`, and other React Ace props |
| Lifecycle | `onLoad`, `onEditorReady`, `onManagerReady`, `onStatusChange`, `onError` |
| UI | `children` render function |

### React: useCollaboration()

The hook returns `editorRef`, editor and manager state, shared text helpers, selection/highlight helpers, collaborative `undo()`/`redo()`, versions, statistics, attachment methods, and `destroy()`. It includes `isSynced`, `versions`, `refreshSnapshots()`, and `refreshVersions()` alongside the save and restore helpers. `value`, `remoteSelections`, and `stats` are reactive snapshots.

### CollaborationManager Methods

| Method group | Methods |
| - | - |
| Lifecycle | `initialize()`, `destroy()`, `attachEditor()`, `detachEditor()` |
| Text | `getValue()`, `setValue()`, `insertText()`, `flushEditorToStore()`, `forceReset()` |
| Awareness | `setLocalSelection()`, `publishCurrentSelection()`, `addHighlight()`, `getRemoteSelections()`, `renderRemoteSelections()`, `clearRemoteMarkers()` |
| Status | `initialized`, `synced`, `status`, `onStatusChange()`, `onSynced()`, `getStats()` |
| Versions | `saveVersion()`, `getVersions()`, `getVersionById()`, `restoreVersion()`, `setStateFromVersion()` |
| Advanced | `getCollaborationPrimitives()`, `getDoc()`, `getText()`, `getYText()`, `getProvider()`, `getAwareness()`, `getUndoManager()`, `getEditor()`, `getStore()` |

### Exported Types and Helpers

* `AceEditorLike`, `AcePosition`, `AceSelectionRange`, `AceDelta`, `AceRangeFactory`
* `AceCursorData`, `AceAwarenessSelection`, `RemoteSelection`
* `CollaborationConfig`, `CollaborationPrimitives`, `CollaborationStats`, `BindEditorOptions`
* `normalizeText()`, `positionToIndex()`, `indexToPosition()`, `rangeToOffsets()`, `offsetsToRange()`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.