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

# Velt SDK Changelog

> Release Notes of changes added to the core Velt SDK

### Libraries

* `@veltdev/react`
* `@veltdev/client`
* `@veltdev/sdk`

<Update label="5.0.2-beta.18" description="April 16, 2026">
  ### New Features

  * \[**Comments**]: Edit drafts are now preserved in memory when a user dismisses the edit composer without submitting (via click-outside, dialog destroy, or back navigation). The collapsed thread card shows the pending draft with a `(DRAFT)` badge and italic styling; clicking it re-opens the edit composer pre-filled with the saved changes. Drafts are cleared on successful submit, explicit Escape, or page refresh.

  * \[**Comments**]: `VeltInlineCommentsSection` / `velt-inline-comments-section` now supports message truncation via `messageTruncation` / `message-truncation` (default: `false`) and `messageTruncationLines` / `message-truncation-lines` (default: `4`). Truncated messages show a **Show more** button; each comment tracks its own expand/collapse state. The Show more and Show less buttons are full wireframe primitives for complete visual customization. [Learn more →](/async-collaboration/comments/setup/inline-comments#optional-enable-message-truncation)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      <VeltInlineCommentsSection
        targetElementId="article0"
        messageTruncation={true}
        messageTruncationLines={3}
      />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-inline-comments-section
        target-element-id="article0"
        message-truncation="true"
        message-truncation-lines="3">
      </velt-inline-comments-section>
      ```
    </Tab>
  </Tabs>

  * \[**Rewriter**]: Two new methods on `RewriterElement` let you programmatically show or hide the default Velt Rewriter toolbar that appears on text selection. When the default UI is disabled, the rewriter feature remains active (events still fire) but the built-in toolbar is suppressed.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      const rewriterElement = client.getRewriterElement();
      rewriterElement.enableDefaultUI();
      rewriterElement.disableDefaultUI();
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      const rewriterElement = Velt.getRewriterElement();
      rewriterElement.enableDefaultUI();
      rewriterElement.disableDefaultUI();
      ```
    </Tab>
  </Tabs>

  ### Bug Fixes

  * \[**Comments**]: The text-comment toolbar position clamping now measures width from the inner `.velt-text-comment-toolbar--container` element instead of the outer host element, preventing the toolbar from being positioned off-screen or clipped at the viewport edge.

  * \[**Comments**]: New comment pin contexts are now seeded with the latest cached `unreadCommentsMap` instead of an empty default. Unread badges on newly rendered pins now show the correct state immediately without waiting for the next observable emission.

  * \[**Comments**]: Typing `@name` (with a trailing space) when no matching user is found in the autocomplete dropdown now closes the panel and converts the text into a mention chip, matching the behavior of clicking the "new contact" fallback item.
</Update>

<Update label="5.0.2-beta.17" description="April 13, 2026">
  ### Bug Fixes

  * \[**Comments**]: The autocomplete/@mention dropdown now repositions correctly when scrolling inside custom scroll containers.

  * \[**Notifications**]: The notifications panel list now updates reactively when new notifications arrive. Fixed a stale-caching issue.

  * \[**Comments**]: Invalid email addresses are no longer incorrectly tagged as user contacts in @mention — the `isEmailValid` flag is now checked before adding to `taggedUserContacts`.

  * \[**Comments**]: The Delete/Backspace keyboard shortcut no longer deletes annotations in edit-mode composers when `deleteOnBackspace` is disabled.
</Update>

<Update label="5.0.2-beta.16" description="April 09, 2026">
  ### New Features

  * \[**Comments**]: Added `anonymousEmail` prop to `VeltComments`, `VeltInlineCommentsSection`, and `VeltCommentDialog`. When set to `false`, unrecognized email addresses are suppressed from @mention suggestions; known contacts are always included. Per-section overrides are fully isolated and cleaned up on destroy. Use `enableAnonymousEmail()` / `disableAnonymousEmail()` on the `CommentElement` for API-based control.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      <VeltComments anonymousEmail={false} />
      <VeltInlineCommentsSection targetElementId="article-body" anonymousEmail={false} />
      <VeltCommentDialog anonymousEmail={false} />

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.disableAnonymousEmail();
      commentElement.enableAnonymousEmail();
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments anonymous-email="false"></velt-comments>
      <velt-inline-comments-section target-element-id="article-body" anonymous-email="false"></velt-inline-comments-section>
      <velt-comment-dialog anonymous-email="false"></velt-comment-dialog>
      ```

      ```js theme={null}
      // Using API methods
      const commentElement = Velt.getCommentElement();
      commentElement.disableAnonymousEmail();
      commentElement.enableAnonymousEmail();
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: `updateContactList()` now accepts an optional `filters` boolean in its config object. When `true`, the sidebar's People/Assigned/Tagged/Involved filters are restricted to the custom contact list plus the current user.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      const contactElement = client.getContactElement();
      contactElement.updateContactList(contacts, { filters: true });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      const contactElement = Velt.getContactElement();
      contactElement.updateContactList(contacts, { filters: true });
      ```
    </Tab>
  </Tabs>

  ### Improvements

  * \[**Comments**]: Pasting rich-formatted content into the comment composer now preserves inline formatting (bold, italic, underline, links). CSS-based formatting is converted to semantic HTML, block-level tags are stripped with `<br>` breaks.

  * \[**Comments**]: @mention spans now include `velt-mention` plus `velt-mention--name` or `velt-mention--email` CSS classes, enabling styling differentiation between name and email mentions.

  ### Bug Fixes

  * \[**Comments**]: Fixed comment composer click-to-focus stealing focus from buttons, links, inputs, and action components rendered inside the composer.
</Update>

<Update label="5.0.2-beta.15" description="April 8, 2026">
  ### New Features

  * \[**Notifications**]: Added granular primitive components for `VeltNotificationsPanel` and `VeltNotificationsTool`, including — header tabs, content lists, settings accordion, skeleton, and more. Each primitive is standalone (not nested inside another) and supports `variant`, `darkMode`, `shadowDom`, and `parentLocalUIState` props. [Learn more →](/ui-customization/features/async/notifications/notifications-primitives)

  * \[**Workspace REST API**]: Added 20 endpoints to create and manage Velt workspace covering workspace creation, API key management, auth tokens, domain management, email and webhook configuration, and API key metadata. [Learn more →](/api-reference/rest-apis/v2/workspace/create)

  ### Bug Fixes

  * \[**Comments**]: Fixed iframes capturing pointer events during comment pin and area pin drag/resize operations. A `velt-pin-dragging` class is now added to `body` during drag to prevent iframe event interception.
  * \[**Comments**]: Fixed hidden comment composer instances inside `velt-if` conditions overwriting the shared active textarea reference.
  * \[**Access Control**]: Fixed permission cleanup on document unset not including the document context, causing access revocation to be incorrectly scoped.
</Update>

<Update label="5.0.2-beta.14" description="April 03, 2026">
  ### New Features

  * \[**Comments**]: Added a `pinDrag` / `pin-drag` prop on `<VeltComments>` / `<velt-comments>` (default: `true`) and `enablePinDrag()` / `disablePinDrag()` API methods to lock or unlock comment and area pin dragging at runtime. When disabled, pins become non-draggable for all users regardless of ownership.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      import { VeltComments } from '@veltdev/react';

      // Using prop
      <VeltComments pinDrag={false} />

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.disablePinDrag();
      commentElement.enablePinDrag();
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <!-- Using attribute -->
      <velt-comments pin-drag="false"></velt-comments>
      ```

      ```js theme={null}
      // Using API methods
      const commentElement = Velt.getCommentElement();
      commentElement.disablePinDrag();
      commentElement.enablePinDrag();
      ```
    </Tab>
  </Tabs>

  ### Bug Fixes

  * \[**Recorder**]: `getUserMedia` calls now use `{ exact: deviceId }` instead of `{ ideal: deviceId }` when a user has selected a specific camera or microphone. The browser will now reject with `OverconstrainedError` if the chosen device is unavailable rather than silently falling back to a different device.
</Update>

<Update label="5.0.2-beta.13" description="April 1, 2026">
  ### New Features

  * \[**Presence**]: New `addUser()` and `removeUser()` methods on the Presence element allow you to programmatically add or remove custom users (AI agents, bots, system accounts) in the presence list. Set `localOnly: true` to restrict the change to the current client without persisting to the database. [Learn More →](/realtime-collaboration/presence/customize-behavior#adduser)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      const presenceElement = client.getPresenceElement();

      // Add a persistent custom user
      presenceElement.addUser({ user: { userId: 'ai-agent-1', name: 'AI Assistant' } });

      // Add a local-only user (not persisted)
      presenceElement.addUser({ user: { userId: 'local-bot' }, localOnly: true });

      // Remove users
      presenceElement.removeUser({ user: { userId: 'ai-agent-1' } });
      presenceElement.removeUser({ user: { userId: 'local-bot' }, localOnly: true });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      const presenceElement = Velt.getPresenceElement();

      presenceElement.addUser({ user: { userId: 'ai-agent-1', name: 'AI Assistant' } });
      presenceElement.addUser({ user: { userId: 'local-bot' }, localOnly: true });

      presenceElement.removeUser({ user: { userId: 'ai-agent-1' } });
      presenceElement.removeUser({ user: { userId: 'local-bot' }, localOnly: true });
      ```
    </Tab>
  </Tabs>

  * \[**Rewriter**]: New public API methods on the Rewriter element: `on('textSelected')` subscribes to text selection events, `askAi()` sends a prompt to any AI model via Velt's api, `replaceText()` swaps selected DOM text with a new string, and `addComment()` creates a comment annotation on the selection. [Learn More →](/ai/rewriter/setup)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      const rewriterElement = client.getRewriterElement();
      rewriterElement.enableRewriter();

      rewriterElement.on('textSelected').subscribe(async (event) => {
        const aiResponse = await rewriterElement.askAi({
          model: 'gemini-2.5-flash',
          prompt: 'Make it more formal',
          selectedText: event.text,
        });
        if (aiResponse.success) {
          await rewriterElement.replaceText({ text: aiResponse.text, event });
        }
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      const rewriterElement = Velt.getRewriterElement();
      rewriterElement.enableRewriter();

      rewriterElement.on('textSelected').subscribe(async (event) => {
        const aiResponse = await rewriterElement.askAi({
          model: 'gemini-2.5-flash',
          prompt: 'Make it more formal',
          selectedText: event.text,
        });
        if (aiResponse.success) {
          await rewriterElement.replaceText({ text: aiResponse.text, event });
        }
      });
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Three new standalone Comment Bubble primitive components — `VeltCommentBubbleAvatar`, `VeltCommentBubbleCommentsCount`, and `VeltCommentBubbleUnreadIcon` — can be used independently to compose custom comment bubble UIs.

  ### Improvements

  * \[**Comments**]: Several Sidebar V2 improvements to bring it in line with Sidebar V1.

  ### Bug Fixes

  * \[**All Components**]: `shadow-dom="true"` now works correctly in wireframe mode across 20+ components that previously ignored the attribute when wireframes were present.

  * \[**Comments**]: The comment dialog wireframe host element is now conditionally wrapped in shadow DOM only when explicitly enabled, preventing style leakage between the host page and wireframe content.

  * \[**Comments**]: Fixed a race condition where the wireframe subscription could fire before `ngAfterViewInit`, causing the wireframe host element to be unresolved. The component now queues pending wireframe elements and processes them after view init.
</Update>

<Update label="5.0.2-beta.12" description="April 1, 2026">
  ### New Features

  * \[**Recorder**]: Added `recordingDoneLocal` event on the recorder element, emitted immediately after a recording annotation is saved locally — before cloud upload and transcription begin. The attachment URL in the payload is a blob URL at this point; the permanent CDN URL arrives later via `recordingDone`. Use this event to dismiss recording UI or show a "processing" indicator without waiting for cloud round-trips. [Learn More →](/async-collaboration/recorder/customize-behavior#on)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      const recorderElement = client.getRecorderElement();

      recorderElement.on('recordingDoneLocal').subscribe((data) => {
        // data.assets[0].url is a blob URL at this point
        console.log('recordingDoneLocal', data);
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      const recorderElement = Velt.getRecorderElement();

      recorderElement.on('recordingDoneLocal').subscribe((data) => {
        // data.assets[0].url is a blob URL at this point
        console.log('recordingDoneLocal', data);
      });
      ```
    </Tab>
  </Tabs>

  * \[**Presence**]: Added `POST /v2/presence/add`, `POST /v2/presence/update`, and `POST /v2/presence/delete` endpoints for server-side management of user presence records (online/away/offline) per document. Use these to show AI agents or bots as live participants while they work on a document — for example, surfacing an "AI Editor" avatar alongside human collaborators. Each entry is keyed by a namespaced user ID derived from `userId` and `apiKey`. [Learn More →](/api-reference/rest-apis/v2/presence/add-presence)

  * \[**AI Rewriter**]: Added `POST /v2/rewriter/ask-ai` endpoint that proxies text generation requests to OpenAI, Anthropic, or Gemini using the customer's own encrypted API keys stored in workspace config (`aiModelsConfig`). The provider is resolved automatically from the model name prefix (`gpt-*`/`o1-*`/`o3-*`/`o4-*` → OpenAI, `claude-*` → Anthropic, `gemini-*` → Gemini). [Learn More →](/api-reference/rest-apis/v2/rewriter/ask-ai)
</Update>

<Update label="5.0.2-beta.11" description="March 26, 2026">
  ### New Features

  * \[**Core**]: Added `proxyConfig` to route all Velt traffic through your own reverse proxies via a unified `ProxyConfig` object. [Learn more →](/api-reference/sdk/models/data-models#proxyconfig)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      import { VeltProvider } from '@veltdev/react';

      <VeltProvider apiKey="YOUR_API_KEY" config={{
        proxyConfig: {
          cdnHost: 'cdn-proxy.example.com',
          apiHost: 'api-proxy.example.com',
          v2DbHost: 'persistence-db-proxy.example.com',
          v1DbHost: 'rtdb-proxy.example.com',
          storageHost: 'storage-proxy.example.com',
          authHost: 'auth-proxy.example.com',
        },
      }}>
        <App />
      </VeltProvider>
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      import { initVelt } from '@veltdev/client';

      const client = await initVelt('YOUR_API_KEY', {
      proxyConfig: {
      cdnHost: 'cdn-proxy.example.com',
      apiHost: 'api-proxy.example.com',
      v2DbHost: 'persistence-db-proxy.example.com',
      v1DbHost: 'rtdb-proxy.example.com',
      storageHost: 'storage-proxy.example.com',
      authHost: 'auth-proxy.example.com',
      },
      });

      ```
    </Tab>
  </Tabs>

  * \[**UI Customization**]: Six comment component families — Comment Pin, Comment Bubble, Text Comment, Inline Comments Section, Multi-Thread Comment Dialog, and Sidebar Button — have been migrated to the V2 primitive architecture. Each family now exposes standalone primitive components for fine-grained wireframe customization. All primitives accept a `defaultCondition` / `default-condition` prop to control whether the built-in render condition is evaluated. [Learn more →](/ui-customization/features/async/comments/comment-dialog-primitives/overview)

  * \[**Recorder**]: Added a `recorder.done` webhook event (available on both V1 `WebhookService` and V2 `WebhooksV2Service`) fired when a recording finishes processing. The event payload includes recording assets, transcription data (segments, VTT file URL, content summary), and action user metadata. Enable or disable it per workspace via `triggers.recorder.done` (defaults to `true`).

  * \[**REST API**]: Added `GET /v2/recordings/get` for retrieving recordings with optional `recordingIds` filtering and standard pagination. [Learn more →](/api-reference/rest-apis/v2/recordings/get-recordings)

  ### Improvements

  * \[**UI Customization**]: Added `annotations` and `allAnnotations` as shorthand wireframe data variables, resolving to the component's full annotation arrays. Use `<velt-data field="annotations">` or `<velt-data field="allAnnotations">` in wireframe templates to bind list-level data (e.g., for Inline Comments Section).

  ### Bug Fixes

  * \[**Comments**]: Fixed area comment pins and resize handles rendering at incorrect positions inside CSS-transformed containers (e.g., scaled canvases). The offset calculation now uses post-transform screen-space coordinates for resize operations.

  * \[**Core**]: Fixed `AttachmentDataProvider.delete()` receiving internal SDK metadata fields (`commentAnnotationId`, `attachmentId`). The callback now receives only `apiKey`, `documentId`, `organizationId`, and `folderId`.

  * \[**UI Customization**]: Fixed `<velt-data field="context.someProperty">` resolving from the wrong source in Inline Comments Section wireframes. It now correctly resolves from `parentLocalUIState.context`. For annotation-level context in other components, use `field="annotation.context.someProperty"`.
</Update>

<Update label="5.0.2-beta.10" description="March 23, 2026">
  ### New Features

  * \[**Activity Logs**]: Added prebuilt components for Activity Logs feature. It's a filterable timeline of all Velt activities (custom, comments, reactions, recorder, CRDT) grouped by date. Supports `darkMode`, `useDummyData`, and `variant` props, and is fully customizable via primitives and wireframes. [Learn more →](/async-collaboration/activity/overview)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      import { VeltActivityLog } from '@veltdev/react';

      <VeltActivityLog darkMode={false} useDummyData={true} />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-activity-log use-dummy-data="true"></velt-activity-log>
      ```
    </Tab>
  </Tabs>

  * \[**Activity Logs**]: `targetEntityId` is now optional for non-`custom` `featureType` values (`comment`, `reaction`, `recorder`, `crdt`) when creating activities. An optional `id` field is also supported to use as the DB record ID for idempotent writes. `targetEntityId` remains required when `featureType` is `custom`. [Learn more →](/async-collaboration/activity/overview)
  * \[**Access Control**]: [`getUserPermissions`](/api-reference/sdk/api/api-methods#getuserpermissions) response entries for folders and documents now include an `accessType` field (`public`, `restricted`, `organizationPrivate`). Documents in folders inherit their folder's `accessType`. The field is also present on `PERMISSION_DENIED` entries so consumers can inspect the effective access type even when access is denied. Organization-level access is now verified via DB permission rules rather than array membership checks.
  * \[**Self-Host Data**]: Added `activity` field to [`VeltDataProvider`](/api-reference/sdk/models/data-models#veltdataprovider) accepting an [`ActivityAnnotationDataProvider`](/api-reference/sdk/models/data-models#activityannotationdataprovider). When configured via [`setDataProviders()`](/api-reference/sdk/api/api-methods#setdataproviders), Velt strips PII from activity records before writing to its backend and routes it to your storage; on read, resolved data is merged back transparently. [Learn more →](/self-host-data/activity)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using API methods
      client.setDataProviders({
        activity: {
          get: async (request) => {
            const data = await myBackend.getActivities(request.activityIds);
            return { data, success: true, statusCode: 200 };
          },
          save: async (request) => {
            await myBackend.saveActivities(request.activity);
            return { data: undefined, success: true, statusCode: 200 };
          },
          config: { resolveTimeout: 60000, fieldsToRemove: ['customSensitiveField'] },
        },
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      Velt.setDataProviders({
        activity: {
          get: async (request) => {
            const data = await myBackend.getActivities(request.activityIds);
            return { data, success: true, statusCode: 200 };
          },
          save: async (request) => {
            await myBackend.saveActivities(request.activity);
            return { data: undefined, success: true, statusCode: 200 };
          },
          config: { resolveTimeout: 60000, fieldsToRemove: ['customSensitiveField'] },
        },
      });
      ```
    </Tab>
  </Tabs>

  ### Improvements

  * \[**Core**]: [`SetDocumentsRequestOptions`](/api-reference/sdk/models/data-models#setdocumentsrequestoptions) gains two optional fields: `debounceTime` (per-call override for the debounce window in ms) and `optimisticPermissions` (set to `false` to await permission validation before returning). Existing calls without these fields are unaffected.

  * \[**Access Control**]: Document `accessType` in [`getUserPermissions`](/api-reference/sdk/api/api-methods#getuserpermissions) responses now correctly reflects folder inheritance — documents inside folders report the folder's `accessType` rather than their own `docIamConfig.accessType`.

  * \[**Activity Logs**]: [`ActivityRecord`](/api-reference/sdk/models/data-models#activityrecord) gains an optional `isActivityResolverUsed?: boolean` field, set to `true` when PII was stripped by the activity resolver. Use this to show a loading state while re-hydration is pending.

  ### Bug Fixes

  * \[**Activity Logs**]: Fixed [`getAllActivities()`](/api-reference/sdk/api/api-methods#getallactivities) returning raw server-side records without user name/avatar hydration, resolver re-hydration, or internal field stripping. The stream now pipes through the full client transform pipeline.

  * \[**Core**]: Fixed a race condition where a pending revoke timer from a prior [`setDocuments()`](/api-reference/sdk/api/api-methods#setdocuments) call could fire after a subsequent grant, silently removing access. Pending revokes are now cancelled when a new grant is issued for the same resource.

  * \[**Core**]: Fixed [`setDocuments()`](/api-reference/sdk/api/api-methods#setdocuments) ignoring per-call `options.debounceTime`. The debounce window is now evaluated at emit time, so passing `debounceTime: 1000` correctly debounces at 1 second instead of the global 5-second default.

  * \[**Access Control**]: Fixed temporary permissions silently overwriting valid regular permissions for documents, folders, and organizations. Guards now prevent overwrites unless the existing entry is already denied or errored, while `PERMISSION_DENIED` entries can still be correctly upgraded by valid temporary grants.

  * \[**Access Control**]: Fixed silent failures in [`getUserPermissions`](/api-reference/sdk/api/api-methods#getuserpermissions) — errors in `getPermissionsForResources` and `fetchDocumentDataForAccessType` are now thrown and logged via `console.error` in all catch blocks instead of returning empty objects.
</Update>

<Update label="5.0.2-beta.9" description="March 17, 2026">
  ### New Features

  * \[**UI Customization**]: Added Velt Comments Sidebar V2 — a complete redesign of the Comments Sidebar built on a flat primitive component architecture. Every section of the UI is an independently importable and composable primitive. The V2 sidebar ships with a unified filter model (replacing the legacy `minimalFilter` + `advancedFilters` system), CDK virtual scroll for large comment lists, a focused-thread view, a minimal actions dropdown, and a filter dropdown.
    * All sections are fully wireframe-customizable and available as standalone primitives. See the [UI Customization docs](/ui-customization/features/async/comments/comment-sidebar-components-v2) for the complete wireframe structure and list of `VeltCommentsSidebarV2*` / `velt-comments-sidebar-*-v2` components.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      <VeltCommentsSidebarV2
        pageMode={false}
        focusedThreadMode={false}
        readOnly={false}
        position="right"
        variant="sidebar"
        forceClose={true}
        onSidebarOpen={(data) => console.log('sidebar opened', data)}
        onSidebarClose={(data) => console.log('sidebar closed', data)}
        onCommentClick={(data) => console.log('comment clicked', data)}
        onCommentNavigationButtonClick={(data) => console.log('nav button clicked', data)}
      />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments-sidebar-v2
        page-mode="false"
        focused-thread-mode="false"
        read-only="false"
        position="right"
        variant="sidebar"
        force-close="true"
      ></velt-comments-sidebar-v2>
      ```
    </Tab>
  </Tabs>

  * \[**Notifications**]: Added self hosting options for custom notifications. When configured, Velt calls your `get` and `delete` handlers to fetch and remove notification data from your own backend instead of Velt's storage. The resolution pipeline runs notification → user → comment, ensuring resolver-enriched user references are available when the user resolver runs. Only notifications with `notificationSource === 'custom'` are routed through the resolver. [Learn more →](/self-host-data/notifications)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using API methods
      client.setDataProviders({
        notification: {
          get: async ({ organizationId, notificationIds }) => {
            const results = await myBackend.fetchNotifications(organizationId, notificationIds);
            return { status: 200, data: results };
          },
          delete: async ({ notificationId, organizationId }) => {
            await myBackend.deleteNotification(organizationId, notificationId);
            return { status: 200, data: undefined };
          },
          config: {
            resolveTimeout: 30000,
            getRetryConfig: { retryCount: 2, retryDelay: 500, revertOnFailure: false },
          },
        },
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      Velt.setDataProviders({
        notification: {
          get: async ({ organizationId, notificationIds }) => {
            const results = await myBackend.fetchNotifications(organizationId, notificationIds);
            return { status: 200, data: results };
          },
          delete: async ({ notificationId, organizationId }) => {
            await myBackend.deleteNotification(organizationId, notificationId);
            return { status: 200, data: undefined };
          },
          config: {
            resolveTimeout: 30000,
            getRetryConfig: { retryCount: 2, retryDelay: 500, revertOnFailure: false },
          },
        },
      });
      ```
    </Tab>
  </Tabs>

  The [`Notification`](/api-reference/sdk/models/data-models#notification) model gains `isNotificationResolverUsed?: boolean` to detect resolver-enriched notifications downstream.

  ### Improvements

  * \[**UI Customization**]: `VeltCommentsSidebar` now accepts a `version` prop. Setting `version="2"` routes the existing `<velt-comments-sidebar>` tag to the full V2 implementation, forwarding all props (`pageMode`, `focusedThreadMode`, `readOnly`, `embedMode`, `floatingMode`, `position`, `variant`, `forceClose`).

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      <VeltCommentsSidebar version="2" />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments-sidebar version="2"></velt-comments-sidebar>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `additionalFields` to `CommentAnnotationDataProvider.config`. Unlike `fieldsToRemove` (which strips fields from Velt's DB), `additionalFields` are copied to your resolver endpoint but retained in Velt's storage — enabling data replication without removal.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using API methods
      client.setDataProviders({
        comment: {
          get: async (request) => { /* ... */ },
          config: {
            fieldsToRemove: ['sensitiveField'],
            additionalFields: ['metadataField'],
          },
        },
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      Velt.setDataProviders({
        comment: {
          get: async (request) => { /* ... */ },
          config: {
            fieldsToRemove: ['sensitiveField'],
            additionalFields: ['metadataField'],
          },
        },
      });
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `agentFields?: string[]` to [`CommentRequestQuery`](/api-reference/sdk/models/data-models#commentrequestquery) to filter comment count queries to annotations where `agent.agentFields` contains any of the provided values. When `agentFields` is set, the unread count query is skipped and treated as equal to the total count.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.getCommentAnnotationCount({
        organizationId: 'org-123',
        agentFields: ['agent-1', 'agent-2'],
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      const commentElement = Velt.getCommentElement();
      commentElement.getCommentAnnotationCount({
        organizationId: 'org-123',
        agentFields: ['agent-1', 'agent-2'],
      });
      ```
    </Tab>
  </Tabs>

  ### Bug Fixes

  * \[**Comments**]: Fixed `updateVisibility()` silently overwriting custom metadata in the annotation's [`context`](/api-reference/sdk/models/data-models#context) field. The internal `updateCommentAnnotationContext` call now uses `{ merge: true }`, preserving all custom fields set via `addCommentAnnotation` or `updateCommentAnnotationContext`.

  * \[**Comments**]: Fixed `updateCommentAnnotationContext` dropping the `accessFields` system field when called without `{ merge: true }`. All system-managed context fields (`access`, `accessFields`) are now preserved when they are not explicitly provided in the incoming context object.

  * \[**UI Customization**]: Fixed wireframe components' computed signals (`shouldShow`, `default-condition`) evaluating against default/empty values on first render. The fix defers view attachment until after all inputs are set, so `velt-if` and conditional wireframe customizations work correctly on first render.
</Update>

<Update label="5.0.2-beta.8" description="March 12, 2026">
  ### New Features

  * \[**Recorder**]: Added `recorder` field on `VeltDataProvider` accepting a `RecorderAnnotationDataProvider`. When configured via `setDataProviders()`, recording PII (user identity, transcription, attachment URLs) is stripped from annotations before being written to Velt's Firestore and routed to your own storage via resolver callbacks or config-based HTTP endpoints. On read, the resolved PII is merged back transparently. [Learn more →](/self-host-data/recordings)

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using API methods
      client.setDataProviders({
        recorder: {
          get: async (request) => { /* fetch PII from your backend */ },
          save: async (request) => { /* save PII to your backend */ },
          delete: async (request) => { /* delete PII from your backend */ },
          config: {
            getConfig: { url: 'https://api.yourapp.com/recordings/get' },
            saveConfig: { url: 'https://api.yourapp.com/recordings/save' },
            deleteConfig: { url: 'https://api.yourapp.com/recordings/delete' },
            additionalFields: ['customField1', 'customField2'],
          },
          uploadChunks: false,
          storage: {
            save: async (request) => { /* upload file to your storage */ },
            delete: async (request) => { /* delete file from your storage */ },
          },
        },
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```js theme={null}
      Velt.setDataProviders({
        recorder: {
          get: async (request) => { /* fetch PII from your backend */ },
          save: async (request) => { /* save PII to your backend */ },
          delete: async (request) => { /* delete PII from your backend */ },
          config: {
            getConfig: { url: 'https://api.yourapp.com/recordings/get' },
            saveConfig: { url: 'https://api.yourapp.com/recordings/save' },
            deleteConfig: { url: 'https://api.yourapp.com/recordings/delete' },
            additionalFields: ['customField1', 'customField2'],
          },
          uploadChunks: false,
          storage: {
            save: async (request) => { /* upload file to your storage */ },
            delete: async (request) => { /* delete file from your storage */ },
          },
        },
      });
      ```
    </Tab>
  </Tabs>

  Use `isRecorderResolverUsed` on [`RecorderAnnotation`](/api-reference/sdk/models/data-models#recorderannotation) to show a loading state while PII is being fetched, and `isUrlAvailable` to show an upload progress indicator while the recording URL is still a local blob.

  * \[**REST API**]: Added two endpoints for asynchronous document migration. [Migrate Documents ](/api-reference/rest-apis/v2/documents/migrate-documents) initiates a migration that migrates all data from one document to another. [Migrate Documents Status →](/api-reference/rest-apis/v2/documents/migrate-documents-status) polls progress using the `migrationId` returned by the initiate call.

  ### Improvements

  * \[**Recorder**]: Added `RECORDER_RESOLVER` to `CoreEventTypesMap` with a full 3-stage lifecycle (request formed → triggered → result/error) for get, save, and delete resolver operations. Consistent with the existing comment, reaction, and attachment resolver event patterns.

  * \[**Core**]: The `get`, `save`, and `delete` methods on `CommentAnnotationDataProvider`, `ReactionAnnotationDataProvider`, and `AttachmentDataProvider` are now optional. Developers using config-based URL endpoints (`config.getConfig`, `config.saveConfig`, `config.deleteConfig`) no longer need to supply placeholder callbacks. `ResolverConfig` also gains a new `additionalFields?: string[]` property for including custom fields in resolver payloads.

  * \[**Core**]: Added `attachmentScope?: string` to `UploadFileOptions` to specify which scoped `AttachmentDataProvider` to route an upload to (e.g., `'default'` for comment attachments, `'recorder'` for recording files). Defaults to `'default'` when omitted.

  ### Bug Fixes

  * \[**Comments**]: Fixed text selection highlighting not activating for submitted (non-draft) comment annotations. The `textClicked` flag is now set correctly for all selected annotations when `last` is `true`, not only drafts.

  * \[**REST API**]: Fixed "Transaction too big" errors during document migration by reducing Firestore batch size from 500 to 100 in subcollection copy, notification copy, and user doc config operations. Migrations involving documents with large notification or comment payloads will no longer fail.
</Update>

<Update label="5.0.2-beta.7" description="March 10, 2026">
  ### New Features

  * \[**Activity Logs**]: A complete, real-time record of everything that happens across your product. Velt automatically generates activity log records for Comments, Reactions, Recorder, and CRDT features, and you can push custom events (deployments, status changes, escalations) into the same timeline using `createActivity()`. Subscribe org-wide for admin dashboards, filter by document for inline timelines, or scope by feature type to reduce noise. [Learn more →](/async-collaboration/activity/overview)

  * \[**CRDT**]: Added `setActivityDebounceTime(time)` on `CrdtElement` to control how long editor keystrokes are batched before flushing a single activity log record. Default: 10 minutes. Minimum: 10 seconds. [Documentation →](/realtime-collaboration/crdt/setup/core#activity-log-debounce)

  * \[**Activity Logs REST API**]: Added CRUD endpoints for activity log records (`/v2/activities/get`, `/v2/activities/add`, `/v2/activities/update`, `/v2/activities/delete`). Also added a `triggerActivities` flag on `POST /v2/commentannotations/add` to auto-create activity log records when comments are added. [REST API Reference →](/api-reference/rest-apis/v2/activities/get-activities)

  ### Bug Fixes

  * \[**Comments**]: Fixed temp annotation context being cleared prematurely during comment submission in composer mode, preventing stale context from affecting subsequent comment operations.
</Update>

<Update label="5.0.2-beta.6" description="March 9, 2026">
  ### Bug Fixes

  * \[**Comments**]: Fixed sidebar filters restored from `sessionStorage` being overwritten by the initial empty emission on page reload.

  * \[**Comments**]: Fixed DOM annotation visibility diverging from sidebar filter state when filters were restored from session storage or set via defaults.

  * \[**Comments**]: Fixed `mergeClientFilters` not handling `null` or empty `{}` filter objects correctly, which prevented filters from being cleared.

  * \[**Comments**]: Fixed autocomplete hotkey button inserting a duplicate character when the hotkey already existed at the cursor position.

  * \[**Comments**]: Fixed autocomplete cursor save/restore failing inside shadow DOM.

  * \[**Comments**]: Fixed partial `@mention` replacement when one tagged user's name is a prefix of another (e.g., `@Alice` corrupting `@AliceSmith`).

  * \[**Comments**]: Fixed multi-thread comment annotations not being filtered by sidebar filter state in the DOM.
</Update>

<Update label="5.0.2-beta.5" description="March 2, 2026">
  ### New Features

  * \[**UI Customization**]: Added 13 standalone autocomplete primitive components (`VeltAutocompleteOption`, `VeltAutocompleteChip`, `VeltAutocompleteEmpty`, etc.) for building fully custom autocomplete UIs without requiring the full `<velt-autocomplete>` panel. [Learn More →](/ui-customization/features/async/comments/comment-dialog-primitives/overview#veltautocomplete)

  * \[**UI Customization**]: Added `multiSelect`, `selectedFirstOrdering`, `readOnly`, `inline`, and `contacts` props to the [`VeltAutocomplete`](/ui-customization/features/async/comments/comment-dialog-primitives/overview#veltautocomplete) panel component.

  ### Improvements

  * \[**Access Control**]: [`AccessRequestEvent`](/api-reference/sdk/models/data-models#accessrequestevent) and [`SEMEvent`](/api-reference/sdk/models/data-models#semevent) now include `totalUsers`, `presenceSnippylyUserIds`, and `presenceClientUserIds` fields for real-time presence context.

  * \[**Analytics**]: `COMMENT_ADDED` and `COMMENT_STATUS_CHANGED` events now include `commentAnnotationCreatedAt`, `taggedClientUserIds`, and `taggedSnippylyUserIds`.

  * \[**Analytics**]: New `REACTION_ADDED` and `REACTION_DELETED` events fire with `annotationId`, `commentId`, `reactionId`, and `commentMode`.

  * \[**Analytics**]: Added `CRDT_DATA_UPDATED` analytics event with deduplication.

  ### Bug Fixes

  * \[**Core**]: Fixed `client.setDocuments()` triggering duplicate concurrent initializations when called multiple times in rapid succession with identical document IDs and options.

  * \[**Comments**]: Replaced custom `VeltCommentDialogVisibilityBannerDropdownContentUserPicker` sub-components with the shared autocomplete component.
</Update>

<Update label="5.0.2-beta.4" description="February 24, 2026">
  ### New Features

  * \[**Comments**]: Added Private Comments UI. Enable it with `visibilityOptions`. Users see a persistent banner below the composer with four visibility levels — `public`, `organization-private`, `restricted-self`, and `restricted` — and an inline user-picker (search, avatar list, multi-select) when `restricted` is chosen. Visibility can also be changed after submission from the thread options menu.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      <VeltComments visibilityOptions={true} />

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.enableVisibilityOptions();
      commentElement.disableVisibilityOptions();
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments visibility-options="true"></velt-comments>

      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.enableVisibilityOptions();
      commentElement.disableVisibilityOptions();
      </script>
      ```
    </Tab>
  </Tabs>

  The banner surface is fully wireframe-customizable via the new [`VeltCommentDialogWireframe.VisibilityBanner.*` / `velt-comment-dialog-visibility-banner-*`](/ui-customization/features/async/comments/comment-dialog-components#visibility-banner) family.

  * \[**Comments**]: Added `AddCommentRequest.visibility` field of type `CommentVisibilityConfig` to set comment visibility at creation time. Previously visibility could only be set after creation via `updateVisibility()`.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      const commentElement = client.getCommentElement();
      commentElement.addComment({
        annotationId: 'annotation-id',
        comment: { text: 'Visible only to selected users' },
        visibility: {
          type: 'restricted',
          userIds: ['user1', 'user2'],
        },
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.addComment({
        annotationId: 'annotation-id',
        comment: { text: 'Visible only to selected users' },
        visibility: {
          type: 'restricted',
          userIds: ['user1', 'user2'],
        },
      });
      </script>
      ```
    </Tab>
  </Tabs>

  ### Improvements

  * \[**Comments**]: `VisibilityOptionClickedEvent` now includes an optional `users` field containing the list of selected users when `visibility === 'restricted'`. Previously, the event only reported the visibility type string.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      const eventData = useCommentEventCallback('visibilityOptionClicked');
      useEffect(() => {
        if (eventData) {
          console.log(eventData.visibility); // 'public' | 'organization-private' | 'restricted-self' | 'restricted'
          console.log(eventData.users);      // populated when visibility === 'restricted'
        }
      }, [eventData]);
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.on('visibilityOptionClicked').subscribe((event) => {
        console.log(event.visibility);
        console.log(event.users);
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: `CommentSaveTriggeredEvent` now includes a `commentAnnotation` field with the full annotation object at save time. Handlers for `onCommentSaveTriggered` can now access comment content, assignment, and visibility state without a separate lookup.
</Update>

<Update label="5.0.2-beta.3" description="February 24, 2026">
  ### New Features

  * \[**Comments**]: Added [`AnonymousUserDataProvider`](/self-host-data/users#anonymous-user-resolution) to resolve email → `userId` mappings for users who are tagged by email in comments but are not part of the contact list. When a comment is saved and a tagged contact or `to` recipient has an email but no `userId`, the SDK calls the registered provider to look up the `userId` and backfills it into the comment data before persisting.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Option 1: standalone method
      client.setAnonymousUserDataProvider({
        resolveUserIdsByEmail: async (request) => {
          // request.emails: string[]
          const map = await yourBackend.lookupUserIds(request.emails);
          return { statusCode: 200, success: true, data: map };
        },
        config: {
          resolveTimeout: 5000,
          getRetryConfig: { retryCount: 2, retryDelay: 300 },
        },
      });

      // Option 2: via setDataProviders
      client.setDataProviders({
        anonymousUser: {
          resolveUserIdsByEmail: async (request) => { /* ... */ },
        },
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      // Option 1: standalone method
      Velt.setAnonymousUserDataProvider({
        resolveUserIdsByEmail: async (request) => {
          // request.emails: string[]
          const map = await yourBackend.lookupUserIds(request.emails);
          return { statusCode: 200, success: true, data: map };
        },
        config: {
          resolveTimeout: 5000,
          getRetryConfig: { retryCount: 2, retryDelay: 300 },
        },
      });

      // Option 2: via setDataProviders
      Velt.setDataProviders({
        anonymousUser: {
          resolveUserIdsByEmail: async (request) => { /* ... */ },
        },
      });
      </script>
      ```
    </Tab>
  </Tabs>

  Results are cached in memory for the session; emails already resolved are not re-fetched. On timeout or error, the provider returns whatever was cached and proceeds without the unresolved user IDs. If no [`UserDataProvider`](/api-reference/sdk/models/data-models#userdataprovider) is configured, resolved user IDs are automatically backfilled to the organization database as stub user records (`{ userId, name: email, email }`). If a `UserDataProvider` is already configured, the org DB backfill is skipped.

  ### Improvements

  * \[**Comments**]: Comment annotations now store a top-level [`visibilityConfig`](/api-reference/sdk/models/data-models#commentannotationvisibilityconfig) field (`{ type, organizationId, userIds }`) alongside the existing `context.accessFields` tokens. This makes the original visibility setting inspectable without reversing hashed tokens. Annotations without an explicit visibility setting default to `{ type: 'public' }`. The `context.accessFields` tokens continue to govern actual access control.

  * \[**Comments**]: When setting restricted visibility, the current user's `userId` is now automatically appended to `userIds` (deduplicated) if not already present. Existing explicit `userIds` lists are preserved. This applies across [`enablePrivateMode`](/async-collaboration/comments/customize-behavior#enableprivatemode), [`addCommentAnnotation`](/async-collaboration/comments/customize-behavior#addcommentannotation), and [`updateVisibility`](/async-collaboration/comments/customize-behavior#updatevisibility).
</Update>

<Update label="5.0.2-beta.2" description="February 24, 2026">
  ### New Features

  * \[**Comments**]: Added a `commentSaveTriggered` event that fires immediately when a user clicks the save button, before the async save completes. Use it for immediate pre-save feedback alongside the existing `commentSaved` event, which continues to fire after the save finishes.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const event = useCommentEventCallback('commentSaveTriggered');

      useEffect(() => {
        if (event) {
          console.log('Save triggered for annotation:', event.annotationId);
        }
      }, [event]);
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.on('commentSaveTriggered').subscribe((event) => {
        console.log('Save triggered for annotation:', event.annotationId);
      });
      </script>
      ```
    </Tab>
  </Tabs>

  The exported [`CommentSaveTriggeredEvent`](/api-reference/sdk/models/data-models#commentsavetriggeredevent) interface has the following shape:

  ```ts theme={null}
  interface CommentSaveTriggeredEvent {
    annotationId: string;
    metadata: VeltEventMetadata;
  }
  ```

  ### Improvements

  * \[**Comments**]: The sidebar now automatically returns to the full comment list when all annotations are deselected while in focused thread mode.

  * \[**Comments**]: Page scroll position is now preserved when navigating to a highlighted annotation from the sidebar.
</Update>

<Update label="5.0.2-beta.1" description="February 20, 2026">
  ### New Features

  * \[**Comments**]: Added a visibility dropdown to the comment composer, letting users set a comment's visibility to `public` or `private` before submitting. Disabled by default; enable it via the `visibilityOptionDropdown` prop or API methods. A `visibilityOptionClicked` event fires on each selection, emitting the annotation ID, full [`CommentAnnotation`](/api-reference/sdk/models/data-models#commentannotation), and chosen visibility.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Declarative prop
      <VeltComments visibilityOptionDropdown={true} />

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.enableVisibilityOptionDropdown();
      commentElement.disableVisibilityOptionDropdown();

      // Listening to the event
      import { useCommentEventCallback } from '@veltdev/react';
      const visibilityEvent = useCommentEventCallback('visibilityOptionClicked');

      useEffect(() => {
        if (visibilityEvent) {
          console.log('Visibility selected:', visibilityEvent.visibility);
          console.log('Annotation ID:', visibilityEvent.annotationId);
        }
      }, [visibilityEvent]);
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments visibility-option-dropdown="true"></velt-comments>

      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.enableVisibilityOptionDropdown();
      commentElement.disableVisibilityOptionDropdown();

      commentElement.on('visibilityOptionClicked').subscribe((event) => {
        console.log('Visibility selected:', event.visibility);
        console.log('Annotation ID:', event.annotationId);
      });
      </script>
      ```
    </Tab>
  </Tabs>

  To customize the dropdown via wireframes, you can refer to the [wireframe documentation](/ui-customization/features/async/comments/comment-dialog-components#visibility-dropdown-composer).

  ### Bug Fixes

  * \[**Comments Sidebar**]: Fixed the "(This page)" indicator not appearing for locations identified by `locationName` instead of `id`. The sidebar now matches the current location by `id`, `locationName`, or legacy `locationId`, in that order.
</Update>

<Update label="5.0.1" description="February 18, 2026">
  ### Bug Fixes

  * \[**Comments**]: Fixed temporary composer annotations (prefixed `TEMP_COMPOSER_`) being inadvertently persisted to the database. Guards were added so temp annotations remain client-only and are never written to the database.

  * \[**Comments**]: Fixed the comment dialog calling `markAsRead()` for `TEMP_COMPOSER_` annotations that do not yet exist in the database. The call is now guarded so only persisted annotations are marked read when a pin is selected.

  * \[**Comments Sidebar**]: Fixed the sidebar failing to identify the current page's annotation group when a location is configured with `locationName` but no `id`. The sidebar now resolves the current location using `location.id` first, then `location.locationName`, then alphabetical order.

  * \[**Notifications**]: Fixed a race condition where a stale async call completing after a newer one could overwrite the user's saved notification config with defaults.

  * \[**Comments**]: Fixed comment pins being placed at incorrect positions on video timelines whose total duration is not a whole number of seconds. Fractional-second durations are now preserved correctly.
</Update>

<Update label="5.0.1-beta.4" description="February 18, 2026">
  ### New Features

  * \[**Comments**]: Added `enablePrivateMode(config)` and `disablePrivateMode()` methods to set a global visibility policy for all newly created comments. Supports `restricted` (user-list), `organizationPrivate`, and `public` visibility types via the new [`PrivateModeConfig`](/api-reference/sdk/models/data-models#privatemodeconfig) type.

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using API methods
      const commentElement = client.getCommentElement();

      // Restrict all new comments to specific users
      commentElement.enablePrivateMode({ type: 'restricted', userIds: ['user-1', 'user-2'] });

      // Restrict all new comments to the current organization
      commentElement.enablePrivateMode({ type: 'organizationPrivate' });

      // Revert to default public visibility
      commentElement.disablePrivateMode();
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```ts theme={null}
      const commentElement = Velt.getCommentElement();

      // Restrict all new comments to specific users
      commentElement.enablePrivateMode({ type: 'restricted', userIds: ['user-1', 'user-2'] });

      // Restrict all new comments to the current organization
      commentElement.enablePrivateMode({ type: 'organizationPrivate' });

      // Revert to default public visibility
      commentElement.disablePrivateMode();
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added a `commentSaved` event that fires after a comment is persisted to the database, providing a reliable signal for triggering downstream actions. The payload includes the annotation ID, full [`CommentAnnotation`](/api-reference/sdk/models/data-models#commentannotation), and [`VeltEventMetadata`](/api-reference/sdk/models/data-models#velteventmetadata).

  <Tabs>
    <Tab title="React / Next.js">
      ```tsx theme={null}
      // Using Hooks
      import { useCommentEventCallback } from '@veltdev/react';
      import { useEffect } from 'react';

      const commentSavedData = useCommentEventCallback('commentSaved');

      useEffect(() => {
        if (commentSavedData) {
          console.log('Comment saved:', commentSavedData.annotationId);
          console.log('Annotation:', commentSavedData.commentAnnotation);
        }
      }, [commentSavedData]);

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.on('commentSaved').subscribe((event) => {
        console.log('Comment saved:', event.annotationId);
        console.log('Annotation:', event.commentAnnotation);
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```ts theme={null}
      const commentElement = Velt.getCommentElement();
      commentElement.on('commentSaved').subscribe((event) => {
        console.log('Comment saved:', event.annotationId);
        console.log('Annotation:', event.commentAnnotation);
      });
      ```
    </Tab>
  </Tabs>

  * \[**Notifications**]: Added opt-in notification delay and batching pipeline for comment notifications. Configure `delayConfig` to hold notifications and suppress delivery if a user views the comment during the delay window. Configure `batchConfig` to accumulate notifications per document or per user before delivery, reducing noise on high-activity content. Webhooks and workflow triggers always fire immediately and are never subject to delay or batching.

  ```json theme={null}
  {
    "delayConfig": {
      "isEnabled": true,
      "delaySeconds": 30
    },
    "batchConfig": {
      "document": {
        "isEnabled": true,
        "batchWindowSeconds": 300,
        "maxActivities": 10
      },
      "user": {
        "isEnabled": true,
        "batchWindowSeconds": 300,
        "maxActivities": 10
      }
    }
  }
  ```

  Set `notificationServiceConfig` in your workspace notification settings in the [Velt Console](https://console.velt.dev/dashboard/config/notification). [Learn more](/async-collaboration/notifications/customize-behavior#notificationserviceconfig)

  * \[**Notifications**]: Webhook payloads now include per-user notification preferences for all involved users. Depending on the config level, payloads carry either `usersOrganizationNotificationsConfig` or `usersDocumentNotificationsConfig`, enabling downstream consumers to route notifications based on each user's settings. See [`UserNotificationsConfig`](/api-reference/sdk/models/data-models#usernotificationsconfig) for the type definition. [Learn more](/webhooks/basic)

  <Tabs>
    <Tab title="Webhook Payload — Org-Level Config">
      ```json theme={null}
      {
        "webhookId": "notif-123",
        "usersOrganizationNotificationsConfig": {
          "user-1": { "inbox": "ALL", "email": "MINE" },
          "user-2": { "inbox": "ALL", "email": "MINE" }
        }
      }
      ```
    </Tab>

    <Tab title="Webhook Payload — Document-Level Config">
      ```json theme={null}
      {
        "webhookId": "notif-456",
        "usersDocumentNotificationsConfig": {
          "user-1": { "inbox": "ALL", "email": "MINE" },
          "user-2": { "inbox": "ALL", "email": "MINE" }
        }
      }
      ```
    </Tab>
  </Tabs>

  * \[**Notifications**]: The `getConfig` notification API now supports a `getOrganizationConfig: true` option to retrieve org-level notification config for a user. `documentIds` is now optional in `setConfig` — omitting it applies the config at the organization level as the user's default for all documents. [setConfig](/api-reference/rest-apis/v2/notifications/set-config) | [getConfig](/api-reference/rest-apis/v2/notifications/get-config)

  <Tabs>
    <Tab title="setConfig — Org-Level">
      ```json theme={null}
      {
        "data": {
          "organizationId": "org-123",
          "userIds": ["user-1", "user-2"],
          "config": {
            "inbox": "ALL",
            "email": "NONE"
          }
        }
      }
      ```
    </Tab>

    <Tab title="getConfig — Org-Level">
      ```json theme={null}
      {
        "data": {
          "organizationId": "org-123",
          "userId": "user-1",
          "getOrganizationConfig": true
        }
      }
      ```
    </Tab>
  </Tabs>

  ### Improvements

  * \[**Comments**]: Added auto-resolution of the organization ID from the current org configuration when `updateVisibility()` or `enablePrivateMode()` is called with `type: 'organizationPrivate'` and no `organizationId` is provided.

  ### Bug Fixes

  * \[**Comments**]: Fixed global dark mode toggling not affecting open comment dialogs. Dialog initialization no longer sets a component-level dark mode override that would block `ThemeService` propagation.

  * \[**Comments**]: Fixed composer placeholder text not reappearing after a user typed and deleted all text. The contenteditable element is now cleared with `innerHTML = ''` to ensure the CSS `:empty::before` placeholder renders correctly.

  * \[**Comments**]: Fixed SVG gradient, clip-path, and filter rendering for duplicate emoji reactions. Each emoji SVG instance now receives unique internal IDs via `EmojiService.uniquifySvgIds()`.

  * \[**Comments**]: Fixed priority dropdown showing an incorrect background color in dark mode when no priority was set.

  * \[**Comments**]: Fixed user migration not updating `{{userId}}` placeholders in `commentText`/`commentHtml` and not remapping `views`/`viewedByUserIds` by internal user key. All userId references in comment content and view tracking are now correctly updated during migration.
</Update>

<Update label="5.0.1-beta.3" description="February 16, 2026">
  ### Improvements

  * \[**Comments**]: Added backward compatibility so V4 custom wireframes using property names `allowAssignment`, `allowEdit`, `allowToggleNotification`, and `allowChangeCommentAccessMode` automatically resolve to their v5 equivalents. No changes to your wireframe code are required when upgrading from v4 to v5.

  * \[**Comments**]: Added edit composer wireframe reuse in comment thread cards so it now applies the same `velt-comment-dialog-composer-wireframe` customization as the reply composer. Previously the edit composer always rendered the default template.

  ### Bug Fixes

  * \[**Comments**]: Fixed custom wireframes for nested comment dialog components — including thread cards, priority/status dropdowns, attachment layouts, reply avatars, and seen dropdowns — silently falling back to the default template instead of rendering correctly. This restores the v4 behavior where parent components extracted nested wireframes via `querySelector` and passed them to children.

  * \[**Comments**]: Fixed `veltDynamicTemplate` placement in multiple components so it wraps only the replaceable inner content (e.g., an icon) instead of the entire component structure. Previously the default template always rendered and the wireframe content was never injected.

  * \[**Comments**]: Fixed `velt-class` conditional styling in custom wireframes not resolving correctly for per-item data such as `{commentObj.*}`, `{attachment.*}`, and `{file.*}`. Added `applyConditionalClasses()` calls with per-item data contexts across 40+ comment dialog primitive components.

  * \[**Comments**]: Fixed click event handlers being lost when custom wireframes replace default content by moving handlers from inside `veltDynamicTemplate` blocks to wrapper elements outside the template directive. This restores click functionality for custom wireframes on mark-as-read, dropdown items, options, and priority/status items.

  * \[**Comments**]: Fixed per-dialog state (`localUIStateSignal`) not being propagated to child primitives and `veltDynamicTemplate` contexts throughout the entire comment dialog component tree, including through wireframe-replaced sections.

  * \[**Comments**]: Fixed custom wireframes using data bindings like `<VeltData field="attachment.name" />` displaying empty content by adding per-item data fields (`attachment`, `commentObj`, `file`, `item`, etc.) to `veltDynamicTemplate` context objects.

  * \[**Comments**]: Fixed layout issues caused by styled wrapper elements persisting outside the template when wireframes replaced inner content. Visual styling classes (width, height, padding) are now inside the `veltDynamicTemplate` default content.

  * \[**Comments**]: Restored CSS class names on the priority dropdown content item components to their v4 names (`velt-priority-dropdown-content-item`, `velt-priority-dropdown-content-item-tick`, `s-priority-*`) so existing CSS overrides continue to work without changes.

  * \[**Comments**]: Fixed mark-as-read not firing when clicking a comment in sidebar focused-thread mode or inline comment section mode. Previously the comment dialog is always in a selected state in these modes, preventing the mark-as-read path from triggering.

  * \[**Comments**]: Fixed the attachment image download button not hiding for anonymous users, disabled states, and expired plans. The button now correctly matches v4 behavior.

  * \[**Comments**]: Fixed attachment loading spinners crashing when `config.uiState` is not yet initialized (e.g., in standalone usage or during initialization race conditions).

  * \[**Comments**]: Fixed sidebar deselection accidentally re-selecting an annotation that was already removed from the selected annotations map by another code path.
</Update>

<Update label="5.0.1-beta.2" description="February 16, 2026">
  ### New Features

  * \[**Comments**]: Added `enableAttachmentDownload()` / `disableAttachmentDownload()` API methods and an `attachmentDownload` prop on `<VeltComments>` to control whether clicking an attachment triggers a file download. A new `attachmentDownloadClicked` event fires on every attachment click regardless of the download setting, letting you intercept clicks for custom viewers, analytics, or access control. Download is enabled by default so there is no breaking change.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Declarative prop
      <VeltComments attachmentDownload={false} />

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.enableAttachmentDownload();
      commentElement.disableAttachmentDownload();

      // Listening to the event
      const handleAttachmentDownloadClicked = useCommentEventCallback('attachmentDownloadClicked');

      useEffect(() => {
        if (handleAttachmentDownloadClicked) {
          console.log('Attachment clicked:', handleAttachmentDownloadClicked.attachment.attachmentId);
          console.log('Annotation ID:', handleAttachmentDownloadClicked.annotationId);
        }
      }, [handleAttachmentDownloadClicked]);
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments attachment-download="false"></velt-comments>

      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.enableAttachmentDownload();
      commentElement.disableAttachmentDownload();

      commentElement.on('attachmentDownloadClicked').subscribe((event) => {
        console.log('Attachment clicked:', event.attachment.attachmentId);
        console.log('Annotation ID:', event.annotationId);
      });
      </script>
      ```
    </Tab>
  </Tabs>

  The exported `AttachmentDownloadClickedEvent` interface has the following shape:

  ```ts theme={null}
  interface AttachmentDownloadClickedEvent {
    annotationId: string;
    commentAnnotation: CommentAnnotation;
    attachment: Attachment;
    metadata?: VeltEventMetadata;
  }
  ```

  ### Improvements

  * \[**Comments**]: The wireframe template for the resolve and unresolve buttons in the assignee banner is now nested inside the button component rather than wrapping it, giving custom wireframes direct access to button state, styling, and event handlers. The affected wireframes are `velt-comment-dialog-assignee-banner-resolve-button-wireframe` and `velt-comment-dialog-assignee-banner-unresolve-button-wireframe`.

  * \[**Comments**]: Added CSS classes to comment composer attachment components to reflect loading and edit-mode states: `.velt-composer-attachment-container`, `.velt-composer-attachment--loading`, and `.velt-composer-attachment--edit-mode`.

  * \[**Comments**]: Added `.s-comment-dialog-assign-to-menu` and `.velt-thread-card--assign-to-menu` CSS classes to the assign-to menu component for more granular styling control.

  ### Bug Fixes

  * \[**Comments**]: Added `'iframe'` to the list of non-nestable elements so that `getHostElement()` returns the iframe's parent element instead of attempting to place a comment pin inside the iframe document context.

  * \[**Comments**]: Fixed a regression where navigating back from a focused thread triggered an unintended draft auto-save. A guard now skips auto-save when the focused thread dialog is destroyed via back navigation.

  * \[**Comments**]: Fixed stale selection state in the sidebar after back-navigating from a focused annotation. The annotation is now properly deselected from the internal selection map on exit.

  * \[**Comments**]: Thread card attachment components now use `isTemplateEmpty()` to detect empty wireframe templates instead of a truthy check, so attachments correctly fall back to default rendering when an empty wireframe is provided.

  * \[**Comments**]: Custom thread card wireframes that do not include an edit composer wireframe now automatically render the default edit composer, so inline comment editing remains functional without requiring wireframe changes.
</Update>

<Update label="5.0.1-beta.1" description="February 13, 2026">
  ### New Features

  * \[**Comments**]: Added `clearContext` option to `PageModeComposerConfig` to control whether comment context is cleared after page mode composer submission. Set `clearContext: false` to preserve context data across submissions (defaults to `true`).

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      const commentElement = useCommentUtils();
      commentElement.setContextInPageModeComposer({
        context: { documentId: '123', section: 'intro' },
        targetElementId: 'my-element',
        clearContext: false
      });

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.setContextInPageModeComposer({
        context: { documentId: '123', section: 'intro' },
        targetElementId: 'my-element',
        clearContext: false
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.setContextInPageModeComposer({
        context: { documentId: '123', section: 'intro' },
        targetElementId: 'my-element',
        clearContext: false
      });
      </script>
      ```
    </Tab>
  </Tabs>

  ### Bug Fixes

  * \[**Comments**]: Removed host CSS classes `velt-composer-open` and `velt-composer-input-focused` from the Comment Dialog Composer component. If your custom styles targeted these classes, update your selectors accordingly.

  * \[**Comments**]: Fixed wireframe template forwarding in Comment Dialog so custom templates set via `dialogTemplate` are now correctly applied to all child components.

  * \[**Comments**]: Fixed nested wireframe template extraction in Comment Dialog Threads so custom `velt-comment-dialog-thread-card-wireframe` and `velt-comment-dialog-more-reply-wireframe` templates are correctly applied.

  * \[**Comments**]: Fixed Mark as Read/Unread wireframe template structure so custom wireframe templates now provide proper control over the button's internal elements.

  * \[**Comments**]: Added proper wireframe template wrapper to the parent Mark as Read component with a clickable container that includes accessibility attributes for the toggle action.

  * \[**Comments**]: Fixed comment annotation data resolution in button click handlers so `commentAnnotation` context data now correctly resolves from the updated component config structure.
</Update>

<Update label="5.0.0" description="February 12, 2026">
  ### New Features

  * \[**CRDT**]: Added [Add CRDT Data](/api-reference/rest-apis/v2/crdt/add-crdt-data) REST API to create new CRDT editor data on the backend. Supports `text`, `map`, `array`, and `xml` types for use with Multiplayer Editing (Yjs).

  * \[**CRDT**]: Added [Update CRDT Data](/api-reference/rest-apis/v2/crdt/update-crdt-data) REST API to replace existing CRDT editor data. Generates proper CRDT operations so connected clients pick up the change automatically.

  ### Bug Fixes

  * \[**Comments**]: Fixed edit mode not working in thread card messages. Edit mode now properly propagates to comment messages within thread cards.
</Update>

<Update label="5.0.0-beta.17" description="February 12, 2026">
  ### Improvements

  * \[**Comments**]: Updated `updateVisibility()` with `type: 'restricted'` to auto-default to the current authenticated user when no `userIds` are provided. Developers no longer need to manually pass the current user's ID.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const commentElement = useCommentUtils();

      // Before: Had to pass userIds manually
      commentElement.updateVisibility({
        annotationId: 'annotation-1',
        visibility: { type: 'restricted', userIds: ['current-user-id'] }
      });

      // After: userIds auto-defaults to current authenticated user
      commentElement.updateVisibility({
        annotationId: 'annotation-1',
        visibility: { type: 'restricted' }
      });

      // Using API method
      const commentElement = client.getCommentElement();
      commentElement.updateVisibility({
        annotationId: 'annotation-1',
        visibility: { type: 'restricted' }
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();

      // Before: Had to pass userIds manually
      commentElement.updateVisibility({
        annotationId: 'annotation-1',
        visibility: { type: 'restricted', userIds: ['current-user-id'] }
      });

      // After: userIds auto-defaults to current authenticated user
      commentElement.updateVisibility({
        annotationId: 'annotation-1',
        visibility: { type: 'restricted' }
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Improved performance of `getCommentAnnotationsCount()` by automatically batching queries when 2+ `documentIds` are provided. You can customize the debounce delay with `debounceMs` parameter (default 5000ms).

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const commentElement = useCommentUtils();

      // Automatically batches when 2+ documentIds are provided
      const counts = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', 'doc-3']
      });

      // Optional: customize debounce (default 5000ms)
      const countsWithDebounce = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', 'doc-3'],
        debounceMs: 2000
      });

      // Using API method
      const commentElement = client.getCommentElement();
      const counts = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', 'doc-3']
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();

      // Automatically batches when 2+ documentIds are provided
      const counts = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', 'doc-3']
      });

      // Optional: customize debounce (default 5000ms)
      const countsWithDebounce = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', 'doc-3'],
        debounceMs: 2000
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Deprecated legacy `enablePrivateCommentMode()`, `disablePrivateCommentMode()`, and `updateAccess()` methods. These are replaced by the visibility API. Existing usage continues to work but IDEs will show deprecation warnings.
</Update>

<Update label="5.0.0-beta.16" description="February 11, 2026">
  ### Improvements

  * \[**Core**]: Added ability to control Firestore SDK logs via `sessionStorage` or environment config. By default, logs are silenced to reduce console noise in production.

  * \[**Performance**]: Added GSAP animation detection to reduce MutationObserver overhead. Pages using GSAP now experience \~20x performance improvement (from \~375ms to \~18ms per callback).

  ### Bug Fixes

  * \[**Comments**]: Fixed cursor position loss on mobile when selecting autocomplete options. The cursor now stays at the correct position after selecting @-mentions.

  * \[**Comments**]: Fixed hotkey insertion in autocomplete tool. Clicking the autocomplete trigger button (e.g. "@") now inserts at cursor position on all browsers, preserving mention spans.

  * \[**Comments**]: Fixed autocomplete panel click events bubbling through dropdown. Selecting options no longer accidentally triggers underlying elements on mobile browsers.

  * \[**Comments**]: Disabled format options (bold, italic, etc.) by default. To enable, set `enableFormatOptions: true` in your comment configuration.

  * \[**Comments**]: Fixed cursor position in bottomsheet mode after selecting mentions. The cursor now appears after the mention span instead of jumping to the beginning.

  * \[**Comments**]: Fixed attachment components not receiving parent UI state. Selected and invalid attachment components now correctly react to parent composer state changes.
</Update>

<Update label="5.0.0-beta.15" description="February 11, 2026">
  ### New Features

  * \[**Comments**]: Added rich text formatting toolbar for comment composers. Users can apply bold, italic, underline, and strikethrough formatting to emphasize text.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      import { VeltComments } from '@veltdev/react';

      <VeltComments formatOptions={true} />

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.enableFormatOptions();
      commentElement.disableFormatOptions();

      // Configure individual format types
      commentElement.setFormatConfig({
        bold: { enable: true },
        italic: { enable: true },
        underline: { enable: false },
        strikethrough: { enable: false }
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments format-options="true"></velt-comments>

      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.enableFormatOptions();
      commentElement.disableFormatOptions();

      // Configure individual format types
      commentElement.setFormatConfig({
        bold: { enable: true },
        italic: { enable: true },
        underline: { enable: false },
        strikethrough: { enable: false }
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `clearComposer()` method to programmatically reset composer state (text, attachments, recordings, tagged users).

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      const commentElement = useCommentUtils();
      commentElement.clearComposer({ targetComposerElementId: 'my-composer-1' });

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.clearComposer({ targetComposerElementId: 'my-composer-1' });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.clearComposer({ targetComposerElementId: 'my-composer-1' });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `getComposerData()` method to retrieve current composer state synchronously without subscribing to events.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      const commentElement = useCommentUtils();
      const data = commentElement.getComposerData({ targetComposerElementId: 'my-composer-1' });

      if (data) {
        console.log(data.text);
        console.log(data.annotation);
        console.log(data.targetComposerElementId);
      }

      // Using API methods
      const commentElement = client.getCommentElement();
      const data = commentElement.getComposerData({ targetComposerElementId: 'my-composer-1' });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      const data = commentElement.getComposerData({ targetComposerElementId: 'my-composer-1' });

      if (data) {
        console.log(data.text);
        console.log(data.annotation);
        console.log(data.targetComposerElementId);
      }
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `context` prop to `VeltCommentDialogComposer` to attach custom context data to comments created from standalone composers.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      <VeltCommentDialogComposer
        targetComposerElementId="my-composer"
        context={{ projectId: 'abc123', section: 'header' }}
      />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-dialog-composer
        target-composer-element-id="my-composer"
        context='{"projectId": "abc123", "section": "header"}'>
      </velt-comment-dialog-composer>
      ```
    </Tab>
  </Tabs>

  ### Improvements

  * \[**Comments**]: Updated `composerTextChange` event to include the full draft annotation object and `targetComposerElementId`. Access tagged users, attachments, recordings, context, and assign-to data without additional API calls.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      const composerTextChangeEvent = useCommentEventCallback('composerTextChange');

      useEffect(() => {
        if (composerTextChangeEvent) {
          console.log(composerTextChangeEvent.text);
          console.log(composerTextChangeEvent.annotation);
          console.log(composerTextChangeEvent.targetComposerElementId);
        }
      }, [composerTextChangeEvent]);

      // Using API methods
      const commentElement = client.getCommentElement();
      commentElement.on('composerTextChange').subscribe((event) => {
        console.log(event.text);
        console.log(event.annotation);
        console.log(event.targetComposerElementId);
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.on('composerTextChange').subscribe((event) => {
        console.log(event.text);
        console.log(event.annotation);
        console.log(event.targetComposerElementId);
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Notifications**]: Fixed notification panel settings accordion wireframes to properly forward custom trigger and content templates to child components.

  ### Bug Fixes

  * \[**Comments**]: Fixed `assignToType` to correctly respect values set via attribute or API without being overridden by hardcoded defaults.

  * \[**Comments**]: Fixed formatted text (bold, italic, underline, strikethrough) to preserve formatting on submit and edit.
</Update>

<Update label="5.0.0-beta.14" description="February 10, 2026">
  ### New Features

  * \[**Comments**]: Added page mode composer context APIs to pass context when opening the sidebar via the comment tool. When enabled, clicking the comment tool opens the sidebar with the page mode composer and passes configured context automatically.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using API methods
      import { useCommentUtils, VeltCommentTool } from '@veltdev/react';

      const commentElement = useCommentUtils();

      // Enable context passing mode
      commentElement.enableContextInPageModeComposer();

      // Set context with target element
      commentElement.setContextInPageModeComposer({
        context: { projectId: 'abc123', section: 'header' },
        targetElementId: 'my-target-element'
      });

      // Focus the composer programmatically
      commentElement.focusPageModeComposer();

      // Clear context
      commentElement.clearPageModeComposerContext();

      // Disable context passing mode
      commentElement.disableContextInPageModeComposer();


      // Using props
      <VeltCommentTool
        contextInPageModeComposer={true}
        context={{ section: 'header', elementId: 'nav-menu' }}
      />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      Using props:

      ```html theme={null}
      <velt-comment-tool
        context-in-page-mode-composer="true"
        context='{"section": "header", "elementId": "nav-menu"}'>
      </velt-comment-tool>

      Using API methods:
      <script>
      const commentElement = Velt.getCommentElement();

      // Enable context passing mode
      commentElement.enableContextInPageModeComposer();

      // Set context with target element
      commentElement.setContextInPageModeComposer({
        context: { projectId: 'abc123' },
        targetElementId: 'my-target-element'
      });

      // Focus the composer
      commentElement.focusPageModeComposer();

      // Clear context
      commentElement.clearPageModeComposerContext();

      // Disable
      commentElement.disableContextInPageModeComposer();
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `setAssignToType()` method to switch the assignment UI between dropdown and checkbox modes. Checkbox mode allows quick self-assignment with a toggle.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      import { useCommentUtils, VeltComments } from '@veltdev/react';

      const commentElement = useCommentUtils();

      // Use dropdown mode (default)
      commentElement.setAssignToType({ type: 'dropdown' });

      // Use checkbox mode for quick self-assignment
      commentElement.setAssignToType({ type: 'checkbox' });


      // Using props
      <VeltComments assignToType="checkbox" />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      Using props:

      ```html theme={null}
      <velt-comments assign-to-type="checkbox"></velt-comments>

      Using API methods:
      <script>
      const commentElement = Velt.getCommentElement();

      // Use dropdown mode (default)
      commentElement.setAssignToType({ type: 'dropdown' });

      // Use checkbox mode
      commentElement.setAssignToType({ type: 'checkbox' });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added "Assigned to me" filter option in the sidebar to filter comments by assignment.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      import { VeltCommentsSidebarWireframe } from '@veltdev/react';

      <VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content>
        <VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.FilterAssignedToMe />
      </VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content>
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comments-sidebar-minimal-filter-dropdown-content-filter-assigned-to-me-wireframe>
      </velt-comments-sidebar-minimal-filter-dropdown-content-filter-assigned-to-me-wireframe>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added new event types to track comment tool and sidebar button clicks. Subscribe to these events to access context data for analytics and custom workflows.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using Hooks
      import { useCommentEventCallback } from '@veltdev/react';
      import { useEffect } from 'react';

      const commentToolClickEvent = useCommentEventCallback('commentToolClicked');

      useEffect(() => {
        if (commentToolClickedEvent) {
          console.log('Context:', commentToolClickedEvent.context);
          console.log('Target Element ID:', commentToolClickedEvent.targetElementId);
        }
      }, [commentToolClickedEvent]);

      const sidebarButtonClickedEvent = useCommentEventCallback('sidebarButtonClicked');

      useEffect(() => {
        if (sidebarButtonClickedEvent) {
          console.log('Sidebar button clicked');
        }
      }, [sidebarButtonClickedEvent]);

      // Using API methods
      import { useVeltClient } from '@veltdev/react';

      const { client } = useVeltClient();
      const commentElement = client.getCommentElement();

      commentElement.on('commentToolClicked').subscribe((event) => {
        console.log('Context:', event.context);
        console.log('Target Element ID:', event.targetElementId);
      });

      commentElement.on('sidebarButtonClicked').subscribe((event) => {
        console.log('Sidebar button clicked');
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();

      // Comment Tool Click
      commentElement.on('commentToolClicked').subscribe((event) => {
        console.log('Context:', event.context);
        console.log('Target Element ID:', event.targetElementId);
        console.log('Metadata:', event.metadata);
      });

      // Sidebar Button Click
      commentElement.on('sidebarButtonClicked').subscribe((event) => {
        console.log('Sidebar button clicked');
        console.log('Metadata:', event.metadata);
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `readOnly` prop to control read-only mode at the component level. The local prop takes precedence over global settings when explicitly set.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      import { VeltCommentComposer, VeltInlineCommentsSection } from '@veltdev/react';

      <VeltCommentComposer readOnly={true} />

      <VeltInlineCommentsSection
        targetElementId="my-element"
        readOnly={true}
      />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-composer read-only="true"></velt-comment-composer>

      <velt-inline-comments-section
        target-element-id="my-element"
        read-only="true">
      </velt-inline-comments-section>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added wireframe component to customize the assign button on thread cards.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      import { VeltCommentDialogWireframe } from '@veltdev/react';

      <VeltCommentDialogWireframe.ThreadCard.AssignButton />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-dialog-thread-card-assign-button-wireframe></velt-comment-dialog-thread-card-assign-button-wireframe>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added wireframe component to customize the edit composer within comment thread cards.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      import { VeltCommentDialogWireframe } from '@veltdev/react';

      <VeltCommentDialogWireframe.ThreadCard.EditComposer />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-dialog-thread-card-edit-composer-wireframe>
      </velt-comment-dialog-thread-card-edit-composer-wireframe>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added reaction pin component to pin specific reactions and exclude them from the general list.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      import { VeltCommentDialogWireframe } from '@veltdev/react';

      <VeltCommentDialogWireframe.ThreadCard>
        <VeltCommentDialogWireframe.ThreadCard.ReactionPin reactionId="thumbs-up" />
        <VeltCommentDialogWireframe.ThreadCard.ReactionPin reactionId="heart" />

        <VeltCommentDialogWireframe.ThreadCard.Reactions
          excludeReactionIds={['thumbs-up', 'heart']}
        />

        <VeltCommentDialogWireframe.ThreadCard.ReactionTool
          excludeReactionIds={['thumbs-up', 'heart']}
        />
      </VeltCommentDialogWireframe.ThreadCard>
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-dialog-thread-card-reaction-pin-wireframe
        reaction-id="thumbs-up">
      </velt-comment-dialog-thread-card-reaction-pin-wireframe>

      <velt-comment-dialog-thread-card-reactions-wireframe
        exclude-reaction-ids='["thumbs-up", "heart"]'>
      </velt-comment-dialog-thread-card-reactions-wireframe>

      <velt-comment-dialog-thread-card-reaction-tool-wireframe
        exclude-reaction-ids='["thumbs-up", "heart"]'>
      </velt-comment-dialog-thread-card-reaction-tool-wireframe>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `resolvedByUser` property on [`CommentAnnotation`](/api-reference/sdk/models/data-models#commentannotation) to access the full user object of who resolved a comment.

  ### Improvements

  * \[**Comments**]: Renamed `targetElementId` to `targetComposerElementId` on `VeltCommentComposer` for clarity.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      import { VeltCommentComposer } from '@veltdev/react';

      // Before (deprecated)
      <VeltCommentComposer targetElementId="my-composer" />

      // After (required)
      <VeltCommentComposer targetComposerElementId="my-composer" />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <!-- Before (deprecated) -->
      <velt-comment-composer target-element-id="my-composer"></velt-comment-composer>

      <!-- After (required) -->
      <velt-comment-composer target-composer-element-id="my-composer"></velt-comment-composer>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added CSS class `.velt-assign-dropdown--checkbox-icon-selected` to style the checkbox selected state.

  ### Bug Fixes

  * \[**Comments**]: Fixed cursor jumping when clicking autocomplete tool buttons. Text now inserts at the correct cursor position.
  * \[**Comments**]: Fixed actions menu visibility so it remains visible when the assignment dropdown is opened.
  * \[**Comments**]: Added "Assign" tooltip to the assignment button in thread cards.
  * \[**Comments**]: Fixed "Assign To" label capitalization to "Assign to".
  * \[**Reactions**]: Added `.velt-reaction-pin--no-reactions` CSS class for styling empty reaction pins.
  * \[**Comments**]: Fixed autocomplete panel viewport height to respect custom `itemSize` from `autoCompleteScrollConfig`.
  * \[**Comments**]: Fixed attachment metadata handling to use fallback values when metadata is unavailable.
  * \[**Comments**]: Fixed read-only state management so local `readOnly` props are not overridden by global state changes.
  * \[**Reactions**]: Fixed reactions panel to return an empty array on error instead of default emojis.
  * \[**Comments**]: Fixed `reactionId` prop on `VeltCommentDialogWireframe.ThreadCard.ReactionPin` to properly convert to dashed-case in React.
</Update>

<Update label="5.0.0-beta.13" description="February 10, 2026">
  ### New Features

  * \[**Notifications**]: Added ability to enable organization-level notification settings. This allows you to configure notifications once for all documents in an organization instead of per document.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
        <VeltNotificationsPanel
          enableSettingsAtOrganizationLevel={true}
          settings={true}
        />
        {/* or */}
        <VeltNotificationsTool
          enableSettingsAtOrganizationLevel="true"
          settings={true}
        />

      // Using API methods
      const notificationElement = client.getNotificationElement();
      // Enable organization-level settings
      notificationElement.enableSettingsAtOrganizationLevel();

      // Disable organization-level settings
      notificationElement.disableSettingsAtOrganizationLevel();
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <!-- Using panel or tool components -->
      <velt-notifications-panel
        enable-settings-at-organization-level="true"
        settings="true">
      </velt-notifications-panel>
      <!-- or -->
      <velt-notifications-tool
        enable-settings-at-organization-level="true"
        settings="true">
      </velt-notifications-tool>

      <script>
      // Using API methods
      const notificationElement = Velt.getNotificationElement();

      // Enable organization-level settings
      notificationElement.enableSettingsAtOrganizationLevel();

      // Disable organization-level settings
      notificationElement.disableSettingsAtOrganizationLevel();
      </script>
      ```
    </Tab>
  </Tabs>
</Update>

<Update label="5.0.0-beta.12" description="February 6, 2026">
  ### Improvements

  * \[**Comments**]: Comments sidebar group-by views now display "Unassigned" for annotations without assignees and "Untagged" for annotations without tagged users.

  ### Bug Fixes

  * \[**Comments**]: Fixed an issue where image attachments in comment dialog were not opening in a lightbox view.

  * \[**Comments**]: Fixed an issue where sometimes user mentions did not include the leading `@` symbol in display text.

  * \[**Comments**]: Fixed an issue where the recorder control panel in comment dialog composer did not appear when a valid comment dialog ID was not present in component configuration.

  * \[**Comments**]: Fixed an issue where assignment and private comment options did not respect explicit configuration in sidebar mode.

  * \[**Comments**]: Fixed an issue where the comment dialog internal tag was changed from `snippyly-comment-dialog` to `velt-comment-dialog-internal` for correct sidebar focus and keyboard behavior.
</Update>

<Update label="5.0.0-beta.11" description="February 4, 2026">
  ### Improvements

  * \[**Comments**]: Added ability to enable/disable Private Comments feature in [Velt Console](https://console.velt.dev/dashboard/config/appconfig)
</Update>

<Update label="5.0.0-beta.10" description="February 3, 2026">
  ### Improvements

  * \[**Comments**]: Added `batchedPerDocument` mode for `getCommentAnnotationsCount()` that makes the query more efficient by up to 80% while maintaining per-document granularity. Very useful for UIs that need to display comment counts for 100 documents or less on the same page.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const commentElement = useCommentUtils();
      const counts = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', ..., 'doc-100'],
        batchedPerDocument: true
      });

      // Using API method
      const client = useVeltClient();
      const commentElement = client.getCommentElement();
      const counts = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', ..., 'doc-100'],
        batchedPerDocument: true,
        debounceMs: 5000
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      const counts = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', ..., 'doc-100'],
        batchedPerDocument: true
      });

      const countsWithDebounce = commentElement.getCommentAnnotationsCount({
        documentIds: ['doc-1', 'doc-2', ..., 'doc-100'],
        batchedPerDocument: true,
        debounceMs: 5000
      });
      </script>
      ```
    </Tab>
  </Tabs>

  **Return Format:**

  ```typescript theme={null}
  {
    data: {
      "doc-1": { total: 10, unread: 2 },
      "doc-2": { total: 15, unread: 5 }
    }
  }
  ```
</Update>

<Update label="5.0.0-beta.9" description="January 31, 2026">
  ### Bug Fixes

  * \[**Comments**]: Fixed draft mode not working properly. Draft content is now preserved when the dialog is closed and the shake animation now works as expected.

  * \[**Comments**]: Fixed context property access in `velt-data` elements. Templates can now access context properties using `{context.propertyName}` patterns.

  * \[**Comments**]: Fixed edit mode state persisting after dialog close. Reopening the dialog now shows the normal view instead of the edit composer.

  * \[**Comments**]: Fixed text reappearing when using select-all-and-delete in edit mode composer. Users can now properly delete all text in edit mode.

  * \[**Comments**]: Fixed links in comment body not clickable. Clicking links in comment text now opens them in a new tab.

  * \[**Comments**]: Fixed paste handling. Pasting a URL over selected text creates a hyperlink, multiline text preserves line breaks, and images paste as attachments.

  * \[**Comments**]: Fixed ghost comment banners not displaying. "Comment is syncing..." messages now properly show while annotation data is loading.

  * \[**Comments**]: Fixed priority selection not working on new annotations. Users can now set priority before submitting the first comment.

  * \[**Comments**]: Fixed email detection after @ symbol. Typing `@user@example.com` and pressing space now creates an email mention.

  * \[**Comments**]: Fixed recording in progress flag not clearing. Dialog now properly closes on click outside after recording finishes.

  * \[**Comments**]: Fixed links and @here mentions not highlighted in comment text. URLs are now styled as clickable links and @here mentions are properly highlighted.
</Update>

<Update label="5.0.0-beta.8" description="January 30, 2026">
  ### New Features

  * \[**Comments**]: Introducing Private Comments feature: Added `updateVisibility()` method to programmatically set comment access (public, organizationPrivate, or restricted). [Learn more](/async-collaboration/comments/customize-behavior#updatevisibility)

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const commentElement = useCommentUtils();

      // Set comment to organization-only
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'organizationPrivate',
        organizationId: 'org-123'
      });

      // Set comment to private (specific users)
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'restricted',
        userIds: ['user-1', 'user-2']
      });

      // Set comment to public
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'public'
      });

      // Using API method
      const client = useVeltClient();
      const commentElement = client.getCommentElement();
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'organizationPrivate',
        organizationId: 'org-123'
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();

      // Set comment to organization-only
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'organizationPrivate',
        organizationId: 'org-123'
      });

      // Set comment to private (specific users)
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'restricted',
        userIds: ['user-1', 'user-2']
      });

      // Set comment to public
      commentElement.updateVisibility({
        annotationId: "annotationId",
        type: 'public'
      });
      </script>
      ```
    </Tab>
  </Tabs>

  ### Bug Fixes

  * \[**Comments**]: Fixed mentioned users not receiving notifications. Users @mentioned in comments now correctly receive notifications.

  * \[**Comments**]: Fixed notification action type validation. Clients only receive data for valid event types.

  * \[**Comments**]: Fixed status reset when deleting comments. Status now only resets when current status is terminal.
</Update>

<Update label="5.0.0-beta.7" description="January 28, 2026">
  ### New Features

  * \[**Comments**]: Added `addCommentAnnotationDraft` event to dynamically set context when creating comment annotations. Triggered before `addCommentAnnotation` event clicks on the comment tool and the composer is rendered.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const addCommentAnnotationDraftEvent = useCommentEventCallback('addCommentAnnotationDraft');

      useEffect(() => {
          if (addCommentAnnotationDraftEvent?.addContext) {
              addCommentAnnotationDraftEvent.addContext({
                  questionId: '123',
                  questionText: 'What is the capital of France?',
              });
          }
      }, [addCommentAnnotationDraftEvent]);

      // Using API method
      const client = useVeltClient();
      const commentElement = client.getCommentElement();
      commentElement.on('addCommentAnnotationDraft').subscribe((event) => {
          if (event?.addContext) {
              event.addContext({
                  questionId: '123',
                  questionText: 'What is the capital of France?',
              });
          }
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.on('addCommentAnnotationDraft').subscribe((event) => {
          if (event?.addContext) {
              event.addContext({
                  questionId: '123',
                  questionText: 'What is the capital of France?',
              });
          }
      });
      </script>
      ```
    </Tab>
  </Tabs>

  ### Improvements

  * \[**Comments**]: Added `setContextProvider` method to set a global context provider for all comment annotations. Also added `useSetContextProvider` hook for React applications.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      import { useCallback, useEffect } from 'react';
      import { useSetContextProvider } from '@veltdev/react';

      const contextProvider = useCallback((documentId, location) => {
          return {
              questionId: '123',
              questionText: 'What is the capital of France?',
          }
      }, []);

      const { setContextProvider } = useSetContextProvider();

      useEffect(() => {
          if (setContextProvider && contextProvider) {
              setContextProvider(contextProvider);
          }
      }, []);

      // Using API method
      const client = useVeltClient();
      const commentElement = client.getCommentElement();
      commentElement.setContextProvider((documentId, location) => {
          return {
              questionId: '123',
              questionText: 'What is the capital of France?',
          }
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.setContextProvider((documentId, location) => {
          return {
              questionId: '123',
              questionText: 'What is the capital of France?',
          }
      });
      </script>
      ```
    </Tab>
  </Tabs>
</Update>

<Update label="5.0.0-beta.6" description="January 28, 2026">
  ### Bug Fixes

  * \[**Core**]: Fixed package integrity issue in v5.0.0-beta.5.
</Update>

<Update label="5.0.0-beta.5" description="January 28, 2026">
  ### Bug Fixes

  * \[**Comments**]: Fixed page mode and multi-thread annotation ID not found error. Page mode and multi-thread comments now work as expected.

  * \[**Comments**]: Fixed `updateOverlayPosition` function not triggering. Comment dialog now opens in the correct position.

  * \[**Comments**]: Fixed unread status issues in inline and focused thread modes. Annotations are now marked as read when opened or clicked.

  * \[**Comments**]: Fixed three-dot menu not visible in sidebar.

  * \[**Comments**]: Fixed composer not being focused when opened.

  * \[**Comments**]: Fixed comments navigating on click. Comments now only navigate when the navigation button is clicked.
</Update>

<Update label="5.0.0-beta.4" description="January 23, 2026">
  ### Bug Fixes

  * \[**Comments**]: Fixed `lastUpdated` timestamp not being updated when changing context in comment annotation via SDK. Ensures context updates are properly synced to other users.
</Update>

<Update label="5.0.0-beta.3" description="January 22, 2026">
  ### Improvements

  * \[**Core**]: Added robustness to initialization when `VeltProvider` was re-rendered multiple times over a slow network.

  ### Bug Fixes

  * \[**Comments**]: Refactored `submitComment` method to fix resolver issue for `velt-comment-composer`. Now follows the standard comment submission flow.

  * \[**Comments**]: Fixed unread status not updating correctly in bottom sheet. This was a regression in v5.

  * \[**Comments**]: Fixed navigation button not working properly. This was a regression in v5.

  * \[**Comments**]: Fixed disable recording option not working in `velt-comment-composer`. This was a regression in v5.
</Update>

<Update label="5.0.0-beta.2" description="January 22, 2026">
  ### New Features

  * \[**Core**]: Added `globalStyles` option to control whether Velt's global CSS is loaded. Set to `false` to disable default styles when implementing custom theming.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using VeltProvider
      <VeltProvider apiKey='API_KEY' config={{
          globalStyles: false
      }}>
          {/* Your app content */}
      </VeltProvider>

      // Using API method
      const client = useVeltClient();
      client.initConfig('API_KEY', {
        globalStyles: false
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      Velt.initConfig('API_KEY', {
        globalStyles: false
      });
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `submitComment(targetElementId)` method to programmatically trigger comment submission. Enables custom buttons or keyboard shortcuts for submitting comments.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}

      <VeltCommentComposer targetElementId="composer-1" />
      <VeltCommentDialogComposer targetElementId="composer-2" />

      // Using hook
      const commentElement = useCommentUtils();
      commentElement.submitComment('composer-1');

      // Using API method
      const client = useVeltClient();
      const commentElement = client.getCommentElement();
      commentElement.submitComment('composer-1');
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-composer target-element-id="composer-1"></velt-comment-composer>
      <velt-comment-dialog-composer target-element-id="composer-2"></velt-comment-dialog-composer>

      <button onclick="submitComment('composer-1')">Send 1</button>
      <button onclick="submitComment('composer-2')">Send 2</button>

      <script>
        function submitComment(targetElementId) {
          const commentElement = Velt.getCommentElement();
          commentElement.submitComment(targetElementId);
        }
      </script>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `placeholder` prop to customize input placeholder text in comment composer. Overrides default placeholders. [Learn more](/ui-customization/features/async/comments/standalone-components/comment-composer#placeholder)

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      <VeltCommentComposer placeholder="Share your thoughts..." />
      <VeltCommentDialogComposer placeholder="Add a comment..." />
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <velt-comment-composer placeholder="Share your thoughts..."></velt-comment-composer>
      <velt-comment-dialog-composer placeholder="Add a comment..."></velt-comment-dialog-composer>
      ```
    </Tab>
  </Tabs>

  * \[**Comments**]: Added `composerTextChange` event that fires when text changes in any comment composer. Enables features like auto-save drafts, character counters, or real-time validation. [Learn more](/async-collaboration/comments/customize-behavior#event-subscription)

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Using hook
      const composerTextChangeEvent = useCommentEventCallback('composerTextChange');
      useEffect(() => {
          if (composerTextChangeEvent) {
              console.log('Text changed:', composerTextChangeEvent.text);
          }
      }, [composerTextChangeEvent]);

      // Using API method
      const client = useVeltClient();
      const commentElement = client.getCommentElement();
      commentElement.on('composerTextChange').subscribe((event) => {
          console.log('Text changed:', event.text);
      });
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <script>
      const commentElement = Velt.getCommentElement();
      commentElement.on('composerTextChange').subscribe((event) => {
          console.log('Text changed:', event.text);
      });
      </script>
      ```
    </Tab>
  </Tabs>
</Update>

<Update label="5.0.0-beta.1" description="January 21, 2026">
  ### Bug Fixes

  #### Comment Dialog Primitives

  Released 115+ primitive components for building custom comment dialogs. Each subcomponent can now be used independently without requiring the full dialog structure.

  <Tabs>
    <Tab title="React / Next.js">
      ```jsx theme={null}
      // Pattern 1: ID-Based (Standalone)
      <VeltCommentDialogHeader annotationId="abc123" />
      <VeltCommentDialogComposer annotationId="abc123" />

      // Pattern 2: Context Wrapper
      <VeltCommentDialogContextWrapper annotationId="abc123">
        <VeltCommentDialogHeader />
        <VeltCommentDialogComposer />
        <VeltCommentDialogBody />
      </VeltCommentDialogContextWrapper>
      ```
    </Tab>

    <Tab title="Other Frameworks">
      ```html theme={null}
      <!-- Pattern 1: ID-Based (Standalone) -->
      <velt-comment-dialog-header annotation-id="abc123"></velt-comment-dialog-header>
      <velt-comment-dialog-composer annotation-id="abc123"></velt-comment-dialog-composer>

      <!-- Pattern 2: Context Wrapper -->
      <velt-comment-dialog-context-wrapper annotation-id="abc123">
        <velt-comment-dialog-header></velt-comment-dialog-header>
        <velt-comment-dialog-composer></velt-comment-dialog-composer>
        <velt-comment-dialog-body></velt-comment-dialog-body>
      </velt-comment-dialog-context-wrapper>
      ```
    </Tab>
  </Tabs>

  **Available Components:**

  * **Header/Body**: Header, Body, CloseButton
  * **Thread**: ThreadCard with Avatar, Name, Time, Message, Reactions, Recordings, Reply, Options, and more
  * **Composer**: Composer, ComposerInput, ComposerActionButton, ComposerAttachmentButton, ComposerRecorderButton, ComposerRecorderPlayer, ComposerFiles
  * **Dropdowns**: StatusDropdown, PriorityDropdown, OptionsDropdown, CustomAnnotationDropdown (each with full sub-component breakdown)
  * **Additional**: ReplyAvatars, AssigneeBanner, ResolveButton, UnresolveButton, CopyLink, DeleteButton, PrivateBanner, NavigationButton, and 90+ more

  [View full documentation →](/ui-customization/features/async/comments/comment-dialog-primitives/overview) | [API Methods](/api-reference/sdk/api/api-methods#comment-dialog-primitives) | [Data Models](/api-reference/sdk/models/data-models#comment-dialog-primitives)
</Update>
