`
* First enable the feature and then define which elements should sync realtime.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
// Enable auto-sync state
liveStateSyncElement.enableAutoSyncState();
// In your JSX
return (
);
```
```html theme={null}
```
## Editor
### setUserAsEditor
Sets the current user as the editor, making all other users read-only.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.setUserAsEditor();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.setUserAsEditor();
```
#### Error handling
`setUserAsEditor` includes validations to prevent assigning an editor when one already exists. It returns a promise that resolves to an optional error object.
```ts theme={null}
export interface SetUserAsEditorResponse {
error?: ErrorEvent;
}
export type ErrorEvent = {
error?: string;
code: string;
message?: string;
source?: string;
};
// Usage
const liveStateSyncElement = useLiveStateSyncUtils();
const result = await liveStateSyncElement.setUserAsEditor();
if (result?.error) {
if (result.error.code === 'same_user_editor_current_tab') {
// Same user already editor in current tab
} else if (result.error.code === 'same_user_editor_different_tab') {
// Same user already editor in different tab
} else if (result.error.code === 'another_user_editor') {
// Another user already editor
}
}
```
```js theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
const result = await liveStateSyncElement.setUserAsEditor();
if (result && result.error) {
if (result.error.code === 'same_user_editor_current_tab') {
// Same user already editor in current tab
} else if (result.error.code === 'same_user_editor_different_tab') {
// Same user already editor in different tab
} else if (result.error.code === 'another_user_editor') {
// Another user already editor
}
}
```
Possible error values:
```ts theme={null}
{
code: 'same_user_editor_current_tab',
message: 'Same user is already editor in current tab.',
source: 'setUserAsEditor',
}
{
code: 'same_user_editor_different_tab',
message: 'Same user is already editor in different tab.',
source: 'setUserAsEditor',
}
{
code: 'another_user_editor',
message: 'Another user is already editor.',
source: 'setUserAsEditor',
}
```
### isUserEditor
* Get the current user's editor status.
**Returns**
* `null`: When the state is not available yet.
* `undefined`: When there are no current editors available in single editor mode.
* `UserEditorAccess`: When there is at least one editor available in single editor mode.
* `isEditor` (`boolean`) - Whether the user is the editor
* `isEditorOnCurrentTab` (`boolean`) - Whether the user is editor on current tab
```jsx theme={null}
// Using Hooks
const { isEditor, isEditorOnCurrentTab } = useUserEditorState();
// Using API
const liveStateSyncElement = useLiveStateSyncUtils();
let subscription = liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
console.log('Is Editor:', userEditorAccess.isEditor);
console.log('Is Editor on Current Tab:', userEditorAccess.isEditorOnCurrentTab);
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
let subscription = liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
console.log('Is Editor:', userEditorAccess.isEditor);
console.log('Is Editor on Current Tab:', userEditorAccess.isEditorOnCurrentTab);
});
// To unsubscribe from the subscription:
subscription?.unsubscribe()
```
### getEditor
Get information about the current editor.
**Returns**
* `User` object:
* `email` (`string`) - Editor's email
* `name` (`string`) - Editor's name
* `photoUrl` (`string`) - Editor's photo URL
* `userId` (`string`) - Editor's unique ID
```jsx theme={null}
// Using Hooks
const editor = useEditor();
// Using API
const liveStateSyncElement = useLiveStateSyncUtils();
let subscription = liveStateSyncElement.getEditor().subscribe((user) => {
console.log('Editor:', user);
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
let subscription = liveStateSyncElement.getEditor().subscribe((user) => {
console.log('Editor:', user);
});
// To unsubscribe from the subscription:
subscription?.unsubscribe()
```
### isEditorAccessRequested
Check if any viewer has requested editor access.
**Returns**
* `null` - User is not editor or request was canceled
* `EditorRequest` object:
* `requestStatus` (`string`) - 'requested' for active requests
* `requestedBy` (`User`) - User object of the requester
```jsx theme={null}
// Using Hooks
const editorAccessRequested = useEditorAccessRequestHandler();
// Using API
const liveStateSyncElement = useLiveStateSyncUtils();
let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
if (data === null) {
console.log('No active requests or user is not editor');
} else {
console.log('Request from:', data.requestedBy.name);
}
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
if (data === null) {
console.log('No active requests or user is not editor');
} else {
console.log('Request from:', data.requestedBy.name);
}
});
// To unsubscribe from the subscription:
subscription?.unsubscribe()
```
### acceptEditorAccessRequest
Accept editor access requests.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.acceptEditorAccessRequest();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.acceptEditorAccessRequest();
```
### rejectEditorAccessRequest
Reject editor access requests.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.rejectEditorAccessRequest();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.rejectEditorAccessRequest();
```
### editCurrentTab
Make current tab editable when editor has multiple tabs open.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.editCurrentTab();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.editCurrentTab();
```
## Viewer
### requestEditorAccess
Request editor access from the current editor.
**Returns**
* `null` - Request is pending
* `true` - Request accepted
* `false` - Request rejected
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
let subscription = liveStateSyncElement.requestEditorAccess().subscribe((status) => {
if (status === null) console.log('Request pending');
else if (status === true) console.log('Request accepted');
else console.log('Request rejected');
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
let subscription = liveStateSyncElement.requestEditorAccess().subscribe((status) => {
if (status === null) console.log('Request pending');
else if (status === true) console.log('Request accepted');
else console.log('Request rejected');
});
// To unsubscribe from the subscription:
subscription?.unsubscribe()
```
### cancelEditorAccessRequest
Cancel the editor access request.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.cancelEditorAccessRequest();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.cancelEditorAccessRequest();
```
## Heartbeat
The heartbeat mechanism provides more reliable presence detection in single editor mode scenarios. This feature is enabled by default when single editor mode is enabled.
If you want to disable heartbeat functionality, you must disable it before enabling single editor mode.
### [enableHeartbeat](/api-reference/sdk/api/api-methods#enableheartbeat)
Enables/Disables the heartbeat mechanism for Single Editor Mode presence detection.
Default: `enabled`
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.enableHeartbeat();
liveStateSyncElement.disableHeartbeat();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.enableHeartbeat();
liveStateSyncElement.disableHeartbeat();
```
## Presence Heartbeat
### updateUserPresence
Send a lightweight heartbeat from your host app to Velt. Velt will use that as a fallback in rare edge cases if it fails to detect multi‑tab/device presence. Most apps don't need this; use only if you see ambiguity in who's the active editor.
```ts theme={null}
export interface LiveStateSingleEditorExternalUserPresence {
/** True if the same user is present on a different tab. */
sameUserPresentOnTab?: boolean;
/** True if a different user is present on a different tab. */
differentUserPresentOnTab?: boolean;
/** User IDs of users present on a different tab. */
userIds?: string[];
}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.updateUserPresence({
sameUserPresentOnTab: false,
differentUserPresentOnTab: true,
userIds: ['user-2']
});
```
```ts theme={null}
export interface LiveStateSingleEditorExternalUserPresence {
/** True if the same user is present on a different tab. */
sameUserPresentOnTab?: boolean;
/** True if a different user is present on a different tab. */
differentUserPresentOnTab?: boolean;
/** User IDs of users present on different tabs. */
userIds?: string[];
}
```
```js theme={null}
// API method
const liveStateSyncElement = Velt.getLiveStateSyncElement();
const userPresenceObject = {
sameUserPresentOnTab: false,
differentUserPresentOnTab: true,
userIds: ['user-2']
};
liveStateSyncElement.updateUserPresence(userPresenceObject);
```
### resetUserAccess
Reset editor access for all users.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.resetUserAccess();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.resetUserAccess();
```
## UI
### enableDefaultSingleEditorUI
* Control the visibility of the default Single Editor Mode System UI.
* The default UI shows:
* Current user's editor/viewer status
* Editor access requests
* Request timeout countdown
* Request rejection options
* If you disable the default UI, you'll need to implement your own UI for these features.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
// Enable default UI (enabled by default)
liveStateSyncElement.enableDefaultSingleEditorUI();
// Disable default UI for custom implementation
liveStateSyncElement.disableDefaultSingleEditorUI();
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
// Enable default UI (enabled by default)
liveStateSyncElement.enableDefaultSingleEditorUI();
// Disable default UI for custom implementation
liveStateSyncElement.disableDefaultSingleEditorUI();
```
### Embed Single Editor Mode Panel
* Embed the Single Editor Mode Panel into your UI.
```jsx theme={null}
```
```html theme={null}
```
## Event Subscription
### on
Subscribe to Single Editor Mode Events. Here is the list of events you can subscribe to and the event objects you will receive.
| Category | Event Type | Description | Event Object |
| ----------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Editor | `accessRequested` | When a user requests editor access. Editor will get this event. | [AccessRequestEvent](/api-reference/sdk/models/data-models#accessrequestevent) |
| Editor | `accessRequestCanceled` | When a user cancels their request for editor access. Editor will get this event. | [AccessRequestEvent](/api-reference/sdk/models/data-models#accessrequestevent) |
| Viewer | `accessAccepted` | When a user's request for editor access is accepted. Requesting Viewer will get this event. | [AccessRequestEvent](/api-reference/sdk/models/data-models#accessrequestevent) |
| Viewer | `accessRejected` | When a user's request for editor access is rejected. Requesting Viewer will get this event. | [AccessRequestEvent](/api-reference/sdk/models/data-models#accessrequestevent) |
| Access Assignment | `editorAssigned` | When a user is assigned as the editor. | [SEMEvent](/api-reference/sdk/models/data-models#semevent) |
| Access Assignment | `viewerAssigned` | When a user is assigned as a viewer. | [SEMEvent](/api-reference/sdk/models/data-models#semevent) |
| Editor | `editorOnDifferentTabDetected` | When the editor opens the same document in a different browser tab. Editor will get this event. | [SEMEvent](/api-reference/sdk/models/data-models#semevent) |
**Using Hooks:**
```jsx theme={null}
// Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
const eventData = useLiveStateSyncEventCallback("EVENT_TYPE");
useEffect(() => {
if (eventData) {
console.log("EVENT_TYPE data: ", eventData);
}
}, [eventData]);
```
**Using API:**
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
// Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
liveStateSyncElement.on("EVENT_TYPE").subscribe((eventData) => {
console.log("EVENT_TYPE data: ", eventData);
});
```
```javascript theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
// Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
liveStateSyncElement.on("EVENT_TYPE").subscribe((eventData) => {
console.log("EVENT_TYPE data: ", eventData);
});
```
## Best Practices
* Use `singleTabEditor` to prevent confusion when users have multiple tabs open
* Add IDs to HTML elements with sync attributes for more robust syncing
* Only apply sync attributes to native HTML elements, not framework components
# Overview
Source: https://docs.velt.dev/realtime-collaboration/single-editor-mode/overview
Allow only one user to edit at a time but still allow everyone to see the changes as they happen.
## Latency
* Extremely low latency, with typical response times no greater than 10 ms.
## Offline Support
* Optimistic local‑first reads and writes.
* Full offline support with automatic syncing when reconnected.
## Conflict Resolution
* Server timestamp-based last-write-wins strategy for automatic conflict resolution.
[Open in larger window](https://landing-page-demo-velt.vercel.app/?feature=single-editor-mode\&layout=horizontal)
# Setup
Source: https://docs.velt.dev/realtime-collaboration/single-editor-mode/setup
Enable Single Editor Mode to allow only one user to edit at a time while others remain in read-only mode.
## Prerequisites
* Node.js (v14 or higher)
* React (v16.8 or higher for hooks)
* A Velt account with an API key ([sign up](https://console.velt.dev/))
* Optional: TypeScript for type safety
## Setup
### Step 1: Setup Velt
Setup and initialize the Velt client by following the [Velt Setup Docs](/get-started/quickstart). This is required for Single Editor Mode to work.
### Step 2: Initialize Single Editor Mode
* Get the Live State Sync element via hook or client API.
* Enable Single Editor Mode. Optionally pass config:
* `customMode`: When true, SDK won’t automatically make HTML elements read-only for viewers. You must manage UI state manually or using the APIs listed [here](/realtime-collaboration/single-editor-mode/customize-behavior). Default: `false`
* `singleTabEditor`: Restrict editing to a single browser tab for the editor. Default: `true`
* Optionally enable/disable the default UI panel. Default: `enabled`
* Optionally scope the feature to specific containers.
- We recommend using the default UI for simplicity and velocity. You can customize it to change the look, feel and position of the UI. You can customize the UI by following [this guide](/ui-customization/features/realtime/single-editor-mode).
- If you do want to create your UI from scratch then you can use the following APIs to create a similar UX: [Editor](/realtime-collaboration/single-editor-mode/customize-behavior#editor) and [Viewer](/realtime-collaboration/single-editor-mode/customize-behavior#viewer).
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
useEffect(() => {
if (liveStateSyncElement) {
// Enable Single Editor Mode
liveStateSyncElement.enableSingleEditorMode({
customMode: false,
singleTabEditor: true
});
// Optional: Show default UI panel
liveStateSyncElement.enableDefaultSingleEditorUI();
// Optional: Restrict to specific containers
liveStateSyncElement.singleEditorModeContainerIds(['editor', 'rightPanel']);
}
// Cleanup on unmount
return () => liveStateSyncElement.disableSingleEditorMode();
}, [liveStateSyncElement]);
return (
{/* Single Editor Mode Panel UI */}
);
```
```html theme={null}
```
### Step 3: Set the editor
* Declare the editor for the current document/session so editing is enabled for one user while others remain read-only.
* You can set the current user as the editor programmatically on an explicit action.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
// Programmatically set the current user as the editor when they perform an explicit action. eg: start typing or interact with the UI.
function setEditor() {
liveStateSyncElement?.setUserAsEditor();
}
```
```html theme={null}
```
When the editor is set, editing tools should become enabled for that user while other users are placed in read-only mode.
You can fine tune which elements are enabled/disabled by following [this guide](/realtime-collaboration/single-editor-mode/customize-behavior#define-single-editor-mode-elements).
Learn more: [Set User as Editor](/realtime-collaboration/single-editor-mode/customize-behavior#setuseraseditor)
### Step 4: Update UI to reflect editor and viewer states
* Non-editor users are automatically viewers. Reflect that state in your UI.
* If you are using the default UI, it will automatically reflect the editor and viewer states in the UI. You can customize it to change the look, feel and position of the UI.
```jsx theme={null}
// Reflect viewer vs editor state in UI
const { isEditor, isEditorOnCurrentTab } = useUserEditorState();
return (
Role: {isEditor ? 'Editor' : 'Viewer'}
{isEditor &&
Editing on this tab: {isEditorOnCurrentTab ? 'Yes' : 'No'}
}
);
```
```html theme={null}
```
See: [Is User Editor](/realtime-collaboration/single-editor-mode/customize-behavior#isusereditor)
### Step 5: Passing access (requests + accept/reject UX)
Provide an access handoff flow so viewers can request edit access and the editor can accept or reject.
* Default UI (recommended): Use the built-in banner/controls.
* Custom UI: Use the APIs to request and respond to access.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
// Default UI (recommended): ensure default UI is enabled and/or panel is rendered
liveStateSyncElement.enableDefaultSingleEditorUI();
//
// Custom UI: Editor-side — show banner and accept/reject
const editorAccessRequested = useEditorAccessRequestHandler();
return (
{editorAccessRequested && (
{`User ${editorAccessRequested.requestedBy?.name || editorAccessRequested.requestedBy?.email} requests access`} →
liveStateSyncElement.acceptEditorAccessRequest()}>Accept
liveStateSyncElement.rejectEditorAccessRequest()}>Reject
)}
{/* Custom UI: Viewer-side — request/cancel access */}
liveStateSyncElement.requestEditorAccess().subscribe((status) => {
console.log('Request status:', status);
})
}
>
Request edit access
liveStateSyncElement.cancelEditorAccessRequest()}>Cancel request
);
```
```html theme={null}
```
Learn more: [Is Editor Access Requested](/realtime-collaboration/single-editor-mode/customize-behavior#iseditoraccessrequested), [Accept Editor Access Request](/realtime-collaboration/single-editor-mode/customize-behavior#accepteditoraccessrequest), [Reject Editor Access Request](/realtime-collaboration/single-editor-mode/customize-behavior#rejecteditoraccessrequest), [Request Editor Access](/realtime-collaboration/single-editor-mode/customize-behavior#requesteditoraccess), [Cancel Editor Access Request](/realtime-collaboration/single-editor-mode/customize-behavior#canceleditoraccessrequest)
### Step 6: Prevent concurrent editing by the same user (tab locking)
If the same user opens another tab and tries to edit, lock the editor to a single tab and guide them back to the active tab when needed.
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
const { isEditor, isEditorOnCurrentTab } = useUserEditorState();
// If the user is the editor but on the wrong tab, prompt and switch
if (isEditor && isEditorOnCurrentTab === false) {
// Show UX prompt like: "You are already editing this page in another tab. Continue editing there."
// Provide an action to move editing to this tab:
const takeOver = () => liveStateSyncElement.editCurrentTab();
// Example: Edit on this tab
}
```
```html theme={null}
```
Recommended UX copy: "You are already editing this page in another tab. Continue editing there."
Learn more: [Edit Current Tab](/realtime-collaboration/single-editor-mode/customize-behavior#editcurrenttab) and [Is User Editor](/realtime-collaboration/single-editor-mode/customize-behavior#isusereditor)
## Notes
* **Initialize Velt first**: Ensure the Velt client is initialized and the user/document are set before enabling Single Editor Mode.
* **Default UI**: The built-in UI can be shown with `enableDefaultSingleEditorUI()` and/or by rendering `
` (React) or `
` (HTML).
* **Restrict scope**: Use `singleEditorModeContainerIds([...])` to limit Single Editor Mode to specific containers instead of the entire DOM.
* **Native elements only**: When `customMode: true`, you must mark native HTML elements with attributes to control them:
* `data-velt-sync-access="true"` to enable control
* `data-velt-sync-access-disabled="true"` to exclude elements
* These attributes work on native elements only, not directly on React components.
## Testing and Debugging
**To test Single Editor Mode:**
1. Open the same document as two different users in separate browser profiles (or two windows with different auth states).
2. Try editing simultaneously. Only one user should be able to edit; the other stays read-only.
3. Use the default UI panel to request/transfer access and verify the flow.
**Common issues:**
* UI not visible: Ensure `enableDefaultSingleEditorUI()` is called or the panel component/element is rendered.
* Elements not disabled: If using `customMode: true`, ensure you set `data-velt-sync-access` attributes on native elements. Attributes do not attach to React components.
* Editor can edit in multiple tabs: Confirm `singleTabEditor` is `true`. Use `editCurrentTab()` to move the editor role to the current tab.
## Complete Example
```jsx Complete Implementation expandable lines theme={null}
import React, { useEffect } from 'react';
import {
VeltProvider,
useLiveStateSyncUtils,
VeltSingleEditorModePanel,
useUserEditorState,
useEditorAccessRequestHandler
} from '@veltdev/react';
function SingleEditorExample() {
const liveStateSyncElement = useLiveStateSyncUtils();
const { isEditor, isEditorOnCurrentTab } = useUserEditorState();
const editorAccessRequested = useEditorAccessRequestHandler();
useEffect(() => {
liveStateSyncElement.enableSingleEditorMode({ singleTabEditor: true });
liveStateSyncElement.enableDefaultSingleEditorUI();
return () => liveStateSyncElement.disableSingleEditorMode();
}, []);
return (
Active editor: {isEditor ? 'Yes' : 'No'}
Editing on this tab: {isEditorOnCurrentTab ? 'Yes' : 'No'}
{editorAccessRequested &&
Access requested by: {editorAccessRequested.requestedBy?.email}
}
);
}
export default function App() {
return (
);
}
```
[Open in larger window](https://landing-page-demo-velt.vercel.app/?feature=single-editor-mode\&layout=vertical)
## Next: Customize Behavior
Learn how to fine-tune Single Editor Mode behavior, timeouts, events, and UI.
[Go to Customize Behavior](/realtime-collaboration/single-editor-mode/customize-behavior)
# Overview
Source: https://docs.velt.dev/realtime-collaboration/video-player-sync/overview
## Video Player Sync
`Video Player Sync` allows users to sync their video player with other `Users` in the same document.
This lets all `Users` watch the same video at the same time.
# Setup
Source: https://docs.velt.dev/realtime-collaboration/video-player-sync/setup
To enable `Video Player Sync` in any video, add the `data-sync-video-player="true"` attribute to your video player.
```html theme={null}
```
Test out `Video Player Sync` by opening two clients side-by-side and having one client play the video player.
To enable `Video Player Sync` in any video, add the `data-sync-video-player="true"` attribute to your video player.
```html theme={null}
```
```jsx React / Next.js theme={null}
export default function App() {
return (
);
}
```
```html HTML theme={null}
```
# Version 3.0.0
Source: https://docs.velt.dev/release-notes/3-0-0
## Change Log
### Improvements
* \[**Comments**]: Added pagination in autocomplete option dropdown for improved performance.
* \[**Comments**]: Added "Edit Comment" option in header options dropdown menu for editing the first comment. Only visible to comment author and admins. [Learn more](/ui-customization/features/async/comments/comment-dialog/subcomponents/options-dropdown).
* \[**Recorder**]: Added `shadow dom` prop in Recorder Player component to control shadow DOM encapsulation.
Using Props:
```jsx theme={null}
```
Using Props:
```html theme={null}
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue where `summary` prop was not working in Recorder Player component.
### Improvements
* \[**Core**]: Added updates to the core library packages.
### Improvements
* \[**Core**]: Added accessibility props and test ids for most Velt components within Comments, Notifications, Live Selection features.
### Features
* \[**UI Customization**]: Added additional CSS variables for z-index customization. [Learn more](/ui-customization/customize-css/themes#available-theme-variables).
* `--velt-comment-pin-z-index: 2147483557`: For Components like Comment Pin, Triangle, etc.
* `--velt-arrow-z-index: 2147483557`: For Arrow Component.
* `--velt-recorder-player-z-index: 2147483557`: For Recorder Player Component.
* `--velt-cursor-z-index: 2147483647`: For Cursor Component.
* `--velt-persistent-comment-frame-z-index: 2147483647`: For Persistent Comment Frame Component.
* `--velt-toast-popup-z-index: 2147483647`: For Toast Popup Component.
* `--velt-live-state-sync-overlay-z-index: 2147483647`: For Live State Sync Overlay Component.
* `--velt-follow-mode-overlay-z-index: 2147483647`: For Follow Mode Overlay Component.
* `--velt-comments-minimap-z-index: 2147483637`: For Comments Minimap Component.
* `--velt-global-overlay-z-index: 2147483637`: For Global Overlay Component.
### Improvements
* \[**Comments**]: Improved comment annotation view count logic.
### Bug Fixes
* \[**Comments**]: Fixed an issue where default custom status was not applied when adding new comment annotation.
* \[**Comments**]: Added support for setting dynamic `targetElementId` value asynchronously in comment tool.
* \[**Comments**]: Fixed an issue where assign dropdown input wasn't closing after user selection.
* \[**Comments**]: Fixed padding around assign dropdown.
* \[**Comments**]: Fixed horizontal scroll and overflow issues for longer emails in assign dropdown input and assign banner.
This release changes how users navigate to comments from the sidebar. We've made navigation more explicit by requiring users to click a dedicated button rather than the comment itself.
### Improvements
* \[**Comments**]: Added navigation button as default in Comments Sidebar.
* Now just clicking on a comment doesn't open the comment on the DOM.
* A lot of users reported this behaviour as frustrating. That's why now there is an explicit navigation button for that.
* You will still get the [`onCommentClick`](/async-collaboration/comments-sidebar/customize-behavior#oncommentclick) event. You can still use that to maintain the old behavior [using this](/async-collaboration/comments/customize-behavior#selectcommentbyannotationid).
* If you had previously used a wireframe for the comment dialog, you will need to add the [navigation button wireframe](/ui-customization/features/async/comments/comment-dialog/subcomponents/header) to show the navigation button.
* \[**Comments**]: Added feature to show resolved comments on for inline comments section.
* \[**Comments**]: Disabled collapsed comments by default. Most customers don't want to show collapsed comments by default so we disabled it. You can enable it by setting using [this](/async-collaboration/comments/customize-behavior#enablecollapsedcomments).
* \[**Comments**]: Disabled auto-focus on new comments in inline comments section and sidebar page mode.
### Bug Fixes
* \[**Comments**]: Fixed inline comment section horizontal scroll issue when the available width is too narrow.
* \[**Comments**]: Fixed Composer Send Button vertical alignment.
* \[**Comments**]: Removed autofocus for inline comments section when the page loads.
* \[**Comments**]: Fixed issue where clicking composer in edit mode was passing clicks through to the sidebar.
* \[**Comments**]: Fixed copy paste replace issue in the composer where pasting text over selected text was not working correctly.
* \[**Comments**]: Fixed Velt Button Sidebar wireframe loading default UI initially.
* \[**Comments**]: Fixed unread comments count APIs to return correct values on document switch.
### Improvements
* \[**Core**]: Added library upgrades to the SDK.
### Improvements
* \[**Comments**]: Added `readOnly` property in comment sidebar context so that it's available in `Velt Data` and `Velt If` features.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the Comments Sidebar was emitting multiple update calls.
* \[**UI Customization**]: Updated border radius for the status component in the Comment Dialog.
### Features
* \[**Velt Button**]: Added a customizable button component that can be used to add custom actions and extend the functionality of any Velt component. [Learn more](/ui-customization/custom-action-component). Some examples include:
* Add custom filtering, sorting and grouping to the Comment Sidebar.
* Add custom actions to each item in the Notifications panel.
* Add custom actions to the Comment Dialog.
* \[**Theme Playground**]: Added a new [Theme Playground](https://playground.velt.dev/themes). You can customize and test your themes. It will generate CSS variables that you can just copy and paste into your app.
### Improvements
* \[**Comments**]: Added additional wireframes for Comment Dialog Toggle Reply Component. [Learn more](/ui-customization/features/async/comments/comment-dialog/subcomponents/body/subcomponents/togglereply).
* \[**Comments**]: Now users can't submit empty comments during editing.
* \[**Comments**]: Improved the padding on the @mention autocomplete dropdown.
* \[**Ergonomics**]: Updated `useCommentActionCallback` to `useCommentEventCallback`. The old hook name will continue to work.
* \[**Ergonomics**]: For Sidebar Custom actions and filters, updated APIs and Hooks:
* `onCommentSidebarInit` can now be also be accessed with `useCommentEventCallback('commentSidebarDataInit')` or `commentElement.on('commentSidebarDataInit')`
* `onCommentSidebarData` can now be also be accessed with `useCommentEventCallback('commentSidebarDataUpdate')` or `commentElement.on('commentSidebarDataUpdate')`
### Bug Fixes
* \[**Comments**]: Fixed an issue where the Comment sidebar group count was not updated correctly.
* \[**Comments**]: Fixed an issue where attached files were not displayed on the very first comment in inline and page mode comments.
* \[**Comments**]: Fixed a UI issue where attachments were misaligned when a comment was edited.
* \[**Comments**]: Fixed a UX issue where the comment composer did not scroll into view when comment edit button was clicked.
* \[**Comments**]: Resolved a UI issue where the Page Mode composer in the comment sidebar during embed mode was not taking the full width of the parent container.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the Comment Annotation unread count was not updated correctly.
### Features
* \[**Comments**]: Added ability to make comments read-only. When comments are made read-only, any features requiring user interaction (e.g., Composer, Reactions, Status) will be removed.
Using Props:
```jsx theme={null}
```
Using API:
```js theme={null}
const commentElement = client.getCommentElement();
commentElement.enableReadOnly();
commentElement.disableReadOnly();
```
Using Props:
```html theme={null}
```
Using API:
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableReadOnly();
commentElement.disableReadOnly();
```
### Bug Fixes
* \[**Comments**]: Fixed image lightbox CSS related issue
### Bug Fixes
* \[**Comments**]: Fixed undefined value error while updating context.
### Bug Fixes
* \[**Comments**]: Added `composerVariant` prop to `VeltInlineCommentsSection` component to support inline composer variant
```jsx theme={null}
```
```html theme={null}
```
* \[**UI**]: Minor CSS fixes and improvements
### Bug Fixes
* \[**Comments**]: Fixed undefined value error while updating context.
### Improvements
* \[**Comments**]: Added support for programatic selection and scrolling to comments in the `velt-comment-text` component used in Tiptap Comments.
### Features
* \[**Comments**]: Added ability to customize the placeholder text shown in the search input of the comments sidebar:
```jsx theme={null}
...
...
```
```html theme={null}
...
...
```
### Features
* \[**Comment**]: Added [new APIs, hooks and event callbacks](/async-collaboration/comments/customize-behavior) for each action that the user can perform on the Comment's Feature Components or API:
* **APIs & Hooks**:
* **Comment Annotation**: addCommentAnnotation, deleteCommentAnnotation
* **Comment**: addComment, deleteComment, updateComment, getComment
* **@Mention**: subscribeCommentAnnotation, unsubscribeCommentAnnotation, assignUser
* **Reaction**: addReaction, deleteReaction, toggleReaction
* **Attachment**: addAttachment, deleteAttachment, getAttachment
* **Status & Priority**: updateStatus, updatePriority, resolveCommentAnnotation
* **Recording**: getRecording, deleteRecording
* **Deep Link**: getLink, copyLink
* **Moderation**: approveCommentAnnotation, acceptCommentAnnotation, rejectCommentAnnotation, updateAccess
* **More Event Callbacks**:
* [On](/async-collaboration/comments/customize-behavior#on)
* [Here](/async-collaboration/comments/customize-behavior#on) is the list of events you can subscribe to.
* \[**Metadata**]: Get the currently set organization, document and location objects:
```jsx theme={null}
const metadata = await client.getMetadata();
```
* \[**UI Customization**]: Added ability to customize comment sidebar search placeholder:
```jsx theme={null}
```
```html theme={null}
```
### Features
* \[**UI Customization**]: Extended themes to include more components:
* Comments:
* Autocomplete
* Tooltip
* Chart Comments
* Text Comment Toolbar
* Comment Inbox
* Comment Text Portal
* Persistent Comment Mode
* Minimap
* Inline Comment Section
* Multi Thread Comments
* Notifications
* Notifications Panel
* Notification Tool
### Features
* \[**UI Customization**]: Introducing Themes! Now you can customize the look and feel of Velt components using CSS variables. This enables you to match Velt's UI with your application's design system.
* The following components now support theming (others will be added soon):
* Comment Components
* Recording Components
* Reactions
* You can customize:
* Border radius
* Spacing
* Typography
* Colors for light and dark modes
* Base Colors
* Accent Colors
* Text Shades
* Background Shades
* Border Shades
* Status Colors (error, warning, success)
* Transparent colors
* Learn more about UI Customization [here](/ui-customization/overview).
```css theme={null}
--velt-border-radius-2xs: 0.125rem; // 2px
--velt-border-radius-xs: 0.25rem; // 4px
--velt-border-radius-sm: 0.5rem; // 8px
--velt-border-radius-md: 0.75rem; // 12px
--velt-border-radius-lg: 1rem; // 16px
--velt-border-radius-xl: 1.25rem; // 20px
--velt-border-radius-2xl: 1.5rem; // 24px
--velt-border-radius-3xl: 2rem; // 32px
--velt-border-radius-full: 5rem; // 80px
```
```css theme={null}
--velt-spacing-2xs: 0.125rem; // 2px
--velt-spacing-xs: 0.25rem; // 4px
--velt-spacing-sm: 0.5rem; // 8px
--velt-spacing-md: 0.75rem; // 12px
--velt-spacing-lg: 1rem; // 16px
--velt-spacing-xl: 1.25rem; // 20px
--velt-spacing-2xl: 1.5rem; // 24px
```
```css theme={null}
--velt-default-font-family: sans-serif;
--velt-font-size-2xs: 0.625rem; // 10px
--velt-font-size-xs: 0.75rem; // 12px
--velt-font-size-sm: 0.875rem; // 14px
--velt-font-size-md: 1rem; // 16px
--velt-font-size-lg: 1.5rem; // 24px
--velt-font-size-xl: 1.75rem; // 28px
--velt-font-size-2xl: 2rem; // 32px
```
```css theme={null}
/* Base Colors */
--velt-light-mode-green: #0DCF82;
--velt-light-mode-magenta: #A259FE;
--velt-light-mode-amber: #FF7162;
--velt-light-mode-purple: #625DF5;
--velt-light-mode-cyan: #4BC9F0;
--velt-light-mode-orange: #FE965C;
--velt-light-mode-black: #080808;
--velt-light-mode-white: #FFFFFF;
--velt-light-mode-gray: #EBEBEB;
/* Accent Colors */
--velt-light-mode-accent: #625DF5;
--velt-light-mode-accent-text: #9491F8;
--velt-light-mode-accent-hover: #534FCF;
--velt-light-mode-accent-foreground: #FFFFFF;
--velt-light-mode-accent-light: #F2F2FE;
--velt-light-mode-accent-transparent: rgba(148, 145, 248, 0.08);
/* Text Shades */
--velt-light-mode-text-0: #0A0A0A;
--velt-light-mode-text-1: #141414;
--velt-light-mode-text-2: #1F1F1F;
--velt-light-mode-text-3: #292929;
--velt-light-mode-text-4: #3D3D3D;
--velt-light-mode-text-5: #525252;
--velt-light-mode-text-6: #666666;
--velt-light-mode-text-7: #7A7A7A;
--velt-light-mode-text-8: #858585;
--velt-light-mode-text-9: #999999;
--velt-light-mode-text-10: #B8B8B8;
--velt-light-mode-text-11: #A3A3A3;
--velt-light-mode-text-12: #8F8F8F;
/* Background Shades */
--velt-light-mode-background-0: #FFFFFF;
--velt-light-mode-background-1: #FAFAFA;
--velt-light-mode-background-2: #F5F5F5;
--velt-light-mode-background-3: #F0F0F0;
--velt-light-mode-background-4: #EBEBEB;
--velt-light-mode-background-5: #E5E5E5;
--velt-light-mode-background-6: #E0E0E0;
--velt-light-mode-background-7: #DBDBDB;
--velt-light-mode-background-8: #D6D6D6;
--velt-light-mode-background-9: #D1D1D1;
--velt-light-mode-background-10: #CCCCCC;
/* Border Shades */
--velt-light-mode-border-0: #FFFFFF;
--velt-light-mode-border-1: #FAFAFA;
--velt-light-mode-border-2: #F5F5F5;
--velt-light-mode-border-3: #F0F0F0;
--velt-light-mode-border-4: #EBEBEB;
--velt-light-mode-border-5: #E5E5E5;
--velt-light-mode-border-6: #E0E0E0;
--velt-light-mode-border-7: #DBDBDB;
--velt-light-mode-border-8: #D6D6D6;
--velt-light-mode-border-9: #D1D1D1;
--velt-light-mode-border-10: #CCCCCC;
/* Status Colors */
/* Error */
--velt-light-mode-error: #FF7162;
--velt-light-mode-error-hover: #DE5041;
--velt-light-mode-error-foreground: #FFFFFF;
--velt-light-mode-error-light: #FFF4F2;
--velt-light-mode-error-transparent: rgba(255, 113, 98, 0.08);
/* Warning */
--velt-light-mode-warning: #FFCD2E;
--velt-light-mode-warning-hover: #C69400;
--velt-light-mode-warning-foreground: #474747;
--velt-light-mode-warning-light: #FFFBEE;
--velt-light-mode-warning-transparent: rgba(255, 205, 46, 0.08);
/* Success */
--velt-light-mode-success: #198F65;
--velt-light-mode-success-hover: #006B41;
--velt-light-mode-success-foreground: #FFFFFF;
--velt-light-mode-success-light: #EDF6F3;
--velt-light-mode-success-transparent: rgba(25, 143, 101, 0.08);
/* Transparent Colors */
--velt-light-mode-background-transparent: rgba(255, 255, 255, 0.80);
--velt-light-mode-border-transparent: rgba(0, 0, 0, 0.16);
--velt-light-mode-animation-transparent: rgba(255, 255, 255, 0.2);
```
```css theme={null}
/* Base Colors */
--velt-dark-mode-green: #0DCF82;
--velt-dark-mode-magenta: #A259FE;
--velt-dark-mode-amber: #FF7162;
--velt-dark-mode-purple: #625DF5;
--velt-dark-mode-cyan: #4BC9F0;
--velt-dark-mode-orange: #FE965C;
--velt-dark-mode-black: #080808;
--velt-dark-mode-white: #FFFFFF;
--velt-dark-mode-gray: #EBEBEB;
/* Accent Colors */
--velt-dark-mode-accent: #625DF5;
--velt-dark-mode-accent-text: #9491F8;
--velt-dark-mode-accent-hover: #534FCF;
--velt-dark-mode-accent-foreground: #FFFFFF;
--velt-dark-mode-accent-light: #F2F2FE;
--velt-dark-mode-accent-transparent: rgba(148, 145, 248, 0.08);
/* Text Shades */
--velt-dark-mode-text-0: #FFFFFF;
--velt-dark-mode-text-1: #F5F5F5;
--velt-dark-mode-text-2: #EBEBEB;
--velt-dark-mode-text-3: #E0E0E0;
--velt-dark-mode-text-4: #D6D6D6;
--velt-dark-mode-text-5: #C2C2C2;
--velt-dark-mode-text-6: #ADADAD;
--velt-dark-mode-text-7: #8F8F8F;
--velt-dark-mode-text-8: #7A7A7A;
--velt-dark-mode-text-9: #666666;
--velt-dark-mode-text-10: #525252;
--velt-dark-mode-text-11: #474747;
--velt-dark-mode-text-12: #3D3D3D;
/* Background Shades */
--velt-dark-mode-background-0: #0F0F0F;
--velt-dark-mode-background-1: #1A1A1A;
--velt-dark-mode-background-2: #1F1F1F;
--velt-dark-mode-background-3: #242424;
--velt-dark-mode-background-4: #292929;
--velt-dark-mode-background-5: #2E2E2E;
--velt-dark-mode-background-6: #333333;
--velt-dark-mode-background-7: #383838;
--velt-dark-mode-background-8: #3D3D3D;
--velt-dark-mode-background-9: #424242;
--velt-dark-mode-background-10: #474747;
/* Border Shades */
--velt-dark-mode-border-0: #0F0F0F;
--velt-dark-mode-border-1: #1A1A1A;
--velt-dark-mode-border-2: #1F1F1F;
--velt-dark-mode-border-3: #242424;
--velt-dark-mode-border-4: #292929;
--velt-dark-mode-border-5: #2E2E2E;
--velt-dark-mode-border-6: #333333;
--velt-dark-mode-border-7: #383838;
--velt-dark-mode-border-8: #3D3D3D;
--velt-dark-mode-border-9: #424242;
--velt-dark-mode-border-10: #474747;
/* Status Colors */
/* Error */
--velt-dark-mode-error: #FF7162;
--velt-dark-mode-error-hover: #DE5041;
--velt-dark-mode-error-foreground: #FFFFFF;
--velt-dark-mode-error-light: #FFF4F2;
--velt-dark-mode-error-transparent: rgba(255, 113, 98, 0.08);
/* Warning */
--velt-dark-mode-warning: #FFCD2E;
--velt-dark-mode-warning-hover: #C69400;
--velt-dark-mode-warning-foreground: #474747;
--velt-dark-mode-warning-light: #FFFBEE;
--velt-dark-mode-warning-transparent: rgba(255, 205, 46, 0.08);
/* Success */
--velt-dark-mode-success: #198F65;
--velt-dark-mode-success-hover: #006B41;
--velt-dark-mode-success-foreground: #FFFFFF;
--velt-dark-mode-success-light: #EDF6F3;
--velt-dark-mode-success-transparent: rgba(25, 143, 101, 0.08);
/* Transparent Colors */
--velt-dark-mode-background-transparent: rgba(0, 0, 0, 0.80);
--velt-dark-mode-border-transparent: rgba(255, 255, 255, 0.16);
--velt-dark-mode-animation-transparent: rgba(255, 255, 255, 0.2);
```
* Example of theme customisation:
* You can update the following variables in `` tag to change the theme.
* For testing, try copy pasting the following sample themes in body tag:
```css theme={null}
body {
--velt-light-mode-accent: #0BA528;
--velt-light-mode-accent-text: #0BA528;
--velt-light-mode-accent-hover: #08841F;
--velt-light-mode-accent-foreground: #FFFFFF;
--velt-light-mode-accent-light: #DFF9E4;
--velt-light-mode-accent-transparent: rgba(11, 165, 40, 0.08);
--velt-dark-mode-accent: #0BA528;
--velt-dark-mode-accent-text: #0BA528;
--velt-dark-mode-accent-hover: #08841F;
--velt-dark-mode-accent-foreground: #FFFFFF;
--velt-dark-mode-accent-light: #DFF9E4;
--velt-dark-mode-accent-transparent: rgba(11, 165, 40, 0.08);
--velt-border-radius-2xs: 2px;
--velt-border-radius-xs: 2px;
--velt-border-radius-sm: 2px;
--velt-border-radius-md: 2px;
--velt-border-radius-lg: 2px;
--velt-border-radius-xl: 2px;
--velt-border-radius-2xl: 2px;
--velt-border-radius-3xl: 2px;
--velt-border-radius-full: 2px;
}
```
### Features
* \[**Comments**]: Added support for passing Comment Annotation objects to Standalone Comment Thread component.
* When using annotations from other documents:
* Comments will be read-only
* Reactions and recordings will not be rendered
* This enables creating Kanban boards by fetching comment annotations from multiple documents using our REST APIs.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `shortUserName` feature to control display of user names.
* For long names, this will first create an initial of the second name and if the name is still long, it will truncate it with ellipses.
* It's enabled by default.
**Using Props:**
```jsx theme={null}
```
**Using API:**
```js theme={null}
const commentElement = client.getCommentElement();
commentElement.enableShortUserName();
commentElement.disableShortUserName();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableShortUserName();
commentElement.disableShortUserName();
```
### Improvements
* \[**Comments**]: Added support for light mode in the contact chip tooltip.
* \[**Comments**]: Removed unnecessary filtering in the sidebar when comment annotation selection changes.
### Bug Fixes
* \[**Comments**]: Fixed an issue where `onCommentSidebarData` event was getting triggered multiple times on sidebar clicks.
* \[**Comments**]: Fixed an issue where empty placeholder was not being displayed in the sidebar for page mode and custom action filters.
### Bug Fixes
* \[**Comments**]: Fixed issue where bottom sheet was incorrectly opening in the sidebar on mobile devices.
### Features
* \[**Comments**]: Fixed issue where edit comment composer was not appearing in the sidebar.
* \[**Comments**]: Added offset property to Comment Player Timeline. This allows comment bubbles to be positioned relative to both parent and child video clips.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Dynamic Hooks**]: Now the LiveStateSync and Views hooks support dynamic input parameters for non-primitive data types.
### Bug Fixes
* \[**Comments**]: Fixed issue where edit comment composer was not appearing in the sidebar.
### Features
* \[**REST APIs**]: Added advanced queries and pagination for GET endpoints.
* You need to upgrade to version 3.0.61 and enable this in your developer console.
* Check out the [V2 REST APIs endpoints](/api-reference/rest-apis/comments-feature/comment-annotations/get-comment-annotations-v2) for more information.
### Features
* \[**Comments**]: Added ability to enable/disable recording transcription feature on recorder:
**Using Props:**
```jsx theme={null}
```
**Using API Methods:**
```javascript theme={null}
// Using comment element
const commentElement = client.getCommentElement();
commentElement.enableRecordingTranscription();
commentElement.disableRecordingTranscription();
// Or using recorder element
const recorderElement = client.getRecorderElement();
recorderElement.enableRecordingTranscription();
recorderElement.disableRecordingTranscription();
```
**Using Props:**
```html theme={null}
```
**Using API Methods:**
```javascript theme={null}
// Using comment element
const commentElement = Velt.getCommentElement();
commentElement.enableRecordingTranscription();
commentElement.disableRecordingTranscription();
// Or using recorder element
const recorderElement = Velt.getRecorderElement();
recorderElement.enableRecordingTranscription();
recorderElement.disableRecordingTranscription();
```
### Bug Fixes
* \[**Comments**]: Fixed range error that occurred when recording without comment text.
### Bug Fixes
* \[**Comments**]: Fixed several issues with the comment dialog and inline comments:
* Fixed cursor position not being set correctly when focusing comment input
* Fixed an issue where editing a comment and saving it as a draft created a new comment
* Fixed an issue where sometimes comment pin was visible on the inline comments section
### Improvements
* \[**Comments**]: Added ability to sort inline comments:
* `asc`: Show comments in ascending order of last updated timestamp
* `desc`: Show comments in descending order of last updated timestamp
* `none`: Show comments in order of creation (default)
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Further improved how empty comments are handled:
* Empty comments are now hidden from the sidebar and sidebar button count
* In popover mode, clicking a new comment button discards any previous empty comment
* \[**Comments**]: Added delete option for each comment in annotations when user has admin privileges.
### Bug Fixes
* \[**Notifications**]: Optimized and fixed issues related to loading notifications on `documentId` and `organizationId` change.
### Improvements
* \[**Comments**]: Improved the system grouping logic for locations.
* \[**Comments**]: Enhanced `updateContext` logic to prevent unnecessary updates if the passed context object remains unchanged.
* \[**Comments**]: Exposed `commentAnnotations` variable in the comments sidebar so that it can be used in `Velt Data` and `Velt If` Components.
### Bug Fixes
* \[**Comments**]: Fixed an issue where adding the `Copy Link` wireframe component in the header options menu generated undefined Comment URLs.
### Improvements
* \[**Comments**]: The "clear filter" button in the sidebar now only appears when comments are hidden due to active filters, not when there are no comments on the document.
### Bug Fixes
* \[**Comments**]: Fixed an issue with the Assign feature across different comment modes:
* Page mode
* Inline mode
* Multi-thread mode
* \[**Comments**]: Fixed an issue where reactions were not updated in focused thread mode.
### New Features
* \[**Comments**]: Now comments are supported on elements with duplicate DOM IDs.
* This is useful in cases where you have multiple instances of the same data component on a page and want the comment to appear on each instance, eg: Popover comments on a table.
* By default, comments appear on all matching elements. Use the `sourceId` attribute to control which element displays the comment dialog when adding a new comment.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: In multithread mode, now you can deselect a thread by clicking on the header of the multi-thread dialog.
* \[**Comments**]: Removed self user from the list of users in the "Seen By" dropdown.
* \[**Comments**]: Removed users without name and email from the list of users in the "Seen By" dropdown.
### Bug Fixes
* \[**Tiptap Comments**]: Removed `data-velt-content` attribute. Now the highlighted text styling is only applied to text when there are comments available.
* \[**Comments**]: Fixed an issue where undefined and null string values appeared in individual and group contact lists.
### Bug Fixes
* \[**Security**]: Added security updates.
### New Features
* \[**Recorder**]: Added `getRecordingData` API to fetch [recording data](/api-reference/models/RecorderData) including transcript, summary, and recording URLs.
**Using Hook:**
```jsx theme={null}
const recorderData = useRecordingDataByRecorderId('-O9yTMWmEe5u6YGX8EFV');
useEffect(() => {
console.log('Recorder Data: ', recorderData);
}, [recorderData]);
```
**Using API:**
```jsx theme={null}
const recorderElement = client.getRecorderElement();
recorderElement.getRecordingDataByRecorderId("-O9yGiYS8lehOXMpQf4j").subscribe((recorderData) => {
console.log('Recorder Data: ', recorderData);
});
```
```javascript theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.getRecordingDataByRecorderId("-O9yGiYS8lehOXMpQf4j").subscribe((recorderData) => {
console.log('Recorder Data: ', recorderData);
});
```
### Improvements
* \[**Comments**]: In the sidebar, changed default `isExpanded` behavior in custom filtering. If not explicitly set, the first group will be expanded while remaining groups are collapsed.
### New Features
* \[**Comments**]: Added variant support to the `Velt Comment Pin` component. This is useful for customizing how the pin looks on different elements like charts, tables, etc.
```jsx theme={null}
```
```html theme={null}
```
* \[**Access Control**]: Enabled users logged in with "Org A" to access documents belonging to "Org B".
* By default, users can only access documents within their own organization.
* You can enable cross-organization access by specifying the `organizationId` of the target document in the document metadata.
* Ensure that the user has access to the target document in the target organization.
**Using Hook:**
```jsx theme={null}
useSetDocument(DOCUMENT_ID, {
organizationId: 'ANOTHER_ORGANIZATION_ID'
});
```
**Using API:**
```jsx theme={null}
client.setDocument(DOCUMENT_ID, {
organizationId: 'ANOTHER_ORGANIZATION_ID'
});
```
```javascript theme={null}
Velt.setDocument(DOCUMENT_ID, {
organizationId: 'ANOTHER_ORGANIZATION_ID'
});
```
* \[**Comments**]: Added ability to toggle the "Seen By" feature:
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableSeenByUsers();
commentElement.disableSeenByUsers();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableSeenByUsers();
commentElement.disableSeenByUsers();
```
### Improvements
* \[**Live Selection**]: Improved the live selection UI.
* \[**Recording**]: Added new wireframes for the recording feature:
* Media Source Settings
* Recorder All Tool
* Recorder All Tool Menu
* Recorder Audio Tool
* Recorder Video Tool
* Recorder Screen Tool
* Recording Preview Steps Dialog
* Recorder Control Panel
* Recorder Player
* Video Player
* Subtitles
* Transcription
* \[**Comments**]: Updated the empty state UI and added a clear filter button in the sidebar.
* \[**Comments**]: The "Custom filters" applied by the user are now stored in session storage just like the System filters.
### Improvements
### Bug Fixes
* \[**Comments**]: Fixed an issue in Tiptap editor where the comment dialog closed prematurely after adding a comment in single-thread mode.
* \[**Comments**]: Fixed an issue on minimap where clicking on it was not navigating to the comment location.
* \[**Comments**]: Fixed an issue where image attachments in comments were not opening in the lightbox.
* \[**Comments**]: Fixed an issue where the "AtHere" was not working when the label was set to "all".
### Improvements
* \[**Security**]: Added security updates.
### Improvements
* \[**Comments**]: Added dark mode support for the "Seen By" dropdown in the comment dialog.
### New Features
* \[**Webhooks**]: Added configuration option to encrypt webhook payloads using a secret key.
* Configure this option in the [Velt Console](https://console.velt.dev/dashboard/config/webhook).
* Encryption details:
* Payload encryption: AES-256-CBC
* Key encryption: RSA with PKCS1 OAEP padding and SHA-256 hash
* Public key format:
* Provide only the base64-encoded key string, without PEM headers/footers
* Recommended key size: 2048 bits
* Example of setting up encryption for Node.js:
```js theme={null}
{
"encryptedData": "1rtsa9UVvXzkP+u0ax2TOlz6xKcwKXhmtHyQF1I4II8X4n9uYb944Q/6AfUNFc2zQj9+AWJIV1Gtoo0j+j5VI8qS4kCVnP4In6v0I3wVECldgZsNAwwD4wKp85OJZUJL4scQmJJK+XXmMNGOW094BcIIa6zKRqYKja5RBm5zEj3k1qsP3WZkUXpggJ4FNuHkWX2nkoDLP5Rby6CY186TEeBIxY+aKS6FyWmOiDDC6ZfuY++BFNJbksNvsbBogDqHB2qa30nK9oEcOKSsXdU4AYof/mPOG01fK2diK3vyk4qcL83mJ0cXm7+SbM+FBFeJpdR+A7iIez1XrdnGlAqppnSfDoNBv2WZ/lRyZJWOyW7QHySMNTn746+JDr8oltIBDVUx5c2m8A/YeQ6E3wWEjjRZcfz3GNSzpEx+jqeNxS0StN7BUXUyHt3786EaXiUtjb2OtrP56mlidXytdHhPZPOy7stRwHnwgXfm5aLsS2yJSs3gSSUubL+ka4fhaJsqxtgXQATSh0RtNXSmAbx930DKn2DipbP23fJRduju/GP1nHnKuy8bOuyB5Du//RrysvKVC4+lMd4mVIc7cSXe25qcPjJFZGpJtJdkNwOZoWCmxMSdR32HBgo7KWJeOWqnWyuLdjQOaxol+JtTu8lopeQk7qfncEXMLcT7YRVQ4t1LZ5T9o4pZEtaOg1LwyX58VQS1OHvgBFWlEPxLfdS1r4c1YzMXLNA4sfYEp06Z11IlEFVCtWobK5//tLc+sIpwfMzdJ3VtVl9Z2XB9kASlnHf88eOdtzvn5A0CRhVBY/v855CttAy/WlPINtXxXSxm9oVMjrBFueWAZ3LQiXDl25to62L5i0NR93zEBKj1BG8egy3F27o8s5kcvrwpc3NGrmDe7x3S11noDAFsxZRWpHnRIapHcsrLWOjWVEumvUxlApKGKL3Ax80XBoN+aTNG4SXGq3dRHSneIs/MNSb0BGWoOD5U5ow58R1tvpzJHtLLnmesL1Vhr23Cug8KHU2q7+e8AnGGPTJIRKfVXjocMDclhDAk5/nuvtUTYG/hRZEQ1yCx3T7H08I6GvyOv4ErtKr+r883hXSYzf1K9eqk7de5mnmxwSEiAh0zagvZ+lMYhWpayeo+xHvtoyzfTsLNyXKc6AYZxfoIVK6UuBfkDnXiAh+NuJDa3wKwig13gQX8GmdJXeSSatI6uuXI1IU5xKIXysaHeAOaHfni+cfDgvUZTtVbWc1qDcNOVEUSl9KsjOUUgdzvST1tJ1ezMNZFbhlrPB3t5y0XvM9QQh1GyyeABxHl8nH/Icrp2Shf5vBntNbRZ3PlzK7nVtgTxXaKhZnGobwY7uruPpahNfkEi83JvOOnHeHBMXrVMAr8GHDRi8099wzvJRHYcb2p6eWocQsDV1X6tcTLuxj3EHGwykWREkkTDQ5C/F40n97PP0U2cxSGJIMePUwgAYw5OFo0dJMsU1HvXjm+2JoO8DkdwPl3Bc9F22trvsA3QecUCKQDGMTuFrFxtlubtJYtVl7w3pBST0SCKx3G2QiycRz0FMWv2FJpazQl6jE4xEqeKf7fiUn/QIo4Levk745LPhfr2tzlXbkdZ2q9TtmSAs5hjpK7ndswbIbvV8Ju5V8mDJXSR0y0NKG2C/8/vTB0xfqYtW/Bv3cXj6do9UQzP6fOFC4SGvYh/l8yohJmCTFq0tETqvZr9Atw9ZOz2cIBFx76wlS/eR9iB/JZ3DGM+2THC6Mjv70ipWX32UW7620Bb5KONm3Vw0eeIHckUn6QaHGfFL/URT6mr7YCJhG5lZynWYZcLv/ffWuFcSmO9p0xCrwqqPEjdaaGs52mqmA4Ikt9MulKAEp6p65V1vxt7Tdy6m9UVjzbEy1zFuU9iOHBAAaj6A8Mj1EEUe6sNx3fLHnC2c0+2Zf3eUxMZPm5dQZPOUXLI28yoCliBIhTYTSh7ATULDDvcnNMs/ziuG7WT/U1wuIHkT5kEE73tnG1EZY4RDODbQobmpBegcuUEh64HEGS7+aK/KPYWxFxWW5oVd0Dc7kvpariXqEhlNdDY65b2T8uBw8bI/HrfvT8d0EnsPz26B1xKZYqyusWnlR+10KdYzPNoupx8vWk74PW8zI5qlcV497SPtvn12a3wvZ8adJzMuP4hsBoKHG/M2nf0lOMbo1gcbHbT0FqcHE3mixY3lU+UnNC5jpmNCs1tK8yqeQdVtHE3YM4Y5SsnBTJddUWVpUxZ6rlU+H2NW/uGcDLBs3HmERTn1l6E1mmqKB2kPA/+Y/YbILXNojbkgRE/3lki5kX4+pjHDxF/mWEEeXpjIl4yKG97mVS2J0dGoJ5CqLv6/CdHhtwu35UydBVDVGHywufVLwPgEiDA9RklM/bQw3ojdlTrn6+irDcz8/Tj7KmK2votLaN6yIEM8Ex2htyBlyX/47eEsh63nSNwSx+uPcTxjH9N5cJpWzJ2KcBMIqZsWOTgISBUndgRdoVTFySY2XwbHlDjh8RCLLBsYRhvOK+nvNqEBnrfzz81B/sqDO1whQDTKT3ZcFnZouaVImRGHcOt0sRioq/JGHAHzRjyc/V9Gb/zTlI8QQob5y5k7dfReAy1rGdkeIa3LXSwWGz8hDjEnGsGGIC4evdiefgoJHkhzEywi/QUEOOnqms/0BzexbLP+89qMgGMlEbA9iLAW/BZgsAkxm+NHqGNtz9HDJStpqewElgjMQ+wV3TUGbrmY0O/FyQn/CXyhXjdRC0/5S1tZnzBMyolHF2a5L5EAzGck2MuV7TgLs6LcvGm7kIeq0vmBCkiUB4IBHMhraU7Ba+cC+CW7tDK0Tkanri5KSMXSXamJpU869Jcsk1JLm69ATMl3eIb5rPx5+GbPUrRogEUP3HQeLMQP8jjq6fVwzGPQByF70t0fE+Z23NuCLzhVss0YkMmzcKK8GjKCJ0vnCA0qanxovpDgCOHjgxvy44N+QNWfUynIKVHS9m7FDE3RgKf7rOfSM9vJ7F/KWo7kywi36ajuFbWcON/MTvlpPUhGm5dboiz3vyfpTWkQbd9XX7SPVBWCkvGg+A87R7RSN8bsWbmYm5m2wt3jrkBVSDn5FV3rek6X0GSpTDTWJ9ktmjKtshplXn7fx7XAKtS4hpEMGhZwi/LWvfTsGqOJlqi2FwYPLI7SVunch2VSfssejrfwxJHPqF50wTv6ax28lp7wToqsVunZprdhyY++gds/LAz083dZLM3EYcbHuGVXiNRFxptpiQNjEnyjZX0fc8UF1W2icDt7Gd5Pp2ckaPERLE+tJ+ackMxomH2/HjFB3XRXlDCoKuljtJ2cbw/gVPmHtV7Qw2w6tWaCzYP3g1D47BlrIqBV4RWjcPRjthfcWPnwUSSHwlJ4dLMQ+cJ402ol+HUukAKpkh5lcjME0uaD8KKReD/Ee9r4kubIR7z9JViXjnJJl3Jxr6KtK3abrg8cG8qVFRr5NDhxbfs9NY/zGDvbgt0GMWXRTi4oMrSkDKthZSWjVezDzPk11AMQ1E+SJSoSXgwUl1rbWPg0O29prkQdfdKQmZcaO5oj7+f3kSPsIOE9+Qn43VOxOWWybkCzSvEbzLgmuov5C8EWYeJgh13qDcNSwNdt4PgAqIq+tikKNUo9qeM9/q20an+i20fatPAcvrRes+xxnIBXmlPDCj02THjX4EulV2KE+nNxFnCrNvFKYp2bEAegJ2neqfeefDDDhn+t7OK9/73v3O3qnEwSyBlt+pEyHfLjv3Cm7Ik7JA5NUQ/nsS3JdC8OYy2i1DWSvi1qsP3ixAVCR7qBVdoOF2Lv5y2GWrJ0EvVcGqaPBnUezMGMdozNjreschNJvRlp3D72dGGQgs00GHyHbIQ5wicC5p+PiZ2z1EUBN7DiDy9ShQPKEDJtISiSrSaPkDPKpW7SxmSfDaLOIxEy4daAupV0gj7yTtrkpEvJjRECpa0kuKFP3/eFVVp/nIjWDzFASfDvYiry90dDrvLxO3tosuvMVfhXcOy/zbyeCkObaFgc3OkO4z2r4X4Vwt18BoRAammiEfgCbnhywl/CmLrSwV1qSjUgALh/XUPkqXCkqerNjYTlZw5NdRUKmheUXHYGwo4Z+xPfDtiHk1N5vRgNL9/qXsgt813spju9kDMGQGiXlrOgIyhArHR5p2B4S3FjRQ/lEoP5+5wN+9tBKYrR79sZXNS8CwR0BPrOoY9GQCYFdxrBtyH6KOWg29FVXNodt2Yvot7ktofcen1zwQJOAr0KTyqF9/TIltO+hS7swSzZMjV368SEPYjrtXfnXNWYltOS2zJAWYeqr0XLrL+iHbbOQLC7Rk0mnizmUt9wdefz4MtfXZNcdKR4LPsOqYyIz5ux90XiCbvcNZJaRa2/dzecv/koLQPbKzFPGxKiUOsHAa5SEGgbWFZE4Y9CBFS4nCuEOgUnVz9XtFAEP4dazc2cxjYLVzaG5msOiOY1O5ZygYMeVZfdKaITg7gMPbkL3Lpzo7QBMXcHmT5YAUeNaSbHxvgg45Jn8r7W72EQP9tF7SPKiPvxo91xkB7MA3JOcZXC1qymTUWqjO038wSShK48kE+qgu7V9rjP5fOCDW3+3338eifxqS7Zq6FSO053c5W2c8wFR4iw==",
"encryptedKey": "OzSHFXzrXFC5wDvM5NPRkriY/NaC/USvFUPE+f4NZ30tiD2qb8sJM2XT2K7uNIZ05uDLfsJ6/BbEoYC1SOPXcFJMYqRiYFiI9RWrNgR4EtPWZ84RgrmxGcZZjzSqHzjuls8g++cuqJGRV+ePbTRH+z2OuZZu0vMKZiemaZpHL46Ewi9HUnbRDXvOlKFFHmQm5tayZ7m7Mv5iu4T5R3DPEAHlZnGqtP98ToLxUJUS2Eku/iLHXRmhmZXn55Qt5GYiyss8P5/miPqJCu1QG0CStn5Nsl4KvU+I4QYAOcMFWWUAGofOwPWtt8vPh8Bx+7t7BbayKpA4ZUEWWAjC+zASxg==",
"iv": "SHM0UHU5WXoyTG03TnExWA=="
}
```
```javascript theme={null}
const crypto = require('crypto');
/**
* Decrypts the symmetric key using the provided private key.
* @param {string} encryptedKey - Base64 encoded encrypted symmetric key
* @param {string} privateKey - RSA private key
* @returns {Buffer} Decrypted symmetric key
*/
function decryptSymmetricKey(encryptedKey, privateKey) {
try {
const encryptedSymmetricKey = Buffer.from(encryptedKey, 'base64');
const decryptedSymmetricKey = crypto.privateDecrypt(
{
key: `-----BEGIN RSA PRIVATE KEY-----\n${privateKey}\n-----END RSA PRIVATE KEY-----`,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
},
encryptedSymmetricKey
);
return decryptedSymmetricKey;
} catch (error) {
console.error('Error decrypting symmetric key:', error);
throw new Error('Failed to decrypt symmetric key');
}
}
/**
* Decrypts the webhook data using the provided symmetric key and IV.
* @param {string} encryptedWebhookData - Base64 encoded encrypted webhook data
* @param {Buffer} symmetricKey - Decrypted symmetric key
* @param {string} payloadIv - Base64 encoded initialization vector
* @returns {Object} Decrypted webhook data as a JSON object
*/
function decryptWebhookData(encryptedWebhookData, symmetricKey, payloadIv) {
try {
const iv = Buffer.from(payloadIv, 'base64');
const decipher = crypto.createDecipheriv('aes-256-cbc', symmetricKey, iv);
let decryptedData = decipher.update(encryptedWebhookData, 'base64', 'utf8');
decryptedData += decipher.final('utf8');
return JSON.parse(decryptedData);
} catch (error) {
console.error('Error decrypting webhook data:', error);
throw new Error('Failed to decrypt webhook data');
}
}
// Example usage:
// const decryptedKey = decryptSymmetricKey(encryptedKey, privateKey);
// const decryptedData = decryptWebhookData(encryptedWebhookData, decryptedKey, payloadIv);
```
### Improvements
* \[**Comments**]: Improved time display in comment dialog by removing "just" from timeago text to make it more concise.
### New Features
* \[**Comments**]: Added "Seen" Feature in the comment dialog. It shows which users have seen the comments. It's automatically enabled in the default component.
* If you had previously used a wireframe for the comment dialog, you will need to add the [seen component wireframe](/ui-customization/features/async/comments/comment-dialog/subcomponents/body/subcomponents/threadcard).
* \[**Comments**]: Added a dynamic attribute to identify if the current user is the author of a comment inside the comment thread:
* Use the `velt-current-user-author` attribute to conditionally hide options for non-authors:
```css theme={null}
[velt-current-user-author='false'] app-comment-dialog-thread-card-options {
display: none;
}
```
### New Features
* \[**Comments**]: Added config to restrict resolve action to admin users only:
**Using props:**
```jsx theme={null}
```
**Using API:**
```javascript theme={null}
const commentElement = client.getCommentElement();
// To enable resolve status access admin only
commentElement.enableResolveStatusAccessAdminOnly();
// To disable resolve status access admin only
commentElement.disableResolveStatusAccessAdminOnly();
```
**Using props:**
```html theme={null}
```
**Using API:**
```javascript theme={null}
const commentElement = Velt.getCommentElement();
// To enable resolve status access admin only
commentElement.enableResolveStatusAccessAdminOnly();
// To disable resolve status access admin only
commentElement.disableResolveStatusAccessAdminOnly();
```
### Improvements
* \[**Comments**]: Added ability to @mention emails not present in the contact list.
* \[**Comments**]: Implemented focus on composer when clicked anywhere within the composer.
### Improvements
* \[**Comments**]: Added "This page" label to the sidebar filters.
* \[**Comments**]: Added React wireframe component for Navigation button:
```jsx theme={null}
```
```html theme={null}
```
### New Features
* \[**Comments**]: Added "focused thread mode" in the comments sidebar:
* In this mode, when you click on a comment in the sidebar, it will open the thread in expanded view within the sidebar itself.
* Other threads and actions like filters, search etc are hidden behind a back button.
* Turning this on also adds a navigation button in the comment dialog. Clicking it will navigate to the comment and also trigger a callback.
* If you had previously used a wireframe for the comment dialog, you will need to add the [navigation button wireframe](/ui-customization/features/async/comments/comment-dialog/subcomponents/navigation-button) and the [focused thread wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/focused-thread).
```jsx theme={null}
```
**Handling the navigation button click:**
```jsx theme={null}
// event handler for when a comment is clicked on
const onCommentNavigationButtonClick = (event) => {
console.log('onCommentNavigationButtonClick', event);
//handle custom navigation by getting location if you have used Locations
const { pageId } = event.location;
//handle custom navigation by getting context if you have used addContext()
const { pageId } = event.context;
yourNavigateToPageMethod(pageId);
};
```
```html theme={null}
```
**Handling the navigation button click:**
```javascript theme={null}
const commentSidebarElement = document.querySelector('velt-comments-sidebar');
commentSidebarElement.addEventListener('onCommentNavigationButtonClick', (s) => {
console.log('onCommentNavigationButtonClick', s.detail);
});
```
* \[**Comments**]: Added standalone thread wireframe component:
```jsx theme={null}
{/* Velt Comment Dialog Wireframe */}
```
```html theme={null}
```
* \[**Live Selection**]: Add ability to get live selection data for the document:
**Using Hook:**
```jsx theme={null}
const liveSelectionData = useLiveSelectionDataHandler();
useEffect(() => {
console.log('liveSelectionData', liveSelectionData);
}, [liveSelectionData]);
```
**Using API:**
```javascript theme={null}
const selectionElement = client.getSelectionElement();
selectionElement.getLiveSelectionData().subscribe((liveSelectionData: LiveSelectionData) => {
console.log("liveSelectionData: ", liveSelectionData);
});
```
**Using API:**
```javascript theme={null}
const selectionElement = Velt.getSelectionElement();
selectionElement.getLiveSelectionData().subscribe((liveSelectionData: LiveSelectionData) => {
console.log("liveSelectionData: ", liveSelectionData);
});
```
* \[**Comments**]: Added standalone `Velt Comment Text` component:
* When you put any text inside this component and provide an annotationId, it will automatically highlight the text and attach the comment to it.
```jsx theme={null}
{/* your content here */}
{/* your content here */}
```
```html theme={null}
```
### Improvements
* \[**Notifications**]: Changed the default maximum days for which Notifications should be displayed from 30 days to 15 days.
* \[**Notifications**]: Added complete document and organization metadata objects to the Notification metadata object.
### New Features
* \[**Live Selection**]: Added new configurations to customize the UI and behavior of the live selection feature:
* Enable/disable user indicator
* Set user indicator position
* Set user indicator type
* Enable/disable default elements tracking
* Enable/disable default styling
* Earlier the live selection was triggered on click, now it is triggered on keyboard actions as well.
* [Learn more](/realtime-collaboration/live-selection/customize-behavior).
### New Features
* \[**Comments**]: Added ability to apply custom filtering, sorting and grouping in comments sidebar.
* Here is the overview on how it works:
* Enable custom actions in the comments sidebar.
* Add action buttons to the sidebar wireframe.
* Implement callback and event handlers to handle custom filtering, sorting, and grouping logic.
* Set custom filtered data in the comments sidebar.
* [Learn more](/async-collaboration/comments-sidebar/customize-behavior#2-custom-filtering-sorting-and-grouping).
### Bug Fixes
* \[**Mentions**]: Resolved an issue where `atHereDescription` was not rendering for non-organization users.
### New Features
* \[**Notifications**]: Added API to mark a single notification as read by notificationId.
```javascript theme={null}
const notificationElement = client.getNotificationElement();
notificationElement.markNotificationAsReadById("notificationId");
```
```javascript theme={null}
const notificationElement = Velt.getNotificationElement();
notificationElement.markNotificationAsReadById("notificationId");
```
* \[**Debugger**]: Added call to `setDocument` method to the debugger.
### Improvements
* \[**Mentions**]: Added `atHereDescription` to the `description` field of the @mention data object.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where the "For You" tab was not displaying the updated document name on initial load.
### New Features
* \[**Mentions**]: Added styling for @mention in the composer when adding or editing a comment.
### Improvements
* \[**React Hooks**]: Updated client and comment related hooks to support dynamic input values.
### Bug Fixes
* \[**Mentions**]: Fixed an issue in the `updateContactList` API where the passed contact list data was being mutated directly.
* \[**Mentions**]: Resolved an issue where @mentions with special characters were not working correctly.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where the `unreadCommentsMap` count was not updating correctly when switching between documents with no unread comments.
### New Features
* \[**Comments**]: Added shadowDOM, dark mode, and variant support in standalone Comment Thread component.
```jsx theme={null}
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where scroll was causing the "Add reply" button to hide.
* \[**Comments**]: Fixed an issue where the assign to dialog was not closing after assigning a user using the keyboard.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where the `document` tab was not visible when user email was not set.
### New Features
* \[**Comments**]: Added support for overlay text commenting in Tiptap editor.
* It works with all frontend frameworks that are supported by Tiptap.
* You can find the extension [here](https://www.npmjs.com/package/@veltdev/tiptap-velt-comments).
```bash theme={null}
npm i @veltdev/tiptap-velt-comments
```
```javascript theme={null}
import { TiptapVeltComments } from '@veltdev/tiptap-velt-comments';
const editor = new Editor({
extensions: [
TiptapVeltComments,
// ... other extensions
],
});
```
* Call this method to add a comment to selected text in the Tiptap editor. You can use this when the user clicks on the comment button in context menu or presses a keyboard shortcut.
* Args:
* `editor`: instance of the Tiptap editor.
* `tiptapVeltCommentConfig`: optional object to set the Comment Annotation's custom metadata.
* Example:
```javascript theme={null}
import { addTiptapVeltComment } from '@veltdev/tiptap-velt-comments';
addTiptapVeltComment(editor, tiptapVeltCommentConfig);
```
### Improvements
* \[**Console Debugger**]: Added logs for the `updateContactList` method to improve debugging.
### Improvements
* \[**Console Debugger**]: Added logs for `addContext` and `updateContext` methods. The context object is now included in the log event properties for better debugging and tracking.
* \[**Comments**]: Now comments will be marked as read if opened via the `selectCommentByAnnotationId()` API.
### Bug Fixes
* \[**Comment Display**]: Improved comment rendering performance for Comments Sidebar.
### New Features
* \[**Comments Sidebar**]: Added a reset filter button to easily clear all applied filters.
* This new button allows users to quickly reset all filters in the comments sidebar when no comments are available for the applied filters.
* Here is the wireframe for the reset filter button:
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Notifications**]: Improved loading state for API calls, now returns null until data is fully loaded.
### New Features
* \[**Notifications**]: Added API to get unread notifications count.
* **Sample response:**
```javascript theme={null}
{
forYou: 4, // # of unread notifications in the "For You" tab
all: 5 // # of unread notifications in the "All" or "Document" tab
}
```
Using Hooks:
```jsx theme={null}
const unreadCount = useUnreadNotificationsCount();
useEffect(() => {
console.log('Unread Count', unreadCount);
}, [unreadCount]);
```
Using API:
```javascript theme={null}
const notificationElement = client.getNotificationElement();
notificationElement.getUnreadNotificationsCount().subscribe((data) => {
console.log('Unread notifications count:', data);
});
```
```javascript theme={null}
const notificationElement = Velt.getNotificationElement();
notificationElement.getUnreadNotificationsCount().subscribe((data) => {
console.log('Unread notifications count:', data);
});
```
### Improvements
* \[**Comments**]: Improved search functionality in @mentions to support spaces in search queries.
### New Features
* \[**Comments**]: Added ability to control whether comments inside the annotation should be collapsed.
Using Props:
```jsx theme={null}
```
Using API:
```javascript theme={null}
const commentElement = client.getCommentElement();
commentElement.enableCollapsedComments();
commentElement.disableCollapsedComments();
```
Using Props:
```html theme={null}
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableCollapsedComments();
commentElement.disableCollapsedComments();
```
* \[**Comments**]: Added ability to get comment annotation by ID.
Using Hooks:
```jsx theme={null}
const annotation = useCommentAnnotationById({
annotationId: '-O6W3jD0Lz3rxuDuqQFx', // AnnotationID
documentId: 'document-id' // DocumentId (Optional)
});
React.useEffect(() => {
console.log('annotation', annotation);
}, [annotation]);
```
Using API:
```javascript theme={null}
const commentElement = client.getCommentElement();
commentElement.getCommentAnnotationById({
annotationId: '-O6W3jD0Lz3rxuDuqQFx', // AnnotationID
documentId: 'document-id' // DocumentId (Optional)
}).subscribe((annotation) => {
console.log('annotation', annotation);
});
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.getCommentAnnotationById({
annotationId: '-O6W3jD0Lz3rxuDuqQFx', // AnnotationID
documentId: 'document-id' // DocumentId (Optional)
}).subscribe((annotation) => {
console.log('annotation', annotation);
});
```
* \[**Notifications**]: Added API to mark all notifications as read.
* Mark all notifications as read, either globally or for a specific tab.
* Using 'all' or 'document' as the `tabId` marks all notifications as read across all tabs (equivalent to calling `setAllNotificationsAsRead()` without arguments).
* Using 'for-you' as the `tabId` only marks notifications in the 'for-you' tab as read.
```javascript theme={null}
const notificationElement = client.getNotificationElement();
// Mark all notifications as read
notificationElement.setAllNotificationsAsRead();
// Mark all notifications as read for a specific tab
notificationElement.setAllNotificationsAsRead({ tabId: 'for-you' });
notificationElement.setAllNotificationsAsRead({ tabId: 'all' });
notificationElement.setAllNotificationsAsRead({ tabId: 'document' });
```
```javascript theme={null}
const notificationElement = Velt.getNotificationElement();
// Mark all notifications as read
notificationElement.setAllNotificationsAsRead();
// Mark all notifications as read for a specific tab
notificationElement.setAllNotificationsAsRead({ tabId: 'for-you' });
notificationElement.setAllNotificationsAsRead({ tabId: 'all' });
notificationElement.setAllNotificationsAsRead({ tabId: 'document' });
```
### Bug Fixes
* \[**UI Customization**]: Fixed an issue in `velt if` where string comparisions were not working as expected.
### New Features
* \[**Comments**]: Added a reset filter button wireframe in multithread comment dialog.
* \[**Notifications**]: Added a notifications panel loading state skeleton with wireframes.
```javascript theme={null}
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where draft comments were not being saved for multithread comments in some cases.
* \[**Comments**]: Fixed an issue where in inline mode, when editing a comment, the annotation was not being selected.
### New Features
* \[**Comments**]: Added a prop to enable confirmation before deleting a reply.
Using Props:
```jsx theme={null}
```
Using API:
```javascript theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDeleteReplyConfirmation();
commentElement.disableDeleteReplyConfirmation();
```
Using Props:
```html theme={null}
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDeleteReplyConfirmation();
commentElement.disableDeleteReplyConfirmation();
```
* \[**Comments**]: Added a callback method for when a comment link is copied. You can use this to track when a comment link is copied or show a confirmation toast.
Using Hooks:
```jsx theme={null}
const commentLink = useCommentCopyLinkHandler();
useEffect(() => {
console.log('commentLink', commentLink);
}, [commentLink]);
```
Using API:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.onCopyLink().subscribe((commentLink) => {
console.log(commentLink);
});
```
Using API:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.onCopyLink().subscribe((commentLink) => {
console.log(commentLink);
});
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.onCopyLink().subscribe((commentLink) => {
console.log(commentLink);
});
```
* \[**Comments**]: Added a minimal Actions Dropdown in Multithread comment dialog. It contains actions like 'Mark all as read', 'Mark all as resolved'.
### Improvements
* \[**UI Customization**]: Renamed the `velt data` prop to `field` (old: `path`) to improve readability. This is backward compatible.
```jsx theme={null}
```
```jsx theme={null}
```
### Improvements
* \[**Comments**]: Renamed `multiThreadMode` prop to `multiThread` for improved clarity and consistency.
* \[**Comments**]: Removed the "0 comments" from the multithread container header, when the first thread is created.
* \[**Comments**]: The comment pin bubble count in multithread mode would sync with the number of unresolved comments in the multithread container.
* \[**Comments**]: Added an unread indicator on the comment pin bubble in multithread mode to help users quickly identify new or unread comments.
* \[**UI Customization**]: Added improvements to the `velt if` conditional rendering logic.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the menu trigger button would hide when the mouse moved on the menu.
* \[**Notifications**]: Resolved an issue where `documentName` wasn't displayed on the notification item in the notification panel.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the composer would throw a console error on Firefox. This didn't have any impact on functionality or UX.
* \[**Comments**]: Fixed reply button toggle issue.
### Improvements
* \[**Comments**]: Improvements to the comments UX in multithread mode.
### Bug Fixes
* \[**Comments**]: Fixed an issue where sign in button was showing up for logged in users on mobile. This only impacted customers where signIn button was enabled.
### New Features
* \[**Webhooks**]: Added configuration option for base64 encoding of webhook payloads (disabled by default).
* Addresses issues with payloads containing HTML tags that may fail due to strict endpoint policies.
* Ensures payload validity and processability by your endpoint.
* Example of decoding a base64 encoded payload:
```js theme={null}
const encodedData="eyJ0ZXN0IjoxLCJ0ZXN0MSI6IjxkaXY+PC9kaXY+In0="
const decodedData = Buffer.from(encodedData, 'base64').toString('utf-8');
console.log(JSON.parse(decodedData));
```
### Improvements
* \[**Comments**]: Significant enhancements to the comments UX in multithread mode:
* Implemented smooth auto-scroll animation to new threads for improved visibility
* Added a new comment button to the header for easier thread creation
* Refined composer show/hide logic for a more intuitive user experience
* Shortened the displayed timestamp format
* \[**Comments**]: Improved the UI for [custom autocomplete dropdown chip](async-collaboration/comments/customize-behavior/custom-lists#2-add-custom-lists-on-comment).
* \[**Notifications**]: Notifications feature and API no longer require documentId to be set for initialization.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the custom autocomplete chip would fail to render if the same chip is added multiple times.
### Bug Fixes
* \[**Comments**]: Fixed a bug where clicking on one's own reaction on the [Velt Comment Player Timeline](/async-collaboration/comments/setup/video-player-setup/custom-video-player-setup) didn't toggle it correctly.
### Improvements
* \[**Comments**]: Added `createdAt` field for [CommentAnnotation](/api-reference/models/CommentAnnotation) model. This can now be used to sort annotations in ascending order of creation.
* \[**Comments**]: Added `isEdited` and `editedAt` fields in [Comment](/api-reference/models/Comment) model. This can now used to indicate if and when a comment is edited.
* \[**Comments**]: In multithread mode, the count on the header is now synced with the total threads visible after all the filters are applied.
* \[**Comments**]: Inline and multi-thread comments are now sorted in ascending order by the `createdAt` field, maintaining backward compatibility.
* \[**Comments**]: Improved the behavior where the mutlthread container was auto-closing when one of the threads was deleted or resolved.
### Bug Fixes
* \[**Comments**]: Fixed a bug where deleting a recording in a single-comment thread incorrectly triggered the Delete Thread dialog.
* \[**Comments**]: Fixed a bug where resolved comment threads weren't disappearing in [inline comment mode](/async-collaboration/comments/setup/inline-comments).
### New Features
* \[**Notifications**]: Added a configurable option to show read notifications in the "For You" tab. By default, read notifications are removed from the "For You" tab.
Using Props:
```jsx theme={null}
```
Using APIs:
```jsx theme={null}
const notificationElement = useNotificationUtils();
// Enable to keep read notifications in the for you tab
notificationElement.enableReadNotificationsOnForYouTab();
// Disable to hide read notifications in the for you tab
notificationElement.disableReadNotificationsOnForYouTab();
```
Using Props:
```jsx theme={null}
```
Using APIs:
```jsx theme={null}
const notificationElement = client.getNotificationElement();
// Enable to keep read notifications in the for you tab
notificationElement.enableReadNotificationsOnForYouTab();
// Disable to hide read notifications in the for you tab
notificationElement.disableReadNotificationsOnForYouTab();
```
Using Props:
```html theme={null}
```
Using API:
```html theme={null}
```
For more details on customizing notifications behavior, refer to the [Notifications Behavior](/async-collaboration/notifications/customize-behavior) section.
### Improvements
* \[**REST API**]: Added advanced querying and pagination on REST APIs for improved performance and functionality.
* \[**Comments**]: Made the rendering of comment dialog action buttons configurable via CSS on hover or selection.
### Bug Fixes
* \[**Comments**]: Resolved a rare bug where the triangle pin component would cause the comment dialog to flicker on hover in popover comment mode.
* \[**Autocomplete dropdowns**]: Fixed a bug where the autocomplete dropdown component would not apply the `--velt-default-font-family`.
* \[**Notifications**]: Fixed a bug occurring when marking all notifications as read in large notification data sets.
### New Features
* \[**Comments**]: Added a standalone Comment Composer component. Now you can use this in combination with [Velt Comment Threads](/async-collaboration/comments/standalone-components/comment-thread/overview) component to embed commenting experiences in custom ways.
```jsx theme={null}
```
```html theme={null}
```
* \[**Inline Comments**]: Added single thread option to inline comments. By default, it will be in multithread mode.
Default value: `true`
```jsx theme={null}
```
```html theme={null}
```
### New Features
* \[**Comments**]: Added a new prop to control the use of ShadowDOM for [Persistent Comment Mode](/async-collaboration/comments/customize-behavior/modes#persistent-comment-mode) banner.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Action buttons are now consistently displayed across all states (default, hover, selection) of the comment dialog and can be customized via CSS alone.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where the `tabConfig` React props was not working as expected.
### Improvements
* \[**Notifications**]: Added an empty state for the document and all tabs in the notification panel.
* \[**Notifications**]: Added type definition for the `tabConfig` prop to improve TypeScript support.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where React props for customizing the notification panel were not working as expected.
### New Features
* \[**Comments**]: Added a new `atHereDescription` prop to customize the description that appears for the @here mention.
Using Props:
```jsx theme={null}
```
Using API Method:
```jsx theme={null}
const contactElement = useContactUtils();
useEffect(() => {
contactElement.setAtHereDescription('Notify all users in this document');
}, [contactElement]);
```
Using Props:
```jsx theme={null}
```
Using API Method:
```jsx theme={null}
const contactElement = client.getContactElement();
contactElement.setAtHereDescription('Notify all users in this document');
```
Using Props:
```html theme={null}
```
Using API Method:
```javascript theme={null}
const contactElement = Velt.getContactElement();
contactElement.setAtHereDescription('Notify all users in this document');
```
* \[**Comments**]: Added the `getSelectedComments()` API to get the currently selected comment annotations.
* This returns an array of [`CommentAnnotation`](/api-reference/models/CommentAnnotation) objects.
```jsx theme={null}
const commentElement = client.getCommentElement();
const subscription = commentElement.getSelectedComments().subscribe((selectedComments) => {
console.log('Selected comments:', selectedComments);
});
```
Unsubscribe from the subscription when you're done:
```jsx theme={null}
subscription?.unsubscribe()
```
```js theme={null}
const commentElement = Velt.getCommentElement();
const subscription = commentElement.getSelectedComments().subscribe((selectedComments) => {
console.log('Selected comments:', selectedComments);
});
```
Unsubscribe from the subscription when you're done:
```js theme={null}
subscription?.unsubscribe()
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where the [custom list](/async-collaboration/comments/customize-behavior/custom-lists#1-add-custom-lists-on-comment-annotation) chip tooltip icon was not displayed.
* \[**Comments**]: Fixed an issue where the reaction tooltip in the [comment player timeline component](/async-collaboration/comments/setup/video-player-setup/custom-video-player-setup) was getting distorted on hover.
* \[**Comments**]: Fixed an issue where the reaction bubble on the [comment player timeline component](/async-collaboration/comments/setup/video-player-setup/custom-video-player-setup) had a transparent background.
### New Features
* \[**Comments**]: Added a Minimal Filter Dropdown Component for the Multithread Comment Dialog. This provides basic filtering and sorting options, including:
* Sorting: by date, by unread status
* Filtering: unread comments, read comments, resolved comments
### Improvements
* \[**Comments**]: Updated multithread behavior to ensure only one composer is open at a time.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the Floating Mode Comment Sidebar would not close when clicked outside.
* \[**Comments**]: Resolved a problem where sidebar dropdowns were not closing when clicked on the trigger button again.
* \[**Comments**]: Fixed an issue where the Add Reply button wasn't working in Inline Comment Section.
### Bug Fixes
* \[**Comments**]: Fixed a bug related to updating comment annotations.
### New Features
* \[**Comments**]: Added Draft State:
* Empty comments are no longer saved.
* Partial comments are saved as drafts, visible only to the author.
* Draft creation:
* Comment is saved as draft when the user adds text, recording, or attachment and closes the comment dialog without submitting.
* On first attempt to close: Dialog shakes to indicate draft status.
* On second attempt: the comment is saved as draft and the dialog closes.
* Added a visual indicator for draft comments in the dialog.
* If you had previously used a wireframe for the comment dialog, you will need to add the [draft wireframe](/ui-customization/features/async/comments/comment-dialog/subcomponents/body/subcomponents/threadcard).
### Improvements
* \[**UI Customization**]: Refactored `velt if` and `velt data` logic for improved performance.
### New Features
* \[**Comments**]: Added Draft State:
* Empty comments are no longer saved.
* Partial comments are saved as drafts, visible only to the author.
* Draft creation:
* Comment is saved as draft when the user adds text, recording, or attachment and closes the comment dialog without submitting.
* On first attempt to close: Dialog shakes to indicate draft status.
* On second attempt: the comment is saved as draft and the dialog closes.
* Added a visual indicator for draft comments in the dialog.
* If you had previously used a wireframe for the comment dialog, you will need to add the [draft wireframe](/ui-customization/features/async/comments/comment-dialog/subcomponents/body/subcomponents/threadcard).
### New Features
* \[**UI Customization**]: Added **Conditional Templates**! These let you conditionally show or hide parts of the Velt Component Wireframes.
* You can add conditions based on the same data models available in [Template Variables](/ui-customization/template-variables).
```jsx theme={null}
{/* Content to render if condition is true */}
```
```html theme={null}
```
* \[**UI Customization**]: You can now customize confirmation dialogs (eg: Delete thread, Delete recorder etc) for each feature by defining variants.
* Supported variants: `recorder`, `comment`, `arrow`, `area`.
```jsx theme={null}
Custom Title
Custom Message
Custom Reject Button
Custom Approve Button
```
```html theme={null}
Custom Title
Custom Message
Custom Reject Button
Custom Approve Button
```
* \[**UI Customization**]: Added two new global [Template Variables](/ui-customization/template-variables):
* `unreadCommentAnnotationCount`: Number of unread comment annotations on the current document.
* `unreadCommentCount`: Total number of unread comments on the current document.
* \[**Comments**]: Added `updateContext` method for updating custom metadata (`context`) on comment annotations. [Learn more](/async-collaboration/comments/customize-behavior#metadata#2-update-custom-metadata-on-comment-annotation). This method is available in two scenarios:
1. **In the `onCommentUpdate` event callback:**
Use this to update the context when a comment is modified.
2. **Via the `updateContext` API method:**
Utilize this method to update the context of a comment annotation at any time. For example, you might use this when the name of the dashboard containing the comment annotation changes.
### Improvements
* \[**Comments**]: Updated the icon for the Unresolve button.
* \[**Comments**]: Whenever the comment sidebar is opened using the button or the api, any open comment dialog will be closed.
* \[**Comments**]: Made position of the reaction tool consistent across different states and content types in the comment dialog.
### Bug Fixes
* \[**Comments**]: Fixed the flicker issue when new popover comment thread was created. This only happened when the triangle component was disabled.
* \[**Comments**]: Fixed minor rendering issue with the @mention chip when it was added at the end of the content.
* \[**Velt Components**]: Reduced the default z-index for all Velt Components to prevent them from going over the host app's header or any other important UI elements.
* \[**Comments**]: Fixed menu overlay positioning to stay with its trigger during page scrolling.
### Bug Fixes
* \[**Comments**]: Reverted comment sidebar button wireframe changes.
### New Features
* \[**Inline Reactions**]: Added `customReactions` prop for `VeltInlineReactionsSection` component in React, allowing custom emoji definitions:
```jsx theme={null}
const customReactions = {
"EMOJI_ID_1": {
"emoji": "🔥" // You could also set emoji using a url or raw svg definition
},
"EMOJI_ID_2": {
"emoji": "🙌" // You could also set emoji using a url or raw svg definition
},
"EMOJI_ID_3": {
"emoji": "💪" // You could also set emoji using a url or raw svg definition
}
}
```
* \[**Comments**]: Added `multiThreadMode` prop for `VeltComments` component in React:
```jsx theme={null}
```
### Improvements
* \[**Comments**]: Updated the unresolved button icon for better understanding.
* \[**Comments**]: Added types for `enableMultiThreadMode` and `disableMultiThreadMode` API methods.
### New Features
* \[**Comments**]: Added multi-thread support for Comments:
Using Props:
```html theme={null}
```
Using API Method:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
// To enable multi-thread mode
commentElement.enableMultiThreadMode();
// To disable multi-thread mode
commentElement.disableMultiThreadMode();
```
* \[**Inline Reactions**]: Added ability to add list of custom reactions:
Using API Method:
```javascript theme={null}
const reactionElement = client.getReactionElement();
const customReactions = {
"EMOJI_ID_1": {
"emoji": "🔥" // You could also set emoji using a url or raw svg definition
},
"EMOJI_ID_2": {
"emoji": "🙌" // You could also set emoji using a url or raw svg definition
},
"EMOJI_ID_3": {
"emoji": "💪" // You could also set emoji using a url or raw svg definition
}
};
reactionElement.setCustomReactions(customReactions);
```
* \[**UI Customization**]: Added wireframe for MultiThreaded Comment Dialog.
* \[**UI Customization**]: Added wireframe for Comment Sidebar Button with the new name.
### Improvements
* \[**Comments**]: Made element binding consistent by using common `targetElementId` attribute in comment feature components:
```jsx theme={null}
```
```html theme={null}
```
### Bug Fixes
* \[**Notifications**]: Fixed an issue where unread icon was not showing up when a comment was added by the user themselves.
* \[**Notifications**]: Resolved a UI issue where the "All Read" container was showing up while the data was still loading.
* \[**Notifications**]: Fixed the load more button on all notifications tabs.
* \[**Notifications**]: Fixed an issue where using the "assign to" options from the thread options menu wasn't generating a notification.
* \[**Recorder**]: Fixed an issue where recording was saved when minimizing the preview panel.
### Improvements
* \[**Comments**]: Pass the `id` attribute on either the `` tag or its parent element. This simplifies the implementation of comments on custom video players.
```jsx theme={null}
```
```html theme={null}
```
### New Features
* \[**Comments**]: Added an "Unresolve Comment" button component in the comment dialog. This button is used to unresolve a comment that was previously marked as resolved.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Improved comment text handling by trimming whitespace from `commentText` and `commentHtml` in the comment dialog.
### Bug Fixes
* \[**Comments**]: Fixed an issue with autocomplete chip shadowDOM not being updated.
* \[**Notifications**]: Resolved an issue where documents with empty notifications were still being rendered.
* \[**Users**]: Fixed a bug related to special characters in user names.
* \[**Comments**]: Fixed an issue where the checkbox in the location filter dropdown in sidebar wasn't updating on selection.
* \[**Comments**]: Fixed an issue where the status filter menu in the sidebar wasn't closing properly.
* \[**Recorder**]: Resolved a problem where the device list wasn't updating correctly in the recording settings menu in certain scenarios.
### New Features
* \[**Comments**]: Added a minimal location filter in the sidebar wireframe for basic location based filtering. By default it's not enabled. You need to explicitly add the wireframe to the sidebar.
* \[**Sidebar**]: Added `Floating Mode` for Comments Sidebar. This allows the sidebar panel to appear as an overlay on top of the sidebar button when clicked.
```jsx theme={null}
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed a bug where clicking the "replies" button in the sidebar comment dialog incorrectly opened the comment dialog on the DOM. This issue only affected implementations using the sidebar comment dialog wireframe.
### New Features
* \[**Comments**]: Added persistent comment mode banner wireframe.
* \[**API**]: Added `getDocumentMetadata` method to get the current document's metadata. It returns a subscription with [`DocumentMetadata`](/api-reference/sdk/models/data-models#documentmetadata) object.
```javascript theme={null}
client.getDocumentMetadata().subscribe((documentMetadata) => {
console.log("Current document metadata: ", documentMetadata);
});
```
```js theme={null}
Velt.getDocumentMetadata().subscribe((documentMetadata) => {
console.log("Current document metadata: ", documentMetadata);
});
```
### Improvements
* \[**Comments**]: The persistent comment banner now inherits the shadow DOM property from `Velt Comments`.
### New Features
* \[**Comments**]: Added a minimal filtering and sorting dropdown in the sidebar wireframe. By default it's not enabled. You need to explicitly add the wireframe to the sidebar. It includes options like:
* Filter by `All`
* Filter by `Unread`
* Filter by `Read`
* Filter by `Resolved`
* Sort by `Unread`
* Sort by `Last Updated Timestamp`
### Bug Fixes
* \[**Comments**]: Fixed an issue where the dialog was overflowing out of the screen in some cases.
### New Features
* \[**Reactions**]: Added [inline reaction section](/async-collaboration/reactions/setup) feature component.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Truncated video timeline comment card to 100 characters for improved readability.
* \[**Users**]: Limited user names to a maximum of 20 characters and disallowed URLs in names for security purposes.
### Bug Fixes
* \[**Comments**]: Resolved an issue with the unread indicator in the comment dialog.
### New Features
* \[**Comments**]: Added feature to render a comment bubble on the comment pin or triangle instead of the comment dialog. Hovering or clicking the bubble will open the comment dialog.
* `bubbleOnPin`: Whether to show bubble on pin \[default: false]
* `bubbleOnPinHover`: If the above is true, whether to show bubble when the pin is hovered over or clicked \[default: true]
Using Props:
```jsx theme={null}
```
Using API:
```javascript theme={null}
const commentElement = client.getCommentElement();
// To enable showing bubble on pin
commentElement.enableBubbleOnPin();
// To disable showing bubble on pin
commentElement.disableBubbleOnPin();
// To enable option to show bubble on pin hover vs only on click
commentElement.enableBubbleOnPinHover();
// To disable option to show bubble on pin hover
commentElement.disableBubbleOnPinHover();
```
Using Props:
```html theme={null}
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
// To enable showing bubble on pin
commentElement.enableBubbleOnPin();
// To disable showing bubble on pin
commentElement.disableBubbleOnPin();
// To enable option to show bubble on pin hover vs only on click
commentElement.enableBubbleOnPinHover();
// To disable option to show bubble on pin hover
commentElement.disableBubbleOnPinHover();
```
### Improvements
* \[**Comments**]: Increased the max allowed size of attachments in composer from 2MB to 15MB.
* \[**Comments**]: Decreased the size of triangle in comment pin from 15px to 10px.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the comment dialog composer user avatar wireframe was not working.
* \[**Comments**]: Resolved minor signal-related issues.
* \[**Comments**]: Fixed Comment Dialog Attachments and Recording UI for inline comment mode, addressing aspect ratio issues.
### Improvements
* \[**Performance**]: Rewrote change detection and state management for the entire SDK to make it more performant. This is a major update and will benefit all developers.
# April 16 2024
Source: https://docs.velt.dev/release-notes/archive/april-16-2024
## Versions
* Latest SDK: [1.0.107](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.119](https://www.npmjs.com/package/@veltdev/types)
## Method to Hide Comments from Users on specific Locations
The [client.excludeLocationIds()](https://docs.velt.dev/async-collaboration/comments/customize-behavior/general-controls) method can be used to hide `Comments` at specific `Locations` from `Users`.
```jsx theme={null}
const locationIds = ['location1', 'location2']; // list of location ids
client.excludeLocationIds(locationIds);
```
## Configuration to Disable Recording Summaries
If you want to [disable the Recording summaries](https://docs.velt.dev/async-collaboration/comments/customize-behavior/multimedia) that appear when you record you audio, voice or screen, you can can now do so.
Example:
```jsx theme={null}
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
// to show recording summary
commentElement.enableRecordingSummary();
// to hide recording summary
commentElement.disableRecordingSummary();
```
## Configuration to Disable Recording countdown
If you want to [disable the countdown](https://docs.velt.dev/async-collaboration/comments/customize-behavior/multimedia) that appears when you begin recording your audio, voice, or screen, you can now do so. When the countdown is disabled, recordings will begin instantly.
Example
```jsx theme={null}
```
API Methods:
```jsx theme={null}
// API method - comment element
const commentElement = client.getCommentElement();
// To enable recording countdown
commentElement.enableRecordingCountdown();
// To disable recording countdown
commentElement.disableRecordingCountdown();
// API method - recorder element
const recorderElement = client.getRecorderElement();
// To enable recording countdown
recorderElement.enableRecordingCountdown();
// To disable recording countdown
recorderElement.disableRecordingCountdown();
```
## Method to Remove the Document ID
You can now unset the `Document Id` to pause the Velt SDK functions.
```jsx theme={null}
client.unsetDocumentId()
```
## More File Type Support in Comment attachments
We've added support for more file types in `Comment` attachments (`csv`, `docx`, `pptx`, `xlsx`, `webp`, `mp4` etc.).
# April 24 2024
Source: https://docs.velt.dev/release-notes/archive/april-24-2024
## Versions
* Latest SDK: [1.0.112](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.127](https://www.npmjs.com/package/@veltdev/types)
## Option to Disable ShadowDOM
We've added an option for you to [disable the ShadowDOM on certain components](https://docs.velt.dev/async-collaboration/comments/customize-behavior/ui-controls).
By default, a ShadowDOM is used with certain components to ensure that your application's CSS does not interfere with the styling of the SDK components.
If you want your application's CSS to affect the styling of the SDK components, you can disable the ShadowDOM.
```jsx theme={null}
```
API methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enablePinShadowDOM();
commentElement.disablePinShadowDOM();
commentElement.enableDialogShadowDOM();
commentElement.disableDialogShadowDOM();
commentElement.enableSidebarShadowDOM();
commentElement.disableSidebarShadowDOM();
```
## Method to Unsubscribe from Subscriptions
We added an `unsubscribe()` method to unsubscribe from any event subscriptions.
Example subscription:
```jsx theme={null}
let subscription = commentElement.getAllCommentAnnotations().subscribe((comments) => {
// Do something with comments
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
## Added webhook notifications for `Huddle` Create and Join Events
Your users will now receive [webhook notifications](https://docs.velt.dev/webhooks/huddle-webhooks) when a `Huddle` is created or joined.
Example notification schema:
```jsx theme={null}
{
"from": {
// UserObject
},
"projectName": "string",
"projectUrl": "string",
"actionType": "string", // created | joined
"notificationSource": "huddle",
"documentId": "string",
"clientDocumentId": "string",
"id": "string",
"timestamp": 1234567890,
"actionUser": {
// UserObject
},
}
```
Click here to see the [Notification](https://docs.velt.dev/api-reference/sdk/models/data-models#notification) schema.
## Added Validations to check for Audio and Video permissions on `Huddles`
Your users will now be prompted to check for Audio and Video permissions on `Huddles`
## Option to disable `Chat` feature on `Huddles`
You can now [disable the Chat feature](https://docs.velt.dev/realtime-collaboration/huddle/customize-behavior) that is enabled by default on `Huddle` component.
## Option to disable `flockMode` feature on `Huddles`
You can now [disable the flockMode feature](https://docs.velt.dev/realtime-collaboration/huddle/customize-behavior) that is enabled by default on `Huddle` component when clicking on another user's Avatar.
## Added support to set a custom order of locations to sort by in the `Comments Sidebar`.
You can now define the [order in which locations appear](https://docs.velt.dev/async-collaboration/comments-sidebar/customize-behavior) in the `Comments Sidebar` filter through the 'order' field in the `filterConfig` object.
```jsx theme={null}
```
## Added configuration for Unread Comments Indicator
You can now configure the [Unread Comments Indicator](https://docs.velt.dev/async-collaboration/comments/customize-behavior/display-metadata) to be either `minimal` or `verbose`.
In `minimal` mode unread comments will be indicated by a small red dot.
In `verbose` mode unread comments will be indicated by a larger badge that says UNREAD
## Added hook for `useUnsetDocumentId`
There is now a hook called [useUnsetDocumentId()](https://docs.velt.dev/api-reference/hooks/hooks#useunsetdocumentid) that calls [client.unsetDocumentId()](https://docs.velt.dev/api-reference/sdk/api/api-methods#client)
```jsx theme={null}
import { useUnsetDocumentId} from '@veltdev/react'
import React from 'react'
export default function YourComponent() {
useUnsetDocumentId();
return (
...
)
}
```
## Added option to use a specific version of the Velt React SDK.
You can now use a [specific version of the Velt React SDK](https://docs.velt.dev/get-started/advanced).
To do so, in your `VeltProvider` component, set the `config` props object to `{ version: '1.0.XYZ' }`.
Example:
```jsx theme={null}
...
```
# Aug 12 2024
Source: https://docs.velt.dev/release-notes/archive/aug-12-2024
# 2.0.30
### New Features
* \[**Comments**]: Added `resolvedByUserId` field on comment resolve. This allows tracking which user resolved a comment thread.
* \[**Comments**]: Added a configuration on sidebar to open the filter panel as a bottom sheet (default) or overlap on the filter button.
* Prop: `filterPanelLayout`. Values: `'menu' | 'bottomSheet'` (default)
* Usage:
```jsx theme={null}
```
```html theme={null}
```
# Aug 13 2024
Source: https://docs.velt.dev/release-notes/archive/aug-13-2024
# 2.0.31
### New Features
* \[**UI Customization**]: Added [Confirm Dialog Wireframe](/ui-customization/features/async/comments/confirm-dialog/overview) and added support for light and dark modes.
* This is a common UI component used for multiple features like deleting comment thread, deleting a recorded content and more.
* It will automatically inherit the light or dark mode property from the parent feature.
# Aug 14 2024
Source: https://docs.velt.dev/release-notes/archive/aug-14-2024
# 2.0.33
### New Features
* \[**UI Customization**]: Added conditional dynamic classes for different states of various interactive components:
* `velt-sidebar-tool-open`: For the sidebar button when the sidebar is open.
* `velt-notification-tool-open`: For the notification tool when the notification panel is open.
* `velt-comments-sidebar-status-open`: For the status dropdown trigger when the dropdown is open.
* `velt-comments-sidebar-filter-button-open`: For the filter button when the filter panel is open.
### Bug Fixes
* \[**Comments**]: Fixed an issue where after adding '@here' the cursor position was incorrectly placed at the start of the text.
# 2.0.32
### New Features
* \[**UI Customization**]: Added conditional classes for different states of various interactive components:
* `velt-comment-pin-open`: For the comment pin when the pin is selected.
* `velt-dropdown-open`: For the dropdown trigger when the dropdown is open.
* `velt-assign-dropdown-open`: For the assign dropdown trigger when the dropdown is open.
* `velt-custom-dropdown-open`: For the custom dropdown trigger when the dropdown is open.
* `velt-options-dropdown-open`: For the options dropdown trigger when the dropdown is open.
* `velt-priority-dropdown-open`: For the priority dropdown trigger when the dropdown is open.
* `velt-status-dropdown-open`: For the status dropdown trigger when the dropdown is open.
* `velt-reaction-tool-open`: For the reaction tool when the tool is open.
* `velt-comment-bubble-open`: For the comment bubble when the bubble is selected.
* `velt-comment-bubble-hover`: For the comment bubble when the bubble is hovered over.
* \[**Comments**]: Added configuration for customizing the '@here' mention label. eg: @all, @everyone, @team, etc:
Using Props:
```jsx theme={null}
```
Using API:
```javascript theme={null}
const contactElement = client.getContactElement();
contactElement.setAtHereLabel('@all');
```
Using Props:
```html theme={null}
```
Using API:
```javascript theme={null}
const contactElement = Velt.getContactElement();
contactElement.setAtHereLabel('@all');
```
### Improvements
* \[**Notifications**]: Various UI improvements on the notification panel.
### Bug Fixes
* \[**Comments**]: Fixed a bug where email contacts could have an invalid character ('.') at the end.
* \[**Comments**]: Resolved an issue where the comment sidebar filter wouldn't be sticky on page refresh.
* \[**Debugger**]: Fixed typo with event name `commentMoreReplayOptionClicked` and updated it to `commentMoreReplyOptionClicked`.
# Aug 16 2024
Source: https://docs.velt.dev/release-notes/archive/aug-16-2024
# 2.0.35
### Improvements
* \[**Comments**]: Added dynamic `velt-composer-input-focused` class on the composer container whenever the input is focused.
This provides more control over the styling of the composer input when it is focused.
# 2.0.34
### Improvements
* \[**Comments**]: Added dynamic `velt-composer-open` class on the composer container whenever the composer is opened.
* \[**Comments**]: Improved contact and reaction tooltips:
* Inherited shadow DOM property from parent's shadow DOM property.
* Added light mode and dark mode support.
### Bug Fixes
* \[**Comments**]: Fixed user avatar background color in autocomplete chip tooltip to match the color assigned to the user in the SDK.
# Aug 21 2024
Source: https://docs.velt.dev/release-notes/archive/aug-21-2024
# 2.0.39
### Bug Fixes
* \[**Comments**]: Fixed a css padding issue related to the action buttons in the comment dialog composer.
# 2.0.38
## Bug Fixes
* \[**Comments**]: Fixed an issue with the user-avatar component, ensuring it's consistently used across all the components.
# 2.0.37
### New Features
* \[**Comments**]: Added the ability to exclude specific location IDs from the comments sidebar. This provides more control over the displayed content.
Using Props:
```jsx theme={null}
```
Using API:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.excludeLocationIdsFromSidebar(['location1', 'location2']);
```
Using Props:
```html theme={null}
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.excludeLocationIdsFromSidebar(['location1', 'location2']);
```
# 2.0.36
### Improvements
* \[**Comments**]: Applied `location` filters to the inline-comments-section component as well, following the same pattern like other comment components.
* \[**UI Customization**]: Removed `!important` declarations from some component CSS, improving overall style flexibility and reducing potential conflicts.
### Bug Fixes
* \[**Comments**]: Resolved an issue related to calculating comment pin positions, ensuring more accuracy and robustness.
# Aug 22 2024
Source: https://docs.velt.dev/release-notes/archive/aug-22-2024
# 2.0.40
### Bug Fixes
* \[**Comments**]: Fixed an issue related to `addContext` in the `onCommentAdd` event. This ensures that context is consistently added when a new comment is created.
# Aug 6 2024
Source: https://docs.velt.dev/release-notes/archive/aug-6-2024
# 2.0.27
### Bug Fixes
* \[**Comments**]: Fixed an issue with changes detection in the text comment feature.
# 2.0.26
### Improvements
* \[**Notifications**]: Renamed Notification panel "all read" container wireframe components for better clarity and consistency:
This is a breaking change.
```jsx theme={null}
// Old
// New
```
```html theme={null}
```
# 2.0.25
### New Features
* \[**Comments**]: For [Custom Comment Annotation dropdown](/async-collaboration/comments/customize-behavior/custom-lists#1-add-custom-lists-on-comment-annotation) on comment dialog:
* Added a default placeholder for the custom dropdown and made it configurable.
* Added it to the composer by default.
```jsx theme={null}
let customList = [
{ id: 'violent', label: 'Violent' },
{ id: 'inappropriate', label: 'Inappropriate' },
{ id: 'robbery', label: 'Robbery' },
{ id: 'nsfw', label: 'NSFW' },
];
const customListDataOnCommentAnnotation = {
type: 'multi', // choose from 'multi' or 'single'
placeholder: 'Custom Placeholder',
data: customList, // your customList data here
};
```
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = useCommentUtils();
commentElement.createCustomListDataOnAnnotation(customListDataOnCommentAnnotation);
```
Using Wireframe:
```jsx theme={null}
Custom Placeholder
```
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.createCustomListDataOnAnnotation(customListDataOnCommentAnnotation);
```
Using Wireframe:
```jsx theme={null}
Custom Placeholder
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.createCustomListDataOnAnnotation(customListDataOnCommentAnnotation);
```
**Using Wireframe:**
```html theme={null}
Custom Placeholder
```
### Bug Fixes
* \[**Comments**]: Fixed an issue related to `disableReactions` in comment dialog customization.
# Aug 8 2024
Source: https://docs.velt.dev/release-notes/archive/aug-8-2024
# 2.0.29
### New Features
* \[**Comments**]: Added feature to render a comment bubble on the comment pin or triangle instead of the comment dialog. Hovering or clicking the bubble will open the comment dialog.
Using props:
```jsx theme={null}
```
Using API:
```javascript theme={null}
const commentElement = client.getCommentElement();
commentElement.showBubbleOnHover(); // Enable bubble on hover
commentElement.hideBubbleOnHover(); // Disable bubble on hover
```
Using props:
```html theme={null}
```
Using API:
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.showBubbleOnHover(); // Enable bubble on hover
commentElement.hideBubbleOnHover(); // Disable bubble on hover
```
* \[**Location**]: For multiple location setup, added support for using `data-velt-location-id` vs full location object for marking additional locations.
### Improvements
* \[**Comments**]:Refactored comment components code for better maintainability.
### Bug Fixes
* \[**Comments**]: Fixed an issue where assignee banner text color was not being applied correctly for custom statuses.
* \[**Notifications**]: Fixed an issue where the document name in the notifications documents tab was not being displayed correctly.
# 2.0.28
### New Features
* \[**Notifications**]: Added ability to customize tabs on the Notifications Panel.
```jsx theme={null}
```
Using APIs:
```jsx theme={null}
const notificationElement = useNotificationUtils();
const tabConfig = {
"forYou": {
name: 'Custom For You',
enable: true,
},
"documents": {
name: 'Custom Documents',
enable: true,
},
"all": {
name: "Custom All",
enable: false,
},
};
notificationElement.setTabConfig(tabConfig);
```
```jsx theme={null}
```
Using APIs:
```jsx theme={null}
const notificationElement = client.getNotificationElement();
const tabConfig = {
"forYou": {
name: 'Custom For You',
enable: true,
},
"documents": {
name: 'Custom Documents',
enable: true,
},
"all": {
name: 'Custom All',
enable: false,
},
};
notificationElement.setTabConfig(tabConfig);
```
```js theme={null}
const tabConfig = {
"forYou": {
name: 'Custom For You',
enable: true,
},
"documents": {
name: 'Custom Documents',
enable: true,
},
"all": {
name: 'Custom All',
enable: false,
},
};
const notificationsTool = document.querySelector('velt-notifications-tool');
notificationsTool?.setAttribute("tab-config", JSON.stringify(tabConfig));
```
Using APIs:
```jsx theme={null}
const notificationElement = Velt.getNotificationElement();
const tabConfig = {
"forYou": {
name: 'Custom For You',
enable: true,
},
"documents": {
name: 'Custom Documents',
enable: true,
},
"all": {
name: 'Custom All',
enable: false,
},
};
notificationElement.setTabConfig(tabConfig);
```
* \[**@ mention**]: Added ability to override contact list on the client side. [Learn more](/async-collaboration/comments/customize-behavior/@mentions).
* The `merge` parameter is used to determine if the new contact list should be merged with the existing contact list or replaced. Default: `false`.
```jsx theme={null}
const contactElement = useContactUtils();
useEffect(() => {
contactElement.updateContactList([{userId: 'userId1', name: 'User Name', email: 'user1@velt.dev'}], {merge: true});
}, [contactElement]);
```
```jsx theme={null}
const contactElement = client.getContactElement();
contactElement.updateContactList([{userId: 'userId1', name: 'User Name', email: 'user1@velt.dev'}], {merge: true});
```
```jsx theme={null}
const contactElement = Velt.getContactElement();
contactElement.updateContactList([{userId: 'userId1', name: 'User Name', email: 'user1@velt.dev'}], {merge: true});
```
* \[**Document**]: Added ability to set metadata on the document while setting the `documentId`.
* You can set any key/value pair in the metadata object. `documentName` is a special field that we use to display the document name in some Velt Components.
```jsx theme={null}
useSetDocument('unique-document-id', {documentName: 'Document Name'});
```
```jsx theme={null}
const { client } = useVeltClient();
useEffect(() => {
if (client) {
client.setDocument('unique-document-id', {documentName: 'Document Name'});
}
}, [client]);
```
```jsx theme={null}
if(Velt){
Velt.setDocument('unique-document-id', {documentName: 'Document Name'});
}
```
```jsx theme={null}
this.client.setDocument('unique-document-id', {documentName: 'Document Name'});
```
```jsx theme={null}
client.setDocument('unique-document-id', {documentName: 'Document Name'});
```
### Improvements
* \[**Comments**]: Improved the get comment annotations API to determine data loading state in React:
```jsx theme={null}
const [loading, setLoading] = useState(true);
const commentAnnotations = useCommentAnnotations();
useEffect(() => {
if (commentAnnotations) {
setLoading(false);
} else {
setLoading(true);
}
}, [commentAnnotations]);
```
* \[**Core**]:Updated SDK CDN URL in React and Client libraries to point to `cdn.velt.dev`.
# Dec 28 2023
Source: https://docs.velt.dev/release-notes/archive/dec-28-2023
## Summary
The new features in this release give you more control over the sidebar button, Recorder media options, reactions, and contact list.
## Enable Sidebar Button on Comment Dialog
By default, each Comment Dialog has a button at the bottom that will open the Comments Sidebar when clicked. You can now disable this button by setting the sidebarButtonOnCommentDialog prop to false.
## Subscribe to Sidebar Button Clicks on Comment Dialog
You can now subscribe to clicks on the Sidebar Button at the bottom of the Comment Dialog by passing a callback to the onSidebarButtonOnCommentDialogClick event handler.
## Set Recorder Media Options
You can now set the Recorder media options within Comments: (audio, screen, video, all, or none). By default, only audio recording is enabled. To set the Recorder media options, pass in a comma separated string of the features you want to enable.
## Enable Reactions
You can now enable or disable emoji reactions in Comments. By default, reactions are enabled.
## Add Comments on specific elements
You can now add a Comment on a specific element by ID. To do this, use the addCommentOnElement() API method and pass in an object with a specific schema.
## Reset Contact List using POST Method API
You can also replace an entire Group Ids contact list using a POST Method on the following API endpoint: `https://updatecontactlist-4mfhcuyw2q-uc.a.run.app`
# Feb 13 2024
Source: https://docs.velt.dev/release-notes/archive/feb-13-2024
## Versions
* Latest SDK: 1.0.83
* Latest Types: 1.0.100
## Location Support in System Comments
Users can now specify location when adding a new comment using [System Comments](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations).
```jsx theme={null}
{
"data": {
"apiKey": "YOUR_API_KEY",
"authToken": "YOUR_AUTH_TOKEN",
"documentId": "snippyly-tinymce-13-oct",
// You can pass location object to set comment to any specific location
"location": {
"locationName": "YOUR_LOCATION",
// You can optionally pass version in location object
// to show comment on specific version only
// Note: if you set version then id and name fields are mandatory
"version": {
"id": "v1",
"name": "Version 1"
}
},
"targetElement": {
"elementId": "systemCommentTest",
"targetText": "we want our custom elements",
"occurrence": 1
},
"commentData": [
{
"commentText": "Replace: dolor sit amet consectetur adipisicing elit with this is test comment",
"replaceContentHtml": "this is bold text test comment",
"from": {
"email": "test@velt.dev",
"name": "Test User"
}
}
]
}
}
```
## Comment Read Receipts
Newly added comments that have not been seen by Users will now display with a small red dot.
## Specifying where comments are allowed using Class Names and Query Selectors
Expanded options for specifying where comments are allowed using class names and query selectors in addition to ids.
Added the `allowedElementClassNames` and `allowedElementQuerySelectors` properties on the ` ` component to provide more flexibility in determining where comments can be placed.
[See Docs](/async-collaboration/comments/customize-behavior/general-controls)
HTML:
```html theme={null}
```
React:
```jsx theme={null}
;
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.allowedElementClassNames(["class-name-1", "class-name-2"]);
commentElement.allowedElementQuerySelectors(["#id1.class-name-1"]);
```
# Feb 20 2024
Source: https://docs.velt.dev/release-notes/archive/feb-20-2024
## Versions
* Latest SDK: 1.0.84
* Latest Types: 1.0.101
## Option to Remove Comment Tool from Velt Video Player
We have introduced a new option to the [Velt Video Player](/async-collaboration/comments/setup/video-player-setup/video-player-setup) component, allowing users to remove the Comment Tool. This feature offers greater flexibility in customizing the player interface according to your specific needs.
To remove the `Comment Tool` from the `VeltVideoPlayer` control panel, set the `commentTool` property to `false`.
```jsx theme={null}
```
## View Analytics Component
Introducing the [View Analytics](/async-collaboration/view-analytics/overview) component, a powerful tool for tracking visitor engagement with your documents. Simply integrate this component into your project to gain insights into document viewership. Customize it to display visitor data tailored to specific locations, enhancing your analytics capabilities.
[Open in larger window](https://landing-page-demo-velt.vercel.app/?feature=view-analytics)
## React Hooks Integration
We're excited to announce the addition of custom [React Hooks](/api-reference/hooks/hooks) to our SDK, streamlining the development process for our users. These hooks provide an alternative method for interacting with the SDK, offering a smoother developer experience. By utilizing hooks, you can access SDK functionalities without the need for additional wrappers, such as useEffect hooks. This enhancement simplifies your codebase and improves overall efficiency.
```jsx theme={null}
import { useSetDocumentId } from '@veltdev/react';
export default function YourDocument() {
useSetDocumentId("my-document-id")
return (
Your Document Template
)
}
```
```jsx theme={null}
import { useVeltClient } from '@veltdev/react';
import { useEffect, useState } from 'react';
export default function YourDocument() {
const { client } = useVeltClient();
useEffect(() => {
if (client) {
client.setDocumentId('unique-document-id');
}
}, [client]);
return (
Your Document Template
);
}
```
# Feb 27 2024
Source: https://docs.velt.dev/release-notes/archive/feb-27-2024
## Versions
* Latest SDK: [1.0.87](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.103](https://www.npmjs.com/package/@veltdev/types)
## Comment Thread Component
We've abstacted out the UI we use for the threads in our `Comments`component into its own component. Now you can use our [Comment Thread](/async-collaboration/comment-thread/overview) component with your comment data to create your own custom UIs.
[Open in larger window](https://landing-page-demo-velt.vercel.app/?feature=comment-thread)
## Update Contact List
The [Contact List](/users/client-side/setup) is the list of emails you see when you try to `@mention` one of your teammates in a comment thread.
We now have two ways for you to keep your users' contact lists up to date.
* [Server Side](/users/server-side/api-reset-contact-list): Implement updates to your contact lists directly through a server-side API call. This method ensures that your contact lists are always synchronized with the latest data.
* [Client Side](/users/client-side/setup): For more dynamic updates, the client-side `identify()` method can be re-called with a configuration object to reinitialize your logged in users' contact list.
## PDF Viewer Support
We've added support for you to [leave comments on PDF Viewers](/async-collaboration/comments/customize-behavior/general-controls). To enable this, simply add the `data-velt-pdf-viewer` attribute on the element containing your PDF.
```jsx theme={null}
```
# Jan 14 2024
Source: https://docs.velt.dev/release-notes/archive/jan-14-2024
## Summary
These new features are designed to improve the collaboration experience in Velt by making it easier for users to work together on documents. Single Editor Mode can be used to prevent conflicts when multiple users are editing the same document, and Text Element Sync can be used to keep everyone in sync with the latest changes to text-based elements. These features are in beta.
## Single Editor Mode
### This feature allows you to make it so that only one user can be editing elements on the document at the same time.
* To enable single editor mode, use the enableSingleEditorMode() method.
* To disable single editor mode, use the disableSingleEditorMode() method.
* To check if the current user is the editor in single editor mode, subscribe to the isUserEditor\$() event.
* To set the current user as the editor in single editor mode, use the setUserAsEditor() method.
* To reset editor access in single editor mode, use the resetUserAccess() method.
## Text Element Sync
### This feature allows you to sync Input-type elements so when one user types, it gets filled for other users viewing the element.
* To allow Input, Textarea or Contentedible elements to sync across clients, set the data-velt-sync-state attribute to true on the element you are trying to sync.
* To disable clicks on elements, set the data-velt-sync-access attribute to false on the element you are trying to disable.
# July 02 2024
Source: https://docs.velt.dev/release-notes/archive/july-02-2024
## Versions
* Latest React SDK: [1.0.160](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [1.0.160](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [1.0.178](https://www.npmjs.com/package/@veltdev/types)
## Added Email ID Validation in Token
We have introduced email ID validation. This enhancement ensures that the email provided in the token matches the email used during the user identification process (identify call), adding an extra layer of security to prevent user impersonation.
# July 03 2024
Source: https://docs.velt.dev/release-notes/archive/july-03-2024
## Versions
* Latest React SDK: [1.0.162](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [1.0.162](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [1.0.180](https://www.npmjs.com/package/@veltdev/types)
## Added "velt-comment-pin" Component
We have introduced the velt-comment-pin component. You can use the Comment Pin component to manually set the position of a Comment Annotation.
```jsx theme={null}
```
```jsx theme={null}
```
# July 04 2024
Source: https://docs.velt.dev/release-notes/archive/july-04-2024
## Versions
* Latest React SDK: [2.0.0](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.0](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.0](https://www.npmjs.com/package/@veltdev/types)
## Added Shadow Dom Support in Notification and Video Component
We have now added Shadow DOM support in both the notification (for notification panel and notification tool) and video components.
```jsx theme={null}
```
```jsx theme={null}
```
# July 09 2024
Source: https://docs.velt.dev/release-notes/archive/july-09-2024
## Versions
* Latest React SDK: [2.0.3](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.3](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.3](https://www.npmjs.com/package/@veltdev/types)
## Added `selectCommentByAnnotationId` Method in Comment Element
A new method [`selectCommentByAnnotationId`](/api-reference/api-methods/comments#selectcommentbyannotationid) has been added to the comment element, allowing you to select a comment based on its annotation ID.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.selectCommentByAnnotationId("COMMENT_ANNOTATION_ID");
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.selectCommentByAnnotationId("COMMENT_ANNOTATION_ID");
```
# July 10 2024
Source: https://docs.velt.dev/release-notes/archive/july-10-2024
## Versions
* Latest React SDK: [2.0.4](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.4](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.4](https://www.npmjs.com/package/@veltdev/types)
## Overview
* We've enhanced our access control model and data structure by adding the concept of "[Organizations](https://docs.velt.dev/key-concepts/overview#organizations)" in addition to the existing [Document](https://docs.velt.dev/key-concepts/overview#documents) and [Location](https://docs.velt.dev/key-concepts/overview#documents) concepts.
* We've added a feature called “[Organization User Groups](https://docs.velt.dev/key-concepts/overview#organization-user-groups)". You can now create custom collection of users such as engineering, marketing etc to tag groups of users and manage access effectively.
* We have added a [debugger](https://console.velt.dev/dashboard/debugger) to improve the debugging experience during development.
## Breaking changes
* If you were previously using only Documents and want to start using the Organization feature, then you can either migrate your existing comments to the new Organization structure or contact us for this migration. We’d be happy to help you with this.
* If you start using Organizations without migration, your data will remain intact, but the older comments won’t show up on the client.
## Organizations
* An Organization is a top-level entity.
* It contains documents, locations, and users.
* Within an organization, you can create multiple documents, and each document can contain several locations.
* [Learn more](/key-concepts/organizations/setup).
## Organization User Groups
You can create a custom collection of users, such as engineering, marketing, etc., to group organization users and manage access effectively.
* This can be used to @mention a team group versus just individuals (just like in Slack).
* This can also be used to provision access to documents.
* Organization users can belong to multiple organization user groups.
* Non-organization users can't be part of organization user groups.
* [Learn more](/key-concepts/users/organization-user-groups).
## Access Control
You can now control access to Velt data at the Organization and Document levels.
This adds an extra layer of data security. [Learn more](/key-concepts/overview#access-control).
## Debugger
* The debugger can be found in the developer console [here](https://console.velt.dev/dashboard/debugger).
* It allows you to view real-time events fired from the Velt SDK in your application.
* The debugger automatically pauses after 5 minutes of use. You can resume it once it does.
## Added `updateCommentDialogPosition` Method in Comment Element
We have added a new method `updateCommentDialogPosition` in Comment element. This triggers a dialog position update for manual pin comments. If the pin position updates while the dialog is open and the dialog position doesn't update, using this will manually trigger an update.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.updateCommentDialogPosition();
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.updateCommentDialogPosition();
```
# July 15 2024
Source: https://docs.velt.dev/release-notes/archive/july-15-2024
## Versions
* Latest React SDK: [2.0.5](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.5](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.5](https://www.npmjs.com/package/@veltdev/types)
## Added the Option to Enable or Disable Area Comments
We have added the option to enable or disable area comment in the comment element. By default area comments are enabled.
```jsx theme={null}
```
```jsx theme={null}
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableAreaComment();
commentElement.disableAreaComment();
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableAreaComment();
commentElement.disableAreaComment();
```
# July 17 2024
Source: https://docs.velt.dev/release-notes/archive/july-17-2024
## Versions
* Latest React SDK: [2.0.9](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.9](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.9](https://www.npmjs.com/package/@veltdev/types)
## Update to `autocomplete-option-description-wireframe`
We have added a `field` property in `autocomplete-option-description-wireframe` to replace email data with mentioned property value, for example:
```jsx theme={null}
```
```jsx theme={null}
```
# July 18 2024
Source: https://docs.velt.dev/release-notes/archive/july-18-2024
## Versions
* Latest React SDK: [2.0.11](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.11](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.11](https://www.npmjs.com/package/@veltdev/types)
## Added Custom Cursor
We have introduced support for a custom cursor for comments. The custom cursor's image should be 32 x 32 pixels.
```jsx theme={null}
```
```jsx theme={null}
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.setPinCursorImage(BASE64_IMAGE_STRING);
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.setPinCursorImage(BASE64_IMAGE_STRING);
```
## Added `placeholder` Support
We have added `placeholder` support for Comments DIalog within the `comment-dialog-composer-input-wireframe`. [Learn more](https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/subcomponents/composer/overview).
## Added `notificationSourceData` Field
We have added the `notificationSourceData` field for custom notifications in SDK and REST API. It is added via the [Add Norifications API](https://docs.velt.dev/api-reference/rest-apis/notifications/add-notifications#add-notifications). This object can be any key value pair that the developer sets. It has no type.
## Added Additional Fields for SendGrid Email Template
We have added a few new fields for [SendGrid email](https://docs.velt.dev/async-collaboration/comments/notifications#email-notifications#email-template-data) template.
* `firstComment`: first message in the thread.
* `latestComment`: Latest message in the thread that prompted the email.
* `prevComment`: The previous message to the latestMessage.
* `commentsCount`: Total number of comments in the comment annotation.
* `fromUser`: Action user's object.
* `commentAnnotation`: The comment annotation object without the comments.
* `actionType`: The action that resulted in the notification.
## Added the Update to Create Organization if Not Specified
For the [Update Disable State](https://docs.velt.dev/api-reference/rest-apis/organizations/update-organization-disable-state#update-disabled-state-for-organizations) and [Add Users](https://docs.velt.dev/api-reference/rest-apis/v2/users/add-users#add-users) API, if Organization does not exist, we will create it with metadata.
# July 19 2024
Source: https://docs.velt.dev/release-notes/archive/july-19-2024
## Versions
* Latest React SDK: [2.0.13](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.13](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.13](https://www.npmjs.com/package/@veltdev/types)
## Added `velt-data` Support
Now you can render dynamic data from these Classes inside your Velt Components Wireframe:
1. User: This represents the current logged-in user. It is available across all components.
2. UserContact: This represents the user contact object (it has the same class like the User). It is available on the autocomplete component where the user contacts are rendered.
3. CommentAnnotation: This represents the comment thread. This is available within the comment feature components.
4. Comment: This represents the message inside the thread. This is available within the comment feature components.
Here is one way to use it:
```jsx theme={null}
```
```jsx theme={null}
```
# July 22 2024
Source: https://docs.velt.dev/release-notes/archive/july-22-2024
## Versions
* Latest React SDK: [2.0.14](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.14](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.14](https://www.npmjs.com/package/@veltdev/types)
## Added `updateContactList` Method
We have now added a way to update UserContacts from client using the `client.updateContactList(userContact[])` method.
a. By default, it will overwrite the current contact list.
b. To merge with the already present contacts, you may use it with the merge flag: `client.updateContactList(userContact[], {merge:true})`.
This will work for both organization and non-organization structures.
# July 23 2024
Source: https://docs.velt.dev/release-notes/archive/july-23-2024
## Versions
* Latest React SDK: [2.0.16](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.16](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.16](https://www.npmjs.com/package/@veltdev/types)
## Added the Option to Add Manual Comment with Context
Within the comment element, we have introduced `addManualComment` method to add manual comment with context.
API Method:
```jsx theme={null}
const commentElement = client.getCommentElement();
const config: ManualCommentAnnotationConfig = {
context: {}, // your context here
};
commentElement.addManualComment(config);
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
const config: ManualCommentAnnotationConfig = {
context: {}, // your context here
};
commentElement.addManualComment(config);
```
`ManualCommentAnnotationConfig` Class:
```jsx theme={null}
export class ManualCommentAnnotationConfig {
context?: any;
}
```
## Added `onCommentClick` Callback
We have added `onCommentClick` callback in `velt-comment-thread` component.
```jsx theme={null}
```
```jsx theme={null}
handleOnCommentClick(data)}
/>
```
# July 24 2024
Source: https://docs.velt.dev/release-notes/archive/july-24-2024
## Versions
* Latest React SDK: [2.0.20](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.20](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.20](https://www.npmjs.com/package/@veltdev/types)
## Added Minimap Component
We have just introduced Minimap Component within Comments. By default, it is positioned right, but you have the option to change it to left.
```jsx theme={null}
{/* scrollable content */}
```
```jsx theme={null}
```
## Added Support for Reaction Tool
We have added support for reaction tool in custom video player. Within the reaction tool, we have added `videoPlayerId` attribute and `onReactionToolClick` event listener.
```jsx theme={null}
onReactionToolClick()} />
```
```jsx theme={null}
```
# July 26 2024
Source: https://docs.velt.dev/release-notes/archive/july-26-2024
## Versions
* Latest React SDK: [2.0.21](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.21](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.21](https://www.npmjs.com/package/@veltdev/types)
## Added Notifications Panel and Tool Wireframes
We have just added Notifications Panel and Notifications Tool wireframes.
## Deprecated Notification History Panel
We have deprecated the Notification History Panel. It will now load the Notification Panel Component.
## Added Option to Open Sidebar From Left
Previously, the sidebar would only open from right by default. Now, we have added the option to open the sidebar from left as well.
```jsx theme={null}
```
```jsx theme={null}
```
## Added More Props Within Notifications Tool Component
We have added `variant`, `panelVariant` and `panelOpenMode` props in Notification Tool Component.
```jsx theme={null}
```
```jsx theme={null}
```
## Added Tab Configuration Support for Notification Tool
You can now customize your tabs for Notifications Tool Component.
```jsx theme={null}
```
```jsx theme={null}
const tabConfig = {
"forYou": {
name: 'Custom For You',
enable: true,
},
"documents": {
name: 'Custom Document',
enable: false,
},
"people": {
name: 'Custom People',
enable: false,
},
"all": {
name: "Custom All",
enable: true,
},
}
const notificationsTool = document.querySelector('velt-notifications-tool');
notificationsTool?.setAttribute("tab-config", JSON.stringify(tabConfig));
```
# July 29 2024
Source: https://docs.velt.dev/release-notes/archive/july-29-2024
## Versions
* Latest React SDK: [2.0.22](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.22](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.22](https://www.npmjs.com/package/@veltdev/types)
## Added Wireframe for Custom Annotation Dropdown
We have added wireframes for custom annotation. [Learn more](https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/subcomponents/custom-annotation-dropdown).
## Added Custom Annotation Dropdown Component Feature
You can now customize annotation dropdown component feature. Create custom list data on annotation as required.
```jsx theme={null}
const customChipDropdownData: {
type: 'multi' | 'single',
data: {
id: string,
label: string
}[]
} = {
type: 'multi',
data: [
{ id: 'violent', label: 'Violent' },
{ id: 'inappropriate', label: 'Inappropriate' },
{ id: 'robbery', label: 'Robbery' },
{ id: 'nsfw', label: 'NSFW' },
]
}
```
## Configure `maxDays` In Notification Tool and Notification Element
Set maximum days for notifications tool and notifications element by using the `setMaxDays()` API method. By default, the maximum days are set to 30.
```jsx theme={null}
```
```jsx theme={null}
```
API Methods:
```jsx theme={null}
const notificationElement = client.getNotificationElement();
notificationElement.setMaxDays(15);
```
```jsx theme={null}
const notificationElement = Velt.getNotificationElement();
notificationElement.setMaxDays(15);
```
## Added New Action Types
We have added new action types within notifications: `reactionAdded`, `reactionDeleted`.
## Added Notifications Metadata Type
Added notifications metadata type. User will get this metadata from `notification.metadata` object in the notification callback. [Learn more](/api-reference/sdk/models/data-models#notification#notificationmetadata-class-properties).
```jsx theme={null}
export class NotificationMetadata {
apiKey?: string;
clientOrganizationId?: string;
organizationId?: string;
clientDocumentId?: string | number;
documentId?: string;
locationId?: number;
location?: Location;
}
```
## Added `customListDataOnComment` Method
For Autocomplete data, you can now directly set the data from comment element.
```jsx theme={null}
const customListDataOnComment: {
hotkey: string,
type: string,
data: {
id: string,
name: string,
description: string
}[]
} = {
hotkey: "!",
type: "custom",
data: [
{
id: '1',
name: 'Name 1',
description: 'Test Description 1'
},
{
id: '2',
name: 'Name 2',
description: 'Test Description 2'
},
{
id: '3',
name: 'Name 3',
description: 'Test Description 3'
}
],
}
```
API Methods
```jsx theme={null}
const commentElement = client.getCommentElement();
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.createCustomListDataOnComment(customListDataOnComment);
```
# July 31 2024
Source: https://docs.velt.dev/release-notes/archive/july-31-2024
## Versions
* Latest React SDK: [2.0.24](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.24](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.24](https://www.npmjs.com/package/@veltdev/types)
## Added the option to Subscribe/Unsubscribe at Thread Level
Within the comments dialog options dropdown, we have added subscribe/unsubscribe wireframes. [Learn more](/ui-customization/features/async/comments/comment-dialog/subcomponents/options-dropdown).
```jsx theme={null}
```
```jsx theme={null}
```
## Added the Option to Add a Custom Initial in the User Object
Users can now add an optional custom initial within the [user object](https://docs.velt.dev/api-reference/sdk/models/data-models#user). If the initial is not provided in the identify call or the Add User REST API, we will automatically create it using the name.
## Added Search on Organization Names
We have added the feature to search on organization names in the contact list, if present.
## Added Delete Button on the Thread Header
You can now add a delete button within the comments dialog thread header. [Learn more](/ui-customization/features/async/comments/comment-dialog/subcomponents/header).
```jsx theme={null}
Delete
```
```jsx theme={null}
Delete
```
# June 10 2024
Source: https://docs.velt.dev/release-notes/archive/june-10-2024
## Versions
* Latest SDK: [1.0.147](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.164](https://www.npmjs.com/package/@veltdev/types)
## Updated JWT Token to include user properties such as groupId and isAdmin
We updated the body that is sent to the `https://api.velt.dev/v1/auth/token/get` [JWT Token endpoint](/security/jwt-tokens) to include user properties such as `groupId` and `isAdmin`.
The `groupId` field is used to validate that the `groupId` provided in the `identify` call is the same as the one passed to the JWT Token.
The `isAdmin` field is used to set a user as an `admin`.
| Field | Required | Description |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `apiKey` | Yes | Velt API Key |
| `authToken ` | Yes | Auth Token from the Velt console |
| `userId ` | Yes | Unique user id of the user |
| `userProperties.groupId` | No | If `groupId` is provided, it will be validated with the groupId used in the identify call. Recommended if you are setting `groupIds` |
| `userProperties.isAdmin` | No | Set to `true` if you want to set user as `admin`. This is the only way to set a user as an `admin` User |
```jsx theme={null}
{
"data": {
"apiKey": "YOUR_API_KEY", //Velt API Key
"authToken": "YOUR_AUTH_TOKEN", // Auth Token from the Velt console
"userId": "yourUserId", // unique user id of the user you are generating a JWT Token for
"userProperties": {
groupId: "YOUR_USER_GROUP_ID", // If groupId is provided here then we will validate it with the groupId used in the identify call
isAdmin: true, // Set to true if you want to set user as admin
}
}
}
```
## Option to force re-login user on identify call
By default when you [identify](/get-started/advanced) a user, we maintain the user auth in the browser unless you explicitly change the logged in user with another identify call.
If you are granting a user additional access or have changed some metadata about the user and want those changes to be reflected immediately, then you should re-call the identify method with `forceReset` set to `true`.
```jsx theme={null}
await client.identify(user, {
forceReset: true
});
```
# June 24 2024
Source: https://docs.velt.dev/release-notes/archive/june-24-2024
## Versions
* Latest React SDK: [1.0.158](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [1.0.158](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [1.0.178](https://www.npmjs.com/package/@veltdev/types)
## Added `onContactSelected` API Method
We have introduced the [`onContactSelected`](/api-reference/sdk/api/api-methods#client#oncontactselected) API method in the Contact Element. This new method allows organization clients to add selected users to their organization or document-level IAM directly.
API Method:
```jsx theme={null}
const contactElement = client.getContactElement();
contactElement.onContactSelected().subscribe((data: any) => {
console.log('contact selected: ', data);
});
```
```jsx theme={null}
const contactElement = Velt.getContactElement();
contactElement.onContactSelected().subscribe((data: any) => {
console.log('contact selected: ', data);
});
```
React Hook:
```jsx theme={null}
import React, { useEffect } from 'react';
import { useContactUtils, useContactSelected } from '@veltdev/react';
function YourComponent() {
const contactUtils = useContactUtils();
useEffect(() => {
console.log('contactUtils: ', contactUtils);
}, [contactUtils]);
const onContactSelected = useContactSelected();
useEffect(() => {
console.log('onContactSelected: ', onContactSelected);
}, [onContactSelected]);
return (
// Your component code
);
}
```
API Method Response Payload:
```jsx theme={null}
export class UserContactSelectedPayload {
/**
* Selected user contact details.
*/
contact!: UserContact;
/**
* Is user part of organization contact.
*/
isOrganizationContact!: boolean;
/**
* Is user part of document contact.
*/
isDocumentContact!: boolean;
/**
* Document access type.
*/
documentAccessType!: string;
}
```
# June 29 2024
Source: https://docs.velt.dev/release-notes/archive/june-29-2024
## Versions
* Latest React SDK: [1.0.159](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [1.0.159](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [1.0.177](https://www.npmjs.com/package/@veltdev/types)
## Added `organizationGroups` Support in Contacts and Autocomplete Feature
Within the contacts and autocomplete features we have added the support for `organizationGroups`.
## Added Various Methods in Contact Element
We have introduced three new methods in the Contact Element: [`enableAtHere`](/api-reference/sdk/api/api-methods#client#enableathere), [`disableAtHere`](/api-reference/sdk/api/api-methods#client#disableathere), and [`updateContactListScopeForOrganizationUsers`](/api-reference/sdk/api/api-methods#client#updatecontactlistscopefororganizationusers).
API Methods:
```jsx theme={null}
const contactElement = client.getContactElement();
// To enable @here for contact list
contactElement.enableAtHere();
// To disable @here for contact list
contactElement.disableAtHere();
/**
* Update contact list scope for organization users.
* @param scope ContactListScopeForOrganizationUsers[]
*/
contactElement.updateContactListScopeForOrganizationUsers(['all', 'organization', 'organizationUserGroup', 'document']);
```
```jsx theme={null}
const contactElement = Velt.getContactElement();
// To enable @here for contact list
contactElement.enableAtHere();
// To disable @here for contact list
contactElement.disableAtHere();
/**
* Update contact list scope for organization users.
* @param scope ContactListScopeForOrganizationUsers[]
*/
contactElement.updateContactListScopeForOrganizationUsers(['all', 'organization', 'organizationUserGroup', 'document']);
```
# June 30 2024
Source: https://docs.velt.dev/release-notes/archive/june-30-2024
## Versions
* Latest React SDK: [2.0.4](https://www.npmjs.com/package/@veltdev/react)
* Latest Non-React SDK: [2.0.4](https://www.npmjs.com/package/@veltdev/client)
* Latest Types: [2.0.4](https://www.npmjs.com/package/@veltdev/types)
## Added REST APIs
We have added REST APIs so that developers have more control when it comes to accessing and setting data.
REST APIs is a featureset that will have continued expansion but the current REST APIs give improved control over **Organizations**, **Documents**, **Users**, **Organization User Groups**, and **Comments**.
More REST APIs will be coming soon!
### [Organizations APIs](/api-reference/rest-apis/organizations/add-organizations)
1. Add Organizations
2. Update Organizations
3. Delete Organizations
4. Get Organizations
5. Update Disabled State for Organizations
### [Documents APIs](/api-reference/rest-apis/documents/add-documents)
1. Add Documents
2. Update Documents
3. Delete Documents
4. Get Documents
5. Update Access for Documents
6. Update Disabled State for Documents
### [Users APIs](/api-reference/rest-apis/v2/users/add-users)
1. Add Users
2. Update Users
3. Delete Users
4. Get Users
### [Organization User Groups APIs](/api-reference/rest-apis/v2/users/add-users)
1. Add User Groups
2. Add Users to Groups
3. Delete Users from Groups
### [Commenting APIs](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations)
1. Add Comment Annotations
2. Update Comment Annotations
3. Delete Comment Annotations
4. Get Comment Annotations
5. Add Comments
6. Update Comments
7. Delete Comments
8. Get Comments
# June 6 2024
Source: https://docs.velt.dev/release-notes/archive/june-6-2024
## Versions
* Latest SDK: [1.0.144](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.161](https://www.npmjs.com/package/@veltdev/types)
# 1. Major Improvements to Comments Sidebar Customization
In this update, we made several improvements to the `Customization` of the `Comments Sidebar` and `Sidebar Button`
## Support for customizing additional Subcomponents within the Comments Sidebar and Sidebar Button
We modified or added support for customizing the following `subcomponents` of the `Comments Sidebar`:
* [Filter](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/overview)
* [Filter Category](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/category)
* [Filter Close Button](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/close-button)
* [Filter Done Button](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/done-button)
* [Filter Item](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
* [Filter Groupby](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/groupby)
* [Filter Location](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/location)
* [Filter People](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/people)
* [Filter Priority](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/priority)
* [Filter Title](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/title)
* [Filter Versions](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/versions)
* [Header](/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/overview)
* [Header Status](/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/status/overview)
* [Header Status Trigger](/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/status/subcomponents/trigger)
* [Header Status Content](/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/status/subcomponents/content)
* [List](/ui-customization/features/async/comments/comments-sidebar/subcomponents/list/overview)
* [List Dialog Container](/ui-customization/features/async/comments/comments-sidebar/subcomponents/list/subcomponents/dialog-container)
* [List Group](/ui-customization/features/async/comments/comments-sidebar/subcomponents/list/subcomponents/group)
We modified or added support for customizing the following `subcomponents` of the `Sidebar Button`:
* [Sidebar Button](/ui-customization/features/async/comments/comments-sidebar/sidebar-button/overview)
* [Sidebar Button Icon](/ui-customization/features/async/comments/comments-sidebar/sidebar-button/subcomponents/icon)
## Subcomponent Name Changes
We changed the names of several subcomponents:
* ` ` -> ` `
* `` -> ``
The `Private Badge` subcomponent was moved from being a child of `Comment Dialog` to being a child of the `Comment Dialog Composer`:
* ` ` -> ``
* `` -> ``
# 2. Live State Sync changes
## `getLiveStateData()` now has a config option to only subscribe to new changes
By default, the [getLiveStateData()](/realtime-collaboration/live-state-sync/setup) subscription will return all changes to the shared live data object including changes that occured when the current client was not subscribed.
If you want to only receive changes starting from when the client subscribed to `getLiveStateData()`, you can pass a config object as shown below:
```jsx theme={null}
const liveStateDataConfig = {
listenToNewChangesOnly: true // default is false
};
let subscription = liveStateSyncElement.getLiveStateData(LIVE_STATE_DATA_KEY, liveStateDataConfig).subscribe((data) => {
console.log('[Debug] getLiveStateData 31-05-2024-2 in html', data);
});
```
This also applies to the `useLiveStateData()` hook:
```jsx theme={null}
const liveStateDataConfig = {
listenToNewChangesOnly: true // default is false
};
const liveStateData = useLiveStateData(LIVE_STATE_DATA_KEY, liveStateDataConfig)
```
As well as the `useLiveState()` hook:
```jsx theme={null}
const [counter, setCounter] = useLiveState < number > ("counter", 0, {
syncDuration: 100,
resetLiveState: true,
listenToNewChangesOnly: true // default is false
});
```
## Method to listen to changes in server connection state
You can listen to changes in the server connection state with the [onServerConnectionStateChange()](/realtime-collaboration/live-state-sync/setup) method:
```jsx theme={null}
const liveStateSyncElement = client.getLiveStateSyncElement();
let subscription = liveStateSyncElement.onServerConnectionStateChange().subscribe((data) => {
console.log('server connection state change: ', data);
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
The server connection state will be an ENUM with the following states:
```jsx theme={null}
export enum ServerConnectionState {
ONLINE = 'online',
OFFLINE = 'offline',
PENDING_INIT = 'pendingInit',
PENDING_DATA = 'pendingData',
}
```
You can also listen to changes in the server connection state using the `useServerConnectionStateChangeHandler()` hook as well:
```jsx theme={null}
const serverConnectionState = useServerConnectionStateChangeHandler();
```
# 3. Updates to Popover Comments
## Added support for Popover comments using Single Comment Tool
There are now two patterns to add the `Comment Tool` component with [Popover comments](/async-collaboration/comments/setup/popover):
* (Already Existed) Add a `Comment Tool` next to each element you want to have `Popover` comments
* (New) Have a single `Comment Tool` and use it to pin a `Popover `comment on a particular element
### Single Comment Tool
If you want to have a single `Comment Tool` in a single location such as the navigation bar, you can do so as well.
To do this, add `data-velt-target-comment-element-id` as an attribute on each element you want to add comments on.
Now, when you click on the `Comment Tool` and click on the target element, it will attach a `Popover` comment to the element.
You will now notice that you can only add one `Comment Annotation` per element.
If you don't add the `data-velt-target-comment-element-id` attribute, you will be adding multiple `Comment Annotations` on the same element.
```jsx theme={null}
```
# March 14 2024
Source: https://docs.velt.dev/release-notes/archive/march-14-2024
## Versions
* Latest SDK: [1.0.96](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.111](https://www.npmjs.com/package/@veltdev/types)
## Notifications Component
The [Notifications](/async-collaboration/notifications/setup) component can be used to notify your users in-app when they receive a reply to a comment or are `@mentioned` by a teammate.
You will need to [enable Notifications in your console](https://console.velt.dev/dashboard/config/notification) in order for Notifications component to work.
[Open in larger window](https://landing-page-demo-velt.vercel.app/?feature=notifications)
To implement `Notifications`, simply add it to your app like this:
```jsx React / Next.js theme={null}
import { VeltNotificationsTool, VeltNotificationsHistoryPanel} from '@veltdev/react';
function YourComponent() {
return (
)
}
```
## Custom Notifications
Additionally, you can [send custom notifications](/async-collaboration/notifications/api-add-notification) to this component using our `https://api.velt.dev/v1/notifications/add` API end point.
Sample Post Request:
```jsx theme={null}
const options = {method: 'POST', body: JSON.stringify(body)};
fetch('https://api.velt.dev/v1/notifications/add', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
Sample Body:
```jsx theme={null}
{
"data": {
"apiKey": "YOUR_API_KEY",
"authToken": "YOUR_AUTH_TOKEN",
"documentId": "YOUR_DOCUMENT_ID",
"actionUser": {
"email": "actionuseremail@domain", // required
"name": "Action Username", // optional
"photoUrl": "Action User Photo URL", // optional
"userId": "User ID", // required
},
"displayHeadlineMessageTemplate": "This is main template, you can pass variables using curly brackets like this: {actionUser}, {recipientUser}, {yourCustomVariableWithStringValue}",
"displayHeadlineMessageTemplateData": {
"actionUser": {
"email": "actionuseremail@domain", // required
"name": "Action Username", // optional
"photoUrl": "Action User Photo URL", // optional
"userId": "User ID", // required
},
"recipientUser": {
"email": "recipientuseremail@domain", // required
"name": "Recipient Username", // optional
"photoUrl": "Recipient User Photo URL", // optional
"userId": "User ID", // required
},
"yourCustomVariableWithStringValue": "Variable will be replaced with this text"
},
"displayBodyMessage": "This is body message (Secondary message)",
// Pass list of users who should be notified, notification will be shown to all the users in all section in notification panel and notification history panel, but it will be shown in 'forYou' section also to notifyUsers.
"notifyUsers": [
{
"email": "notifyuseremail@domain", // required
"name": "Notify User Name", // optional
"photoUrl": "Notify User Photo URL", // optional
"userId": "User ID", // required
}
]
}
}
```
## Dark Mode on All Tool and Button Components
You can now configure Dark Mode individually on all Tool and Button Components in the Velt SDK.
Example:
```jsx theme={null}
```
If you want to configure Dark Mode globally, you can also use:
```jsx theme={null}
client.setDarkMode(true)
```
## Bug Fixes
* Fixed bug where you could still drag comments into restricted areas
* Fixed user email related issue for admin users in invite dialog
* Fixed typed related issues in darkMode
* Added option to disable live selection with disableFeatures()
# March 5 2024
Source: https://docs.velt.dev/release-notes/archive/march-5-2024
## Versions
* Latest SDK: [1.0.91](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.106](https://www.npmjs.com/package/@veltdev/types)
## Method to return the XPath of the DOM element where a Comment was added.
Introducing the [getElementRefByAnnotationId()](/async-collaboration/comments/customize-behavior/action-methods) method that returns the XPath of the DOM element where the specified comment was added.
```jsx theme={null}
const commentElement = client.getCommentElement();
let elementRef = commentElement.getElementRefByAnnotationId('annotationId')
```
## Method to scroll the page to a specific Comment
Added the [scrollToCommentByAnnotationId()](/async-collaboration/comments/customize-behavior/action-methods) method that scrolls the page to the specified element where the comment was added. This functionality is contingent upon the presence of the element in the DOM.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.scrollToCommentByAnnotationId('annotationId')
```
## Live selection style configuration
Added the ability to [customize the styling of Live Selection](/realtime-collaboration/live-selection/customize-behavior) by passing a style object to the `data-live-selection-config` attribute.
```jsx theme={null}
/**
* live selection configuration:
* position:
* - horizontal: 'left'
* border:
* - border: false
*/
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, consequatur.
```
## Event Handler for Click Events on Presence Users
Implemented the [onPresenceUserClick](/realtime-collaboration/presence/customize-behavior) event handler for click events on Presence avatar circles.
```jsx theme={null}
const onPresenceUserClickEvent = (user) => {
console.log("Clicked presence user: ", user);
}
onPresenceUserClickEvent(user)} />
```
## Option to disclude current user from list of Presence Users
The [self](/realtime-collaboration/presence/customize-behavior) property can be used to include or disclude the current user from the list of Presence users. By default the current user is added.
```js theme={null}
```
API Method:
```jsx theme={null}
const presenceElement = client.getPresenceElement();
presenceElement.enableSelf();
presenceElement.disableSelf();
```
# May 10 2024
Source: https://docs.velt.dev/release-notes/archive/may-10-2024
## Versions
* Latest SDK: [1.0.119](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.135](https://www.npmjs.com/package/@veltdev/types)
## Updates to `useLiveState` Hook
The [useLiveState()](https://docs.velt.dev/realtime-collaboration/live-state-sync/setup) Hook was updated to include a config object as a third parameter.
Some background on the `useLiveState()` Hook:
If you are familiar with React's `useState()` hook, we have a similar hook called `useLiveState()` that can be used to sync data realtime for specific state variables in your code.
Hook syntax:
```jsx theme={null}
const [value, setValue] = useLiveState(UNIQUE_ID, INITIAL_VALUE, OPTIONS);
```
The hook takes in 3 parameters:
* `UNIQUE_ID` -> unique id in string to be synced across the screens
* `INITIAL_VALUE` -> initial value of the state
* `OPTIONS` (object)
* `syncDuration` -> debounce duration in milliseconds to sync realtime (optional, default value 50ms)
* `resetLiveState` -> Boolean to reset locatl state value on server side on initiatlize of hook (default: `false`)
The hook returns a setter and getter:
* `value` -> current state value (similar to useState hook)
* `setValue` -> function to be called to set next value (similar to useState hook)
Example:
```tsx theme={null}
import { useLiveState } from "@veltdev/react";
export function MyReactComponent() {
const [counter, setCounter] = useLiveState < number > ("counter", 0, {
syncDuration: 100,
resetLiveState: true
});
return (
setCounter((counter || 0) - 1)}>-
Counter: {counter}
setCounter((counter || 0) + 1)}>+
);
}
```
## Option to [Enable DOM Change Detection in Comment Mode](https://docs.velt.dev/async-collaboration/comments/customize-behavior/general-controls)
By default, we skip `Comment` `DOM Change Detection` when users are in `Comment Mode` to improve performance.
However, you can turn `Comment` `DOM Change Detection` back on with the `changeDetectionInCommentMode` property.
This will make `Comment's` reposition themselves if the DOM happens to change while in `Comment Mode`.
`Default: false`
```jsx theme={null}
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
// To enable change detection when comment mode is on
commentElement.enableChangeDetectionInCommentMode();
// To disable change detection when comment mode is on
commentElement.disableChangeDetectionInCommentMode();
```
## Option to [Sort Order of Comments in Sidebar](http://localhost:3000/async-collaboration/comments-sidebar/customize-behavior)
By default, the `Comments` in the `Sidebar` are ordered in descending order of when they were last updated.
You can change this sorting order with the `sort-data` property.
There are three options for sorting:
* `asc` - to show comments in descendending order of when they were last updated
* `desc` - to show comments in ascending order of when they were last updated
* `none` - to show comments in the sequence they were added
```jsx theme={null}
```
# May 17 2024
Source: https://docs.velt.dev/release-notes/archive/may-17-2024
## Versions
* Latest SDK: [1.0.129](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.146](https://www.npmjs.com/package/@veltdev/types)
## Added Variant support in customization
If you want to have multiple versions of customized components that you can swap between, you can use [variants](/ui-customization/layout#variants).
To define multiple `variants`, add multiple templates of the same component `wireframe` to the `VeltWireframe` component and give them each a `variant` name.
```jsx theme={null}
#Your wireframe for variant sidebar1
#Your wireframe for variant sidebar2
```
To use a specific variant, define it on the `variant` props when using the Velt component in your app.
```jsx theme={null}
```
## Added Custom Dropdown Lists
You can have [custom dropdown lists](/async-collaboration/comments/customize-behavior/autocomplete) appear when certain `hotkeys` are pressed.
When you press a `hotkey` inside the `Comment Dialog` composer, it will open a dropdown list of items that you can select.
Selecting an item frop the dropdown list will create a `chip` that is placed in the comment text.
## Merged useSetLocation and useAddLocation hook
The second parameter of `useSetLocation` can be set to `true` to add additional locations instead of using `useAddLocation`.
```jsx theme={null}
import { useSetLocation } from "@veltdev/react";
function YourComponent() {
// to set main location
useSetLocation(YOUR_LOCATION_OBJECT);
// to set extra location
useSetLocation(YOUR_EXTRA_LOCATION_OBJECT, true);
}
```
## Option to submit Comment on Enter Key Press
By default, pressing `enter` will add a new line and pressing `shift` + `enter` will submit a comment.
If you want to [change this default behavior](/async-collaboration/comments/customize-behavior/general-controls) so that pressing `enter` will submit a comment, you can set the `enterKeyToSubmit` property to `true`.
```jsx theme={null}
```
```jsx theme={null}
// API methods
const commentElement = client.getCommentElement();
// To enable enter key to submit a comment
commentElement.enableEnterKeyToSubmit();
// To disable enter key to submit a comment
commentElement.disableEnterKeyToSubmit();
```
## Added support to disable Shadow DOM on a few more components
There are now a few more components that can have the [Shadow DOM disabled](/ui-customization/customize-css/remove-shadow-dom).
```jsx theme={null}
```
# May 23 2024
Source: https://docs.velt.dev/release-notes/archive/may-23-2024
## Versions
* Latest SDK: [1.0.130](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.147](https://www.npmjs.com/package/@veltdev/types)
## Changes to Wireframe Subcomponent Names
### Comment Dialog
`VeltCommentDialogWireframe.ThreadCard.Files` -> `VeltCommentDialogWireframe.ThreadCard.Attachments`
`VeltCommentDialogWireframe.Composer.Files` -> `VeltCommentDialogWireframe.Composer.Attachments`
`VeltCommentDialogWireframe.CommentCount` -> `VeltCommentDialogWireframe.CommentIndex`
### Comments Sidebar
`VeltCommentsSidebarWireframe.PageMode` -> `VeltCommentsSidebarWireframe.PageModeComposer`
### Text Comment Toolbar
`VeltTextCommentToolbarWireframe.Seperator` -> `VeltTextCommentToolbarWireframe.Divider`
### Options Dropdown
`VeltOptionsDropdownContent.MakePrivate.On` -> `VeltOptionsDropdownContent.MakePrivate.Enable`
`VeltOptionsDropdownContent.MakePrivate.Off` -> `VeltOptionsDropdownContent.MakePrivate.Disable`
### Comments Sidebar
`VeltCommentsSidebarWireframe.PageMode` -> `VeltCommentsSidebarWireframe.PageModeComposer`
# May 24 2024
Source: https://docs.velt.dev/release-notes/archive/may-24-2024
## Versions
* Latest SDK: [1.0.133](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.150](https://www.npmjs.com/package/@veltdev/types)
## Added Skeleton loader subcomponent to Comments Sidebar Wireframe
The Comments Sidebar Wireframe now has a [Skeleton loader subcomponent](/ui-customization/features/async/comments/comment-sidebar-components) that you can modify.
## Added functionality to search by name in @mention dropdown
You can now search by name, in addition to just email, in the `@mention` dropdown
## Added option to show resolved Comments on DOM
By default, resolved Comments are not shown on the DOM.
There is now an option to [show resolved Comments on the DOM](/async-collaboration/comments/customize-behavior/visibility-controls).
```jsx theme={null}
// Html
// React
// API Methods
const commentElement = client.getCommentElement();
// To show resolved comments on dom
commentElement.showResolvedCommentsOnDom();
// To hide resolved comments on dom
commentElement.hideResolvedCommentsOnDom();
```
## User background color
You can now pass in a color when you identify a user using [client.identify()](/get-started/advanced)
```jsx theme={null}
const user = {
userId: uid,
name: displayName,
email: email,
photoUrl: photoURL,
color: colorCode, // Hex code value goes in the place of colorCode
groupId: groupId // this is the organization id the user belongs to.
};
await client.identify(user);
```
## Added additional subcomponents to the Comments Dialog Wireframe
The following subcomponents were added to the Comments Dialog Wireframe:
* [Priority Dropdown](/ui-customization/features/async/comments/comment-dialog/subcomponents/priority-dropdown)
* [Status Dropdown](/ui-customization/features/async/comments/comment-dialog/subcomponents/status-dropdown)
* [Options Dropdown](/ui-customization/features/async/comments/comment-dialog/subcomponents/options-dropdown)
* [Reaction Tool](/ui-customization/features/async/comments/comment-dialog/subcomponents/reaction-tool)
* [Reaction Pin](/ui-customization/features/async/comments/comment-dialog/subcomponents/reaction-pin)
* [Reactions Panel](/ui-customization/features/async/comments/comment-dialog/subcomponents/reactions-panel)
* [Reactions Pin Tooltip](/ui-customization/features/async/comments/comment-dialog/subcomponents/reaction-pin-tooltip)
* [Autocomplete Option](/ui-customization/features/async/comments/comment-dialog/subcomponents/autocomplete-option)
* [Autocomplete Chip Tooltip](/ui-customization/features/async/comments/comment-dialog/subcomponents/autocomplete-chip-tooltip)
## Added support to set custom reactions
You can [set custom reactions](/async-collaboration/comments/customize-behavior/multimedia) by passing a map that contains information about the reactions you want to add.
The map keys should be the reaction ID, and the map value should contain an object with either an `url`, `svg`, or `emoji` field to represent the reaction icon you want to use.
```jsx theme={null}
const customReactions = {
"URL_EMOJI_ID": {
"url": "EMOJI_URL"
},
"SVG_EMOJI_ID": {
"svg": "EMOJI_SVG"
},
"TEXT_EMOJI_ID": {
"emoji": "🤣" // emoji as a text
}
};
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
const customReactions = {
"URL_EMOJI_ID": {
"url": "EMOJI_URL"
},
"SVG_EMOJI_ID": {
"svg": "EMOJI_SVG"
},
"TEXT_EMOJI_ID": {
"emoji": "🤣" // emoji as a text
}
}
commentElement.setCustomReactions(customReactions);
```
## Changed type in VeltCommentDialogWireframe.Composer.ActionButton from `file` to `attachments`. Keeping legacy support for `file`.
In the [Comment Dialog Wireframe](/ui-customization/features/async/comments/comment-dialog-components), we changed the type from `file` to `attachments`
` ` -> ` `
## Added support for customizing attachments in Comment Dialog
The `VeltCommentDialogWireframe.Composer.Attachments` and `VeltCommentDialogWireframe.ThreadCard.Attachments` subcomponents within the [Comment Dialog Wireframe](/ui-customization/features/async/comments/comment-dialog-components) now support customization.
## Added method listen to Comment Selection changes.
The [onCommentSelectionChange()](/async-collaboration/comments/customize-behavior#event-subscription) method can be used to listen to Comment selection changes.
```jsx theme={null}
const onCommentSelectionChange = (data) => {
console.log('onCommentSelectionChange', data);
}
onCommentSelectionChange(data)} />
```
Callback response schema:
```jsx theme={null}
export class CommentSelectionChangeData {
selected!: boolean;
annotation!: CommentAnnotation;
}
```
API Methods:
```jsx theme={null}
const commentElement = client.getCommentElement();
let subscription = commentElement.onCommentSelectionChange().subscribe((data) => {
console.log('onCommentSelectionChange: ', data);
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
Using Hooks:
The `useCommentSelectionChangeHandler` hook can be used to subscribe to Comment selection changes.
```jsx theme={null}
import React, { useEffect } from 'react';
import { useCommentSelectionChangeHandler } from '@veltdev/react';
function YourComponent() {
const commentSelectionChange = useCommentSelectionChangeHandler();
useEffect(() => {
console.log('commentSelectionChange', commentSelectionChange);
}, [commentSelectionChange]);
return (
<>
Selected Comment: {commentSelectionChange.annotation.id}
>
);
}
```
## Added prop to enable or disable Comment Pin Highlighter
The API Methods already existed, but we added a prop to enable or disable the [Comment Pin Highlighter](/async-collaboration/comments/customize-behavior/ui-controls)
```jsx theme={null}
// React
// API method was already added before, adding here just for refernece purpose
const commentElement = client.getCommentElement();
// To enable comment pin highlighter
commentElement.enableCommentPinHighlighter();
// To disable comment pin highlighter
commentElement.disableCommentPinHighlighter();
```
## Added flag to merge location in `updateLocation` cloud function
You can [update a Location's object fields](/key-concepts/locations/setup/api-update-location) while keeping the location id the same using an api call.
Set the `merge` flag to `true` if you want to merge the new `location` fields into the old `location` fields.
Set the flag to `false` if you want the new `location` object to completely replace the old `location` object.
```jsx theme={null}
{
"data": {
"apiKey": "YOUR_API_KEY",
"authToken": "YOUR_AUTH_TOKEN",
"documentId": "YOUR_DOCUMENT_ID",
"migrate": {
"oldLocation": YOUR_OLD_LOCATION_OBJECT_HERE,
"newLocation": YOUR_NEW_LOCATION_OBJECT_HERE
},
"merge" : true
}
}
```
# May 29 2024
Source: https://docs.velt.dev/release-notes/archive/may-29-2024
## Versions
* Latest SDK: [1.0.137](https://www.npmjs.com/package/@veltdev/react)
* Latest Types: [1.0.157](https://www.npmjs.com/package/@veltdev/types)
## Simplified way to modify wireframe subcomponents
You can now modify subcomponent wireframes using two patterns:
#### a. `Parentless` - Modifying the subcomponent without its parent within the ` ` component. (recommended)
In this example, we modify the Header subcomponent, which is a subcomponent of the Velt Comment Dialog. In this pattern, we just put the Header subcomponent in the root of ` ` and modify it. We do not need to add its parent component or any of its siblings.
Example:
```jsx theme={null}
Custom HTML
```
#### a. `With Parent` - Modifying the subcomponent within the context of its parent within the ` ` component. (not recommended)
In this example, we modify the Header subcomponent, which is a subcomponent of the Velt Comment Dialog component. In this pattern, we include its parent component and siblings. This makes it easier to modify several sibling components at once.
Example:
```jsx theme={null}
{/* Skeleton */}
...
{/* Header */}
Custom HTML
{/* Filter */}
...
{/* List */}
{/* Empty Placeholder */}
...
{/* Page Mode Composer */}
...
```
If you modify the component in both the `Parentless` and `With Parent` pattern, the `With Parent` pattern will override the `Parentless` pattern.
## Detect if Velt SDK is initialized
[To detect if the Velt SDK is initialized](/get-started/advanced), subscribe using the following method:
```jsx theme={null}
let subscription = client.getVeltInitState().subscribe((veltInitState: boolean | undefined) => {
console.log('Velt Init State:', veltInitState);
});
```
To unsubscribe from the subscription:
```jsx theme={null}
subscription?.unsubscribe()
```
You can also the use `useVeltInitState()` hook:
```jsx theme={null}
import { useVeltInitState } from '@veltdev/react';
function YourComponent() {
const veltInitState = useVeltInitState();
useEffect(() => {
console.log('Velt Init State:', veltInitState);
if (veltInitState) {
// Velt state is initialized, so user can perform any action here
}
}, [veltInitState]);
}
```
# BlockNote CRDT Library
Source: https://docs.velt.dev/release-notes/version-4/blocknote-changelog
Release Notes of Changes Affecting Velt BlockNote Library
### Libraries
* `@veltdev/blocknote-crdt`
* `@veltdev/blocknote-crdt-react`
### Improvements
* \[**Core**]: Improved the API signatures and naming to improve developer experience.
### Improvements
* \[**Core**]: Released a purpose built react package (`@veltdev/blocknote-crdt-react`) that reduced the implementation code by 95%.
### Bug Fixes
* \[**Core**]: Fixed an issue where last keystroke was not synced in some cases.
### New Features
* \[**Core**]: Introduced purpose built CRDT library for BlockNote Editor to enable multiplayer editing. This is based on the [Yjs](https://docs.yjs.dev/) library. [Learn more](/realtime-collaboration/crdt/setup/blocknote).
# CodeMirror CRDT Library
Source: https://docs.velt.dev/release-notes/version-4/codemirror-changelog
Release Notes of Changes Affecting Velt CodeMirror Library
### Libraries
* `@veltdev/codemirror-crdt`
* `@veltdev/codemirror-crdt-react`
### Improvements
* \[**Core**]: Improved CRDT store initialization by using `veltInitState` for streamlined internal store creation.
### Bug Fixes
* \[**Core**]: Fixed multi-tab synchronization for same user. When the same user edits content in multiple tabs, all tabs now sync correctly.
### Improvements
* \[**Core**]: Released stable version 4.5.8 of CodeMirror CRDT packages.
### New Features
* \[**Developer Tools**]: Added `window.VeltCrdtStoreMap` global interface to inspect and monitor CRDT stores during development. Access store values directly in the browser console using `VeltCrdtStoreMap.get(id)` or `VeltCrdtStoreMap.getAll()`. Subscribe to store updates and monitor registration events for debugging collaborative data synchronization. [Learn more](/realtime-collaboration/crdt/setup/core#veltcrdtstoremap)
### Bug Fixes
* \[**Core**]: Fixed `initialContent` not being applied when no server-side data exists. You can now set `initialContent` in CodeMirror CRDT, and it will be used when the document is empty.
```jsx theme={null}
const { store, isLoading } = useVeltCodeMirrorCrdtExtension({
editorId: "UNIQUE_EDITOR_ID",
initialContent: "body { background-color: lightgrey; }",
});
```
```javascript theme={null}
const result = veltClient.getVeltCodeMirrorCrdtExtension({
editorId: "UNIQUE_EDITOR_ID",
initialContent: "body { background-color: lightgrey; }",
});
```
### Improvements
* \[**Core**]: Improved the API signatures and naming to improve developer experience.
### Improvements
* \[**Core**]: Released a purpose built react package (`@veltdev/codemirror-crdt-react`) that reduced the implementation code by 95%.
### Bug Fixes
* \[**Core**]: Fixed an issue where last keystroke was not synced in some cases.
### Improvements
* \[**Awareness**]: Adding support for awareness enabling features like live cursors and selection highlighting.
### New Features
* \[**Core**]: Introduced purpose built CRDT library for CodeMirror Editor to enable multiplayer editing. This is based on the [Yjs](https://docs.yjs.dev/) library. [Learn more](/realtime-collaboration/crdt/setup/codemirror).
# Core CRDT Library
Source: https://docs.velt.dev/release-notes/version-4/crdt-core-changelog
Release Notes of Changes Affecting All CRDT Libraries
### Libraries
* `@veltdev/crdt`
* `@veltdev/crdt-react`
### Improvements
* \[**Core**]: Improved CRDT store initialization by replacing user-based detection with `veltInitState`. Streamlines internal store creation logic.
### Bug Fixes
* \[**Core**]: Fixed multi-tab synchronization for same user. When the same user edits content in multiple tabs, all tabs now sync correctly.
### Improvements
* \[**Core**]: Released stable version 4.5.8 of all CRDT packages (except BlockNote packages). This version includes Tiptap v3 support and resolves the initialContent issue in tiptap-crdt-react.
### New Features
* \[**Developer Tools**]: Added `window.VeltCrdtStoreMap` global interface to inspect and monitor CRDT stores during development. Access store values directly in the browser console using `VeltCrdtStoreMap.get(id)` or `VeltCrdtStoreMap.getAll()`. Subscribe to store updates and monitor registration events for debugging collaborative data synchronization. [Learn more](/realtime-collaboration/crdt/setup/core#veltcrdtstoremap)
```jsx theme={null}
// Get a specific store
const storeInstance = window.VeltCrdtStoreMap.get('my-store-id');
if (storeInstance) {
const currentValue = storeInstance.getValue();
console.log('Current store value:', currentValue);
// Subscribe to store changes
const unsubscribe = storeInstance.subscribe((newValue) => {
console.log('Store updated:', newValue);
});
}
// Get all registered stores
const allStores = window.VeltCrdtStoreMap.getAll();
console.log('All stores:', allStores);
// Access the stores map directly
const storesMap = window.VeltCrdtStoreMap.stores;
console.log('Stores map:', storesMap);
// Listen for store registration events
window.addEventListener('veltCrdtStoreRegister', (event) => {
console.log('Store registered:', event.detail);
});
// Listen for store unregistration events
window.addEventListener('veltCrdtStoreUnregister', (event) => {
console.log('Store unregistered:', event.detail);
});
```
```html theme={null}
```
### Bug Fixes
* \[**Core**]: Fixed `initialContent` not being applied when no server-side data exists. You can now set initial content in CRDT libraries, and it will be used when the document is empty.
### Bug Fixes
* \[**Core**]: Fixed CRDT synchronization issue affecting document changes in Velt CRDT SDK.
### Bug Fixes
* \[**Core**]: Fixed an issue with where versions were not being saved correctly in some cases.
### Bug Fixes
* \[**Core**]: Fixed an issue where last keystroke was not synced in some cases. Also fixed synchronization issues with React Flow nodes and edges.
### New Features
* \[**Security**]: Enhanced security for CRDTs with a new `encryptionProvider`. This allows you to provide custom encryption and decryption methods to secure your collaborative data.
* See [Custom Encryption](/realtime-collaboration/crdt/setup/core#custom-encryption) for setup instructions.
### New Features
* \[**Core**]: Introduced versioning support for CRDT stores. You can now save snapshots of your collaborative data, list previously saved versions, and restore the store to a specific version. This feature is currently supported for `text` and `array` data types.
### New Features
* \[**Core**]: Introduced our latest CRDT Libraries based on Yjs:
* `@veltdev/crdt`: Framework-agnostic CRDT stores (array, map, text, xml), versioning, subscriptions, and syncing via the Velt client.
* `@veltdev/crdt-react`: React Hook wrapper for CRDT integration.
* `@veltdev/tiptap-crdt`: Purpose built CRDT library for Tiptap Editor to enable multiplayer editing.
# Lexical Changelog
Source: https://docs.velt.dev/release-notes/version-4/lexical-changelog
Release Notes of Changes Affecting Velt Lexical Library
### Improvements
* \[**Comments**]: Refactored `@veltdev/lexical-velt-comments` to follow a common pattern and folder structure across all libraries. This enables faster development of Velt comment integrations and covers more rich text editor scenarios.
### New Features
* \[**Lexical**]: Released documentation for our Lexical CRDT integration. Also added a new `exportJSONWithoutComments` utility to serialize editor content without including comment nodes.
```javascript theme={null}
import { exportJSONWithoutComments } from '@veltdev/lexical-velt-comments';
const cleanState = exportJSONWithoutComments(editor);
```
# React Flow CRDT Library
Source: https://docs.velt.dev/release-notes/version-4/reactflow-changelog
Release Notes of Changes Affecting Velt React Flow Library
### Libraries
* `@veltdev/reactflow-crdt`
### Improvements
* \[**Core**]: Fixed an issue with the ReactFlow CRDT production build where certain test cases were failing. All test cases now pass correctly in production builds.
### Bug Fixes
* \[**Core**]: Fixed multi-tab synchronization for same user. When the same user edits content in multiple tabs, all tabs now sync correctly.
### Improvements
* \[**Core**]: Released stable version 4.5.8 of ReactFlow CRDT package.
### New Features
* \[**Developer Tools**]: Added `window.VeltCrdtStoreMap` global interface to inspect and monitor CRDT stores during development. Access store values directly in the browser console using `VeltCrdtStoreMap.get(id)` or `VeltCrdtStoreMap.getAll()`. Subscribe to store updates and monitor registration events for debugging collaborative data synchronization. [Learn more](/realtime-collaboration/crdt/setup/core#veltcrdtstoremap)
### Bug Fixes
* \[**Core**]: Fixed `initialContent` not being applied when no server-side data exists. You can now set `initialEdges` and `initialNodes` in ReactFlow CRDT, and they will be used when the document is empty.
```jsx theme={null}
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useVeltReactFlowCrdtExtension({
editorId: "UNIQUE_EDITOR_ID",
initialEdges: [
{
id: "edge1",
source: "node1",
target: "node2",
},
],
initialNodes: [
{
id: "node1",
type: 'input',
data: { label: `Node 1` },
position: { x: 0, y: 50 },
},
{
id: "node2",
type: 'default',
data: { label: `Node 2` },
position: { x: 0, y: 150 },
}
],
});
```
```javascript theme={null}
const result = veltClient.getVeltReactFlowCrdtExtension({
editorId: "UNIQUE_EDITOR_ID",
initialEdges: [
{
id: "edge1",
source: "node1",
target: "node2",
},
],
initialNodes: [
{
id: "node1",
type: 'input',
data: { label: `Node 1` },
position: { x: 0, y: 50 },
},
{
id: "node2",
type: 'default',
data: { label: `Node 2` },
position: { x: 0, y: 150 },
}
],
});
```
### New Features
* \[**Core**]: Optimized the library to reduce the implementation code by 95%.
### Bug Fixes
* \[**Core**]: Fixed an issue where last keystroke was not synced in some cases. Also fixed synchronization issues with React Flow nodes and edges.
### New Features
* \[**Core**]: Introduced purpose built CRDT library for React Flow Editor to enable multiplayer editing. This is based on the [Yjs](https://docs.yjs.dev/) library. [Learn more](/realtime-collaboration/crdt/setup/reactflow).
# Velt SDK Changelog
Source: https://docs.velt.dev/release-notes/version-4/sdk-changelog
Release Notes of changes added to the core Velt SDK
### Libraries
* `@veltdev/react`
* `@veltdev/client`
* `@veltdev/sdk`
### Bug Fixes
* \[**Comments**]: Fixed sidebar comment navigation scrolling the page unexpectedly. The window scroll position is now captured and restored after `scrollIntoView()` runs, so only the sidebar panel scrolls and the page remains at its current position.
### Bug Fixes
* \[**Comments**]: Fixed sidebar not auto-exiting focused thread mode when the annotation is deselected. The sidebar now automatically exits focused thread mode and returns to the "all comments" view when the focused annotation is deselected.
### New Features
* \[**Comments**]: Added `attachmentDownloadClicked` event and methods to control automatic attachment downloads. You can now intercept download clicks to implement custom download behavior such as analytics tracking, custom URLs, or access control checks.
```jsx theme={null}
// Using Hooks
import { VeltComments, useCommentUtils } from '@veltdev/react';
// Disable automatic downloads via component prop
// Using API methods
const commentElement = client.getCommentElement();
// Control automatic downloads
commentElement.disableAttachmentDownload();
commentElement.enableAttachmentDownload();
// Listen to download click events
commentElement.on('attachmentDownloadClicked').subscribe((event) => {
console.log('Attachment clicked:', event.attachment);
console.log('Annotation ID:', event.annotationId);
});
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Moved attachment loading indicator to a parent wrapper element for better CSS targeting. Custom CSS targeting `.loading` on attachment elements should be updated to target `.velt-composer-attachment--loading` on `.velt-composer-attachment-container`.
### Bug Fixes
* \[**Comments**]: Fixed unintended draft auto-save when navigating back from a focused thread view.
* \[**Comments**]: Fixed sidebar not deselecting annotations when navigating back from a focused thread.
* \[**Comments**]: Fixed thread card attachments not falling back to default rendering when an empty wireframe template is provided.
* \[**Comments**]: Fixed edit composer not rendering when custom thread card wireframe omits the edit composer wireframe. The default edit composer now renders automatically.
### Bug Fixes
* \[**Comments**]: Fixed `ADD_COMMENT` event not firing for page comment annotations. Event listeners subscribed to `ADD_COMMENT` now receive callbacks consistently for all comment creation flows, including page comments.
### Improvements
* \[**Comments**]: Added a `readOnly` prop to `VeltCommentComposer` and `VeltInlineCommentsSection` components that allows disabling comment input at the component level. Developers can now create mixed read/write interfaces where some comment areas are locked while others are interactive.
```jsx theme={null}
// Using Hooks
import { VeltCommentComposer, VeltInlineCommentsSection } from '@veltdev/react';
// Using API methods
const commentElement = client.getCommentElement();
commentElement.enableReadOnlyMode();
```
```html theme={null}
```
* \[**Comments**]: A new edit composer wireframe for the comment dialog lets you customize the edit composer UI within comment thread cards.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: You can associate comments created via the page mode composer with specific elements using the `targetElementId` property.
```jsx theme={null}
// Using Hooks
import { useCommentUtils } from '@veltdev/react';
const commentElement = useCommentUtils();
commentElement.setContextInPageModeComposer({
context: { projectId: 'abc123' },
targetElementId: 'my-target-element'
});
// Using API methods
const commentElement = client.getCommentElement();
commentElement.setContextInPageModeComposer({
context: { projectId: 'abc123' },
targetElementId: 'my-target-element'
});
```
```html theme={null}
```
* \[**Comments**]: Added `CommentAnnotation.resolvedByUser` property to expose the full `User` object so you can access it without additional API calls.
* \[**Comments**]: `setAssignToType()` now accepts an `AssignToConfig` object for consistent API patterns.
```jsx theme={null}
// Using Hooks
const commentElement = useCommentUtils();
commentElement.setAssignToType({ type: 'dropdown' });
// or
commentElement.setAssignToType({ type: 'checkbox' });
// Using API methods
const commentElement = client.getCommentElement();
commentElement.setAssignToType({ type: 'dropdown' });
```
```html theme={null}
```
* \[**Comments**]: Added `targetElementId` to `CommentToolClickEvent` so you can identify which element was targeted when users initiate a comment.
```jsx theme={null}
// Using Hooks
const commentToolClickEvent = useCommentEventCallback('commentToolClick');
useEffect(() => {
if (commentToolClickEvent) {
console.log('Context:', commentToolClickEvent.context);
console.log('Target Element ID:', commentToolClickEvent.targetElementId);
}
}, [commentToolClickEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('commentToolClick').subscribe((event) => {
console.log('Target Element ID:', event.targetElementId);
});
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed cursor position jumping when clicking autocomplete tool buttons so that @ mention and other autocomplete triggers now insert at the correct cursor position.
* \[**Comments**]: Fixed actions menu visibility in comment dialog so the menu on the first thread card remains visible when the assignment dropdown is opened.
* \[**Comments**]: Added "Assign" tooltip to the assignment button in comment dialog thread cards.
* \[**Comments**]: Added `velt-reaction-pin--no-reactions` CSS class to empty reaction pins for custom styling.
* \[**Comments**]: Fixed autocomplete panel viewport height calculation to respect custom `itemSize` from `autoCompleteScrollConfig`.
* \[**Comments**]: Fixed attachment upload failures by using fallback metadata values from document paths and config service.
* \[**Comments**]: Fixed read-only state management so local `readOnly` props are no longer overridden by global read-only state changes.
### New Features
* \[**Comments**]: Added Thread card assign button wireframe to customize the assign button that appears on individual comment thread cards.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Added a CSS class to indicate the selected state on the assign dropdown checkbox.
### Bug Fixes
* \[**Comments**]: Fixed "Assign To" label capitalization to "Assign to" for proper sentence case.
### New Features
* \[**Comments**]: Added ability to set context data and focus the page mode composer programmatically using `setContextInPageModeComposer()` and `focusPageModeComposer()` methods.
```jsx theme={null}
// Using Hooks
import { useCommentUtils } from '@veltdev/react';
const commentElement = useCommentUtils();
// Set context data for the page mode composer
commentElement.setContextInPageModeComposer({
documentId: 'doc-123',
sectionId: 'section-456',
customData: 'any-value'
});
// Clear the context
commentElement.setContextInPageModeComposer(null);
// Focus the page mode composer input
commentElement.focusPageModeComposer();
// Using API methods
const commentElement = client.getCommentElement();
commentElement.setContextInPageModeComposer({ documentId: 'doc-123' });
commentElement.focusPageModeComposer();
```
```html theme={null}
```
* \[**Comments**]: Added a new `commentToolClick` event that fires when the comment tool button is clicked.
```jsx theme={null}
// Using Hooks
const commentToolClickEvent = useCommentEventCallback('commentToolClick');
useEffect(() => {
if (commentToolClickEvent) {
console.log('Comment tool clicked');
console.log('Context:', commentToolClickEvent.context);
console.log('Target Element ID:', commentToolClickEvent.targetElementId);
}
}, [commentToolClickEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('commentToolClick').subscribe((event) => {
console.log('Context:', event.context);
});
```
```html theme={null}
```
* \[**Comments**]: Added a new `sidebarButtonClick` event that fires when the sidebar button is clicked.
```jsx theme={null}
// Using Hooks
const sidebarButtonClickEvent = useCommentEventCallback('sidebarButtonClick');
useEffect(() => {
if (sidebarButtonClickEvent) {
console.log('Sidebar button clicked');
}
}, [sidebarButtonClickEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('sidebarButtonClick').subscribe((event) => {
console.log('Metadata:', event.metadata);
});
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed `reactionId` prop on `VeltCommentDialogWireframe.ThreadCard.ReactionPin` not converting to dashed-case format when passed to the underlying HTML element.
### New Features
* \[**Comments**]: Added ability to configure the assign-to functionality to use either a dropdown or checkbox mode for assigning users to comments.
```jsx theme={null}
// Using component
import { VeltComments } from '@veltdev/react';
// Using API method
const commentElement = client.getCommentElement();
commentElement.setAssignToType('checkbox'); // or 'dropdown'
```
```html theme={null}
```
* \[**Comments**]: Context in page mode composer—pass context data to the page mode composer when opening the comment sidebar via the comment tool.
```jsx theme={null}
// Using component
import { VeltCommentTool } from '@veltdev/react';
// Using API methods
const commentElement = client.getCommentElement();
commentElement.enableContextInPageModeComposer();
commentElement.disableContextInPageModeComposer();
```
```html theme={null}
```
* \[**Comments**]: Added `clearPageModeComposerContext()` method to clear context data from page mode composer. This is useful when you are using sidebar in embed mode and have to clear the context from page mode composer manually.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.clearPageModeComposerContext();
```
```html theme={null}
```
* \[**Comments**]: Reaction pin component—display specific reaction pins within comment thread cards using the new `VeltCommentDialogThreadCardReactionPin` wireframe component.
```jsx theme={null}
import { VeltCommentDialogWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**Comments**]: Exclude reactions from thread cards—exclude specific reactions from being displayed using `excludeReactionIds` on `VeltCommentDialogThreadCardReactions` and `VeltCommentDialogThreadCardReactionTool` components.
```jsx theme={null}
import { VeltCommentDialogWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**Comments**]: Assigned to me filter—filter comments assigned to the current user using the new filter option in the comment sidebar minimal filter dropdown.
```jsx theme={null}
import { VeltCommentsSidebarWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**Comments**]: Batched per-document comment counts—added `batchedPerDocument` and `debounceMs` properties to `CommentRequestQuery`. This feature was documented in v5.0.0-beta.10 and has been backported to v4.x.
### Bug Fixes
* \[**Comments**]: Fixed `COMPOSER_TEXT_CHANGE` event not triggering when content is pasted into the composer.
### New Features
* \[**Comments**]: Renamed `targetElementId` prop to `targetComposerElementId` on `VeltCommentComposer`.
```jsx theme={null}
// Before
// After
```
```html theme={null}
```
* \[**Comments**]: Changed `submitComment()` to accept request object instead of plain string. Update calls from `submitComment(targetElementId)` to `submitComment({ targetComposerElementId })`.
```jsx theme={null}
// Before
const commentElement = client.getCommentElement();
commentElement.submitComment('my-composer');
// After
const commentElement = client.getCommentElement();
commentElement.submitComment({ targetComposerElementId: 'my-composer' });
```
```html theme={null}
```
* \[**Comments**]: Added `clearComposer()` method to reset composer state including text, attachments, recordings, tagged users, assignments, and custom lists.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.clearComposer({ targetComposerElementId: 'my-composer' });
```
```html theme={null}
```
* \[**Comments**]: Added `getComposerData()` method to fetch current composer state on-demand. Returns the same data structure as the `COMPOSER_TEXT_CHANGE` event.
```jsx theme={null}
const commentElement = client.getCommentElement();
const composerData = commentElement.getComposerData({
targetComposerElementId: 'my-composer'
});
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Enhanced `composerTextChange` event to include full draft `annotation` object and `targetComposerElementId`. Access attachments, recordings, tagged users, and other composer state in real-time.
```jsx theme={null}
// Using Hooks
import { useCommentEventCallback } from '@veltdev/react';
import { useEffect } from 'react';
const composerTextChangeEvent = useCommentEventCallback('composerTextChange');
useEffect(() => {
if (composerTextChangeEvent) {
console.log('Text:', composerTextChangeEvent.text);
console.log('Annotation:', composerTextChangeEvent.annotation);
console.log('Composer ID:', composerTextChangeEvent.targetComposerElementId);
}
}, [composerTextChangeEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('composerTextChange').subscribe((event) => {
console.log('composerTextChange event:', event);
});
```
```html theme={null}
```
### New Features
* \[**Comments**]: Added `submitComment()` method to programmatically trigger comment submission. Trigger submission from custom buttons or keyboard shortcuts instead of relying on the built-in submit button.
```jsx theme={null}
// Using Hooks
// Using API methods
const commentElement = client.getCommentElement();
commentElement.submitComment('composer-1');
```
```html theme={null}
Send
```
* \[**Comments**]: Added `placeholder` prop to comment composer. Customize placeholder text to match your application context.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `composerTextChange` event to track text changes in comment composers. Track input for features like auto-save, character counters, or real-time validation.
```jsx theme={null}
// Using Hooks
import { useCommentEventCallback } from '@veltdev/react';
import { useEffect } from 'react';
const composerTextChangeEvent = useCommentEventCallback('composerTextChange');
useEffect(() => {
if (composerTextChangeEvent) {
console.log('composerTextChange: ', composerTextChangeEvent);
}
}, [composerTextChangeEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('composerTextChange').subscribe((event) => {
console.log('Text changed:', event.text);
});
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed context issue for manual comment pins. Ensures manual comment pins display correctly across all integrations.
### Bug Fixes
* \[**Self-hosting**]: Fixed attachment metadata issue where `organizationId` was missing. Improves debugging for self-hosted attachment handling.
### Improvements
* \[**Core**]: Improved mutation observer element detection logic to skip animation-related elements. Prevents performance slowdowns caused by continuous animations.
* \[**Comments**]: Optimized read status handling to skip marking already-read comment annotations as read again.
### Bug Fixes
* \[**Comments**]: Fixed bottom sheet not closing properly after deleting a comment annotation.
### New Features
* \[**Self-hosting**]: Added attachment resolver support for config-based endpoints. Configure `saveConfig` and `deleteConfig` URLs for file uploads and deletions. [Learn more](/self-host-data/attachments)
```jsx theme={null}
// Using VeltProvider
'
// Note: Content-Type is automatically set by browser
}
},
deleteConfig: {
url: '/api/attachments/delete',
headers: {
'Content-Type': 'application/json'
}
}
}
}
}}>
{/* Your app content */}
// Using API method
const client = useVeltClient();
client.setDataProviders({
attachment: {
config: {
saveConfig: {
url: '/api/attachments/save',
headers: {
'Authorization': 'Bearer '
}
},
deleteConfig: {
url: '/api/attachments/delete',
headers: {
'Content-Type': 'application/json'
}
}
}
}
});
```
```js theme={null}
Velt.setDataProviders({
attachment: {
config: {
saveConfig: {
url: '/api/attachments/save',
headers: {
'Authorization': 'Bearer '
}
},
deleteConfig: {
url: '/api/attachments/delete',
headers: {
'Content-Type': 'application/json'
}
}
}
}
});
```
**File Upload Format:**
When using `saveConfig`, file uploads use `multipart/form-data`:
```typescript theme={null}
// Request
Method: POST
Content-Type: multipart/form-data (set by browser)
// Form fields:
{
file: File, // Binary file object
request: { // JSON string
"attachment": {
"attachmentId": number,
"name": string,
"mimeType": string
},
"metadata": { ... },
"event": string // Optional
}
}
// Response format:
{
data: {
url: string // URL of uploaded attachment
},
success: true,
statusCode: 200
}
```
**Backward Compatibility:**
* Existing custom `save()` and `delete()` methods continue to work
* If `saveConfig` or `deleteConfig` are not provided, the resolver falls back to provider methods
* No breaking changes to existing implementations
### Bug Fixes
* \[**Notifications**]: Fixed notification loading issue that affected rendering.
* \[**Comments**]: Fixed read status not updating when comment dialog opened in bottom sheet.
### New Features
* \[**CRDT**]: Added [Get CRDT Data](/api-reference/rest-apis/v2/crdt/get-crdt-data) REST API to retrieve editor data. Fetch all editors in a document or target a specific editor by ID. [Learn more](/realtime-collaboration/crdt/setup/core#backend-rest-apis)
* \[**Self-hosting**]: New Config-Based DataProviders: We've reduced self-hosting implementation overhead by **90%**.
* **Frontend**: Simply configure your URLs and headers; the SDK handles the requests automatically.
* **Backend**: Use the new [**Velt Python SDK**](https://pypi.org/project/velt-py) to pass your DB and storage configs. You can now pass the raw request object directly to the SDK methods and return the resulting response object straight to the client—no manual processing required.
```jsx theme={null}
// Using VeltProvider
{/* Your app content */}
// Using API method
const client = useVeltClient();
client.setDataProviders({
reaction: {
config: {
resolveTimeout: 2000,
getConfig: {
url: '/api/reactions/get',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
}
},
saveConfig: {
url: '/api/reactions/save'
// headers default to Content-Type: application/json
},
deleteConfig: {
url: '/api/reactions/delete'
}
}
}
});
```
```js theme={null}
Velt.setDataProviders({
comment: {
config: {
resolveTimeout: 20000,
saveRetryConfig: {
retryCount: 3,
retryDelay: 2000
},
getConfig: {
url: '/api/comments/get',
headers: {
'Authorization': 'Bearer your-token'
}
},
saveConfig: {
url: '/api/comments/save',
headers: {
'Authorization': 'Bearer your-token'
}
},
deleteConfig: {
url: '/api/comments/delete',
headers: {
'Authorization': 'Bearer your-token'
}
}
}
// get, save, delete methods are optional when configs are provided
}
});
Velt.setDataProviders({
user: {
config: {
resolveUsersConfig: {
organization: false,
folder: false,
document: true
},
getConfig: {
url: '/api/users/get',
headers: {
'Authorization': 'Bearer your-token'
}
}
}
// get method is optional when getConfig is provided
}
});
```
**Backward Compatibility:**
* Fully backward compatible - existing implementations continue working unchanged
* Custom `get`, `save`, `delete` methods remain supported
* Configs are optional - if not provided, SDK uses provider methods
* User resolver's `get` method supports both old and new response formats
**Notes:**
* All requests use POST method with JSON body
* Response format automatically handled by SDK
* Retry logic (if configured) applies to config-based requests
### Improvements
* \[**Notifications**]: Added dropdown layout option for notification settings. Choose between accordion or dropdown layouts.
```jsx theme={null}
```
```html theme={null}
```
**Type:**
```typescript theme={null}
settingsLayout: 'accordion' | 'dropdown' // Default: 'accordion'
```
### Bug Fixes
* \[**Comments**]: Fixed comment dialog not changing read status in bottom sheet. Dialog now updates read status when opened.
### Improvements
* \[**Comments**]: Added `commentPlaceholder`, `replyPlaceholder`, and `composerPlaceholder` props to inline comments section. Customize placeholder text for different input fields.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `commentPlaceholder`, `replyPlaceholder`, and `pageModePlaceholder` props to sidebar. Customize placeholder text for sidebar input fields.
```jsx theme={null}
```
```html theme={null}
```
* \[**Core**]: Previous document now set on organization change. When `identify()` is called, the previous document is set unless `setDocuments()` is explicitly called.
### Bug Fixes
* \[**Comments**]: Fixed sidebar opening with previously selected inline comment annotation. Sidebar now opens in fresh state.
* \[**Notifications**]: Fixed notification messages not rendering. Messages now display correctly.
### New Features
* \[**Comments**]: Added `autoCompleteScrollConfig` to customize virtual scroll behavior in autocomplete. Configure scroll parameters like item size and buffer pixels.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `forceClose`, `sortOrder`, `sortBy`, and `defaultMinimalFilter` props to sidebar. Control sidebar behavior and default filtering options.
```jsx theme={null}
```
```html theme={null}
```
* \[**Notifications**]: Added `pageSize` prop to control initial notification load count in NotificationPanel and NotificationTool.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Added `elementId` support in `/v2/commentannotations/add` REST API. Pass `elementId` in `targetElement` to define text search boundaries for text comments.
```json theme={null}
{
"data": {
"organizationId": "yourOrganizationId",
"documentId": "yourDocumentId",
"commentAnnotations": [
{
"targetElement": {
"elementId": "yourElementId",
"targetText": "Your Target Text",
"occurrence": 1
},
"commentData": [
{
"commentText": "Sample Comment",
"commentHtml": "Sample Comment
",
"from": {
"userId": "yourUserId",
"name": "yourUserName",
"email": "yourUserEmail"
}
}
]
}
]
}
}
```
* \[**Comments**]: Context data now passed from sidebar and inline views to dialog. Access context in `velt-if` and `velt-data` directives.
* \[**Notifications**]: Truncated long notification text to 3 lines to prevent layout issues.
* \[**Notifications**]: Added `velt-confirm-dialog-overlay-pane` class to confirm dialog overlay for targeted styling.
### Bug Fixes
* \[**Comments**]: Fixed script tag content being included in text search for highlighting. Text comments now ignore script content during search.
* \[**Comments**]: Fixed tooltip visibility in sidebar due to z-index conflict.
* \[**Comments**]: Fixed autocomplete chip height issue. Added `display: block` to prevent scroll issues in thread cards.
* \[**Comments**]: Fixed edit mode not persisting when switching to focused thread mode. Edit state now maintained correctly.
* \[**Notifications**]: Fixed `velt-notifications-panel-settings-wireframe` mapping that was incorrectly configured.
### Improvements
* \[**Access Control**]: Added `context` field in document type during `setNotifications` call in PermissionProvider. Provides additional context data for better debugging.
* \[**CRDT**]: Added CRDT data in Velt Console Data tab. Helps with debugging and reviewing data in the CRDT store for improved developer visibility.
### Improvements
* \[**Access Control**]: Added polyfill for `Promise.allSettled`. Ensures compatibility when external dependencies override native implementations.
* \[**Access Control**]: Fixed response payload for `resourceAccessResultFromCache` in Provider. Now follows the same payload pattern as other methods for better debugging.
### Bug Fixes
* \[**Comments**]: Fixed a minor bug related to adding comments in focused mode.
### New Features
* \[**Comments**]: Added context, locationId, documentId, and folderId props to standalone Composer component. Pass organizational identifiers directly to the composer.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `openAnnotationInFocusMode` config to open comment dialog in focus mode when `focusedThreadMode` is enabled and reply button is clicked or comment is selected via `selectCommentByAnnotationId()`. Requires `focusedThreadMode` to be enabled.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Self-hosting**]: Added `moduleName` to resolver `on()` method for improved debugging. Identifies which module triggered the resolver call.
```typescript theme={null}
export enum UserResolverModuleName {
IDENTIFY = 'identify/authProvider',
GET_TEMPORARY_USERS = 'getTemporaryUsers',
GET_USERS = 'getUsers',
GET_HUDDLE_USERS = 'getHuddleUsers',
GET_SINGLE_EDITOR_USERS = 'getSingleEditorUsers',
}
export enum CommentResolverModuleName {
SET_DOCUMENTS = 'setDocuments',
GET_COMMENT_ANNOTATIONS = 'getCommentAnnotations',
GET_NOTIFICATIONS = 'getNotifications',
}
export enum ReactionResolverModuleName {
SET_DOCUMENTS = 'setDocuments',
GET_REACTION_ANNOTATIONS = 'getReactionAnnotations',
}
```
### Bug Fixes
* \[**Comments**]: Fixed inline comments not marking as read when clicked.
* \[**Comments**]: Fixed focused annotation not clearing when sidebar is closed.
* \[**Notifications**]: Fixed empty state display issue.
### New Features
* \[**Comments**]: Added mark as read/unread option in comment dialog. You can now toggle read status for individual comments. [See wireframe customization →](/ui-customization/features/async/comments/comment-dialog-components#optionscontentmarkasread)
* \[**Comments**]: Added `unread` property to annotations. You can now check if an annotation is unread by the current user. See [`CommentAnnotation` data model](/api-reference/sdk/models/data-models#commentannotation).
* \[**Comments**]: Added Open and Reset filters to sidebar minimal filter dropdown. Filter annotations by open status or clear all active filters. [See wireframe customization →](/ui-customization/features/async/comments/comment-sidebar-components#minimalfilterdropdowncontent)
* \[**Comments**]: Added `context` prop to comment sidebar. Pass context data to page mode composer comments.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Added `velt-comment-dialog--read-only` class to comment dialogs. Style read-only comment dialogs with custom CSS.
* \[**Comments**]: Added `velt-comment-sidebar-minimal-filter-content-item--selected` class to selected filter items. Style selected filter items in sidebar minimal view.
* \[**Comments**]: Minimal actions and filter dropdowns now close automatically when an item is selected.
### Bug Fixes
* \[**Comments**]: Fixed sidebar button showing "..." instead of "0" when document has no comments. Unread count now displays correctly.
### New Features
* \[**CRDT**]: Added webhook support for CRDT data changes. Webhooks are disabled by default. When enabled, the default debounce time is 5 seconds. Control webhook behavior using `enableWebhook()`, `disableWebhook()`, and `setWebhookDebounceTime()` methods. [See documentation →](/realtime-collaboration/crdt/setup/core#webhooks)
* \[**CRDT**]: Added `updateData` event subscription to listen for CRDT data changes. Subscribe using the `on()` method to receive real-time notifications when CRDT data is modified. [See documentation →](/realtime-collaboration/crdt/setup/core#on)
```jsx theme={null}
import { useCrdtUtils, useCrdtEventCallback } from "@veltdev/react";
export function YourComponent() {
const crdtUtils = useCrdtUtils();
useEffect(() => {
if (crdtUtils) {
// Enable webhook
crdtUtils.enableWebhook();
// Optional: Change webhook debounce time (minimum 5 seconds)
crdtUtils.setWebhookDebounceTime(10 * 1000); // 10 seconds
}
}, [crdtUtils]);
// Subscribe to updateData event
const crdtUpdateData = useCrdtEventCallback("updateData");
useEffect(() => {
console.log("[CRDT] event on data change: ", crdtUpdateData);
}, [crdtUpdateData]);
return Your Component
;
}
```
```html theme={null}
```
### Improvements
* \[**Access Control**]: Added context information to permission provider requests when `setDocuments` is called. The context set during `setDocuments` is now included in resource access requests.
```jsx theme={null}
// Using API methods
client.setDocuments([{
documentId: 'document1',
context: {
access: {
code: [1, 2]
}
}
}]);
// Permission provider will receive context in request payload
```
```html theme={null}
```
```json theme={null}
{
"event": "resourceAccessRequestFormed",
"methodName": "setDocuments",
"uniqueId": "qaDuR1t4qN35xZ2lHTp0",
"timestamp": 1766506430306,
"source": "internal",
"payload": {
"requests": [
{
"userId": "user123",
"resource": {
"id": "document1",
"type": "document",
"source": "setDocuments",
"organizationId": "org1",
"context": {
"access": {
"code": [1, 2]
}
}
}
},
{
"userId": "user123",
"resource": {
"id": "{\"code\":1}",
"type": "context",
"source": "setDocuments",
"organizationId": "org1",
"context": {
"access": {
"code": 1
}
}
}
},
{
"userId": "user123",
"resource": {
"id": "{\"code\":2}",
"type": "context",
"source": "setDocuments",
"organizationId": "org1",
"context": {
"access": {
"code": 2
}
}
}
}
]
}
}
```
* \[**Access Control**]: Added context access information to `getUserPermissions` API response. Returns `accessFields` array showing which context values the user has access to.
```jsx theme={null}
// Using API methods
const request = {
organizationId: 'org1',
documentIds: ['document1']
};
const response = await client.getUserPermissions(request);
// Response includes context access info at the user level
console.log(response.result.data['user_123'].context.accessFields); // ['code:1', 'code:2', 'code:3']
```
```html theme={null}
```
```json theme={null}
{
"result": {
"status": "success",
"message": "User permissions retrieved successfully.",
"data": {
"user_123": {
"documents": {
"document1": {
"accessRole": "editor"
}
},
"organization": {
"org1": {
"accessRole": "editor"
}
},
"context": {
"accessFields": ["code:1", "code:2", "code:3"]
}
}
}
}
}
```
### Bug Fixes
* \[**Core**]: Fixed document metadata not being saved during `setDocuments`. Metadata details are now correctly persisted to the database.
### Improvements
* \[**Comments**]: Added paginated contact list to limit user downloads. Significantly reduces data transfer for apps with thousands of users.
```jsx theme={null}
// Using Props
// Using API methods
const commentElement = client.getCommentElement();
// Enable
commentElement.enablePaginatedContactList();
// Disable
commentElement.disablePaginatedContactList();
```
```html theme={null}
```
### New Features
* \[**Live Selection**]: Added `setInactivityTime()` to configure inactivity duration before removing stale live selections. Default is 5 minutes.
```jsx theme={null}
// Using Hooks
import { useLiveSelectionUtils } from '@veltdev/react';
const selectionUtils = useLiveSelectionUtils();
const tenMinutesInMs = 10 * 60 * 1000;
selectionUtils.setInactivityTime(tenMinutesInMs);
// Using API methods
const selectionElement = client.getSelectionElement();
const tenMinutesInMs = 10 * 60 * 1000;
selectionElement.setInactivityTime(tenMinutesInMs);
```
```html theme={null}
```
* \[**Comments**]: Added `commentCountType` prop to control which count displays in sidebar buttons. Choose between total or unread comment counts.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `openDialog` prop to comment bubbles. Set to `false` to prevent dialog from opening when bubble is clicked.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added `commentBubbleClickedEvent` to detect comment bubble interactions.
```jsx theme={null}
// Using Hooks
import { useCommentEventCallback } from '@veltdev/react';
import { useEffect } from 'react';
const commentBubbleClickedEvent = useCommentEventCallback('commentBubbleClicked');
useEffect(() => {
if (commentBubbleClickedEvent) {
console.log('commentBubbleClicked: ', commentBubbleClickedEvent);
}
}, [commentBubbleClickedEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('commentBubbleClicked').subscribe((eventData) => {
console.log('Comment bubble clicked:', eventData);
});
```
```html theme={null}
```
```typescript theme={null}
export interface CommentBubbleClickedEvent {
annotationId: string;
commentAnnotation: CommentAnnotation;
metadata?: VeltEventMetadata;
}
```
* \[**UI Customization**]: Added unread icon wireframes to sidebar buttons and comment bubbles. Display custom unread indicators for unread comments. [See sidebar button wireframes →](/ui-customization/features/async/comments/comment-sidebar-button#unreadicon) [See comment bubble wireframes →](/ui-customization/features/async/comments/comment-bubble#unreadicon)
### New Features
* \[**Core**]: Added `getCurrentUserPermissions` API to subscribe to current user permissions. Useful for debugging and developer tools integration.
```jsx theme={null}
// Subscribe to current user permissions
const subscription = client.getCurrentUserPermissions().subscribe((permissions) => {
console.log('Current user permissions:', permissions);
// permissions structure matches getUserPermissions response format
});
// Unsubscribe when done
subscription.unsubscribe();
```
```html theme={null}
```
### Improvements
* \[**Core**]: Added context access info to `getUserPermissions` and `getCurrentUserPermissions` APIs for enhanced debugging capabilities.
* \[**Core**]: Added context field from `setDocuments` to `fetchDebugInfo` method for improved debugging support.
### Improvements
* \[**Comments**]: Improved robust XPath detection logic when resolving elements through comment anchors. Anchoring now supports more scenarios for detecting target elements when adding comments.
### Bug Fixes
* \[**Comments**]: Fixed shadow DOM error in Comment Player Timeline where a variable was accessing an undefined component instance.
* \[**Comments**]: Fixed `data-velt-comment-pin-active` attribute being incorrectly removed when a dialog was still selected in the host element. The attribute now correctly reflects the active state across all annotations in the host.
* \[**Comments**]: Fixed reset filter in multi-thread comments not showing resolved comments. The reset button now clears all filters and displays all annotations including resolved ones.
### Bug Fixes
* \[**Comments**]: Fixed race condition in `addContext` where user context was being overwritten. Added internal timeout to ensure user context is properly applied alongside the default access context.
### Bug Fixes
* \[**Comments**]: Fixed Comment Bubble closing unexpectedly after adding the first comment. The bubble now remains open as expected.
* \[**Comments**]: Fixed draft comment annotations not being created. Users can now add draft comment annotations again.
### Bug Fixes
* \[**Comments**]: Fixed `contextId` generation in comment annotations. Context filtering now works correctly with exact matches in Comment Tool and Comment Bubble components.
### Improvements
* \[**Core**]: Added batching for internal service calls. Reduces network calls by 90%.
* \[**Comments**]: Refactored `@veltdev/tiptap-velt-comments` and `@veltdev/lexical-velt-comments` to follow a common pattern and folder structure across all libraries. This enables faster development of Velt comment integrations and covers more rich text editor scenarios.
### Improvements
* \[**Comments**]: Added `expandOnSelection` prop to Comments Sidebar. Control whether dialogs expand when selected in the sidebar.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added event subscription support for data providers via the `Velt.on` method. You can now monitor and debug data provider operations (get, save, delete) using the same event-based debugging pattern available for permission providers.
### Bug Fixes
* \[**Comments**]: Added pin click event support for Standalone Comment Pin Component (VeltCommentPin). Click events now work consistently across all pin types.
### Improvements
* \[**Comments**]: Reply counts now display in read-only mode. Previously hidden, you can now see the number of replies on each comment when viewing in read-only state.
* \[**Comments**]: Added `dialogSelection` prop to disable dialog selection in sidebar. Control whether users can select comment dialogs in the sidebar.
```jsx theme={null}
```
```html theme={null}
```
* \[**Core**]: Added `throwError` config to `identify()` and auth provider methods. Set `throwError: true` to receive errors instead of null when authentication fails. Default is `false`.
```jsx theme={null}
// Using Hooks
import { useIdentify } from '@veltdev/react';
// Using API methods
// Promise-based
client.identify(user, {
throwError: true,
}).then((user) => {
// user is authenticated
}).catch((err) => {
// handle error
});
// Async/await
try {
const user = await client.identify(user, {
throwError: true,
});
} catch (err) {
// handle error
}
// Using setVeltAuthProvider
client.setVeltAuthProvider({
user,
options: {
throwError: true,
},
onError: (err) => {
// handle error
}
});
```
```html theme={null}
```
* \[**Core**]: Improved `updateContext()` logic to handle context access scenarios. Context resolution now works correctly regardless of merge settings.
### Improvements
* \[**Core**]: Routine package updates.
### Improvements
* \[**Comments**]: Added `commentPinClicked` event to detect pin interactions. You can now capture click events on comment pins to track user engagement.
```jsx theme={null}
// Using Hooks
import { useCommentEventCallback } from '@veltdev/react';
import { useEffect } from 'react';
const commentPinClickedEvent = useCommentEventCallback('commentPinClicked');
useEffect(() => {
if (commentPinClickedEvent) {
console.log('commentPinClicked: ', commentPinClickedEvent);
}
}, [commentPinClickedEvent]);
// Using API methods
const commentElement = client.getCommentElement();
commentElement.on('commentPinClicked')
.subscribe((eventData) => {
console.log('Comment pin clicked:', eventData);
});
```
```html theme={null}
```
* \[**Comments**]: Added `readOnly` property to Comments Sidebar. Sidebar comment dialogs can now be set to read-only mode to prevent edits.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Fixed tabindex behavior in all comment components. When a component is hidden or empty, tabindex is now removed to prevent focus on invisible elements.
### New Features
* \[**Core**]: Added `getCurrentUser()` method to subscribe to user object changes. You can now detect when the current user's data updates in real-time.
```jsx theme={null}
// Using Hooks
import { useCurrentUser } from '@veltdev/react';
import { useEffect } from 'react';
export function YourComponent() {
const veltUser = useCurrentUser();
useEffect(() => {
console.log("Velt user: ", veltUser);
}, [veltUser]);
}
// Using API methods
client.getCurrentUser().subscribe((veltUser: User | null) => {
console.log("Velt user: ", veltUser);
});
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Added default access context in comments feature. This ensures that all relevant comments are fetched in the query if you are using feature level permissions using access context. [Learn More](https://docs.velt.dev/key-concepts/overview#set-feature-level-permissions-using-access-context-custom-metadata)
### Improvements
* \[**Comments**]: Extended private comments feature to support both admin and organization users.
### Improvements
* \[**REST APIs**]: Added `verifyUserPermissions` flag to [`Add Comment Annotations`](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations), [`Add Comments in Comment Annotation`](/api-reference/rest-apis/v2/comments-feature/comments/add-comments), [`Add Notifications`](/api-reference/rest-apis/v2/notifications/add-notifications), and [`Update Notifications`](/api-reference/rest-apis/v2/notifications/update-notifications) REST APIs. When enabled, these operations are only performed for users who have access to the specified document, preventing unauthorized resource creation or updates.
### Bug Fixes
* \[**Core**]: Added missing types and added automated reviews to prevent this in future.
### Bug Fixes
* \[**Core**]: Fixed an issue preventing fetching all documents within a folder.
### Bug Fixes
* \[**Core**]: Fixed TypeScript types for the Velt `on()` method to display properly in IDE suggestions.
* \[**Core**]: Added validation in `getUserPermissions()` to check if user is set before proceeding, preventing inconsistencies when user is not present.
* \[**Core**]: Added memoization to providers in `VeltProviders` to prevent unnecessary re-renders when input values haven't changed.
* \[**Core**]: Fixed document creation with folder support in the SDK.
### Bug Fixes
* \[**Core**]: Fixed race condition in the `identify()` method that prevented user colors from updating correctly.
* \[**Comments**]: Fixed an issue where text comments data remained stale in case of text editors that could cause data loss when saving annotations.
### New Features
* \[**Core**]: Added `getHeartbeat()` method to subscribe to user-specific heartbeat data. You can retrieve heartbeat data for the current user or specify a `userId` to get heartbeats for any user, providing better visibility into active sessions per user.
```jsx theme={null}
// Using Hooks
import { useHeartbeat } from "@veltdev/react";
import { useEffect } from "react";
const { data: heartbeatData } = useHeartbeat();
useEffect(() => {
console.log("Heartbeat data: ", heartbeatData);
}, [heartbeatData]);
// Using API methods
client.getHeartbeat().subscribe((heartbeatData) => {
console.log("Heartbeat data: ", heartbeatData);
});
```
```html theme={null}
```
**Heartbeat Interfaces:**
```typescript theme={null}
export interface HeartbeatConfig {
userId?: string;
}
export interface GetHeartbeatResponse {
data: Heartbeat[] | null;
}
```
### Improvements
* \[**Comments**]: Added location removal logic when video plays in Timeline Player. Comment pins are now properly removed as the video plays, ensuring accurate timeline visualization.
* \[**Comments**]: Added change detection in Timeline Player. The timeline bar now moves correctly when hovering over the play button or timeline, ensuring responsive playback controls.
### Bug Fixes
* \[**Core**]: Fixed disconnect errors in the heartbeat feature. The SDK now handles network issues at the client side without triggering server disconnect errors.
* \[**Core**]: Fixed user color and text color persistence during auto-login. Colors passed during `identify()` now persist correctly in auto-login scenarios, ensuring consistent user identification.
* \[**Presence**]: Fixed `multipleUsersOnline` event firing when only a single user is present. The presence feature now correctly identifies single-user scenarios, preventing unwanted analytics events.
### Bug Fixes
* \[**Comments**]: Fixed race condition errors in Player Timeline by adding proper lifecycle management. The player timeline now properly handles component destruction, preventing "undefined" errors that could occur when the component was destroyed during asynchronous operations.
* \[**Notifications**]: Fixed error handling for document notifications by adding metadata validation. The SDK now checks if notification metadata exists before processing document notifications, preventing unnecessary errors in analytics tracking when notifications are missing required data.
### Bug Fixes
* \[**Core**]: Fixed user resolution for tagged and assigned users in contact lists. Users added via `updateContactList()` are now properly cached, ensuring that tagged and assigned users resolve correctly from cached data instead of failing to load.
### Bug Fixes
* \[**Core**]: Improved `updateContactList()` method behavior by removing unnecessary validation checks. The method now directly applies user-provided contact list data without additional validation, giving you more control over contact list management.
### Bug Fixes
* \[**Recorder**]: Fixed an issue where zoom was not applied unless the zoom section in the timeline was modified directly.
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the zoom animation in the editor preview had a curve in the transition. The editor preview now displays smooth transitions to the correct coordinates instead of curved transitions.
* \[**Recorder**]: Fixed an issue where VeltIf was throwing an error when evaluating MediaStream object.
* \[**Comments**]: Fixed sanitization of `target` attributes in comment HTML content. The DomPurifier now preserves `target` attributes in comment HTML fields, allowing users to include links with target specifications (such as `target="_blank"`) in their comments.
### Improvements
* \[**Core**]: Optimized the initialization module and significantly reduced the SDK initialization time.
### New Features
* \[**Core**]: Added `fetchDebugInfo()` and `getDebugInfo()` methods to retrieve debugging information about your Velt implementation. Use `fetchDebugInfo()` to get a one-time snapshot or `getDebugInfo()` to subscribe to real-time updates of key setup info like sdk version, apikey, user, organizationId, documentId, folderID version, locations etc. You can also get this info from [Velt’s Chrome devtools](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl)
```jsx theme={null}
// Using API methods - One-time fetch
await client.fetchDebugInfo();
// Using API methods - Subscribe to updates
client.getDebugInfo().subscribe((debugInfo) => {
console.log("Debug info: ", debugInfo);
});
```
```html theme={null}
```
**Debug Info Interface:**
```typescript theme={null}
export interface VeltDebugInfo {
veltVersion?: string;
apiKey?: string;
serverMap?: {
organization?: OrganizationMetadata;
documents?: DocumentMetadata[];
locations?: Location[];
folder?: FolderMetadata;
user?: User;
};
clientMap?: {
organization?: OrganizationMetadata;
documents?: DocumentMetadata[];
locations?: Location[];
folder?: FolderMetadata;
user?: User;
};
}
```
### Improvements
* \[**Access Control**]: Prevent users from adding comments, reactions, recorders, and area comments through the SDK when `access context` is used and the user doesn’t have access to the specific context.
### New Features
* \[**Comments**]: Added preliminary infra required to capture screenshots in comments.
### Improvements
* \[**Access Control**]: Now you can set feature level permissions using Access Context. Access Context allows you to set granular, feature-level permissions using custom metadata. When configured, new feature data is added and existing feature data is fetched only for the access context values the current user has access to. [Learn more →](/key-concepts/overview#set-feature-level-permissions-using-access-context-custom-metadata)
### Improvements
* \[**Recorder**]: Updated video editor timeline picture mode design for improved visual clarity. The unselected portion of timeline pictures now displays with a cleaner design that better distinguishes selected from unselected frames.
* \[**Recorder**]: Fixed audio merging in screen recording to combine microphone and tab audio. When recording with both microphone and tab audio enabled, both audio sources are now properly merged into the final recording.
### Bug Fixes
* \[**Comments**]: Fixed type definition for `selectCommentByAnnotationId()` and made `annotationId` parameter optional.
* \[**Comments**]: Fixed embed mode logic in comments sidebar to support multiple embedded sidebars.
### New Features
* \[**Access Control**]: Added the new Permission Provider feature. With this approach, Velt pings your defined endpoint to verify whether a user should be granted access to a resource (organization, folder, or document). This ensures that your backend is still the source of truth and you don't have to sync the permissions into Velt directly. [Learn more →](/key-concepts/overview#c-real-time-permission-provider)
* \[**Access Control**]: Added a config to automatically revoke permissions, including revoking access to documents, folders, and optionally organizations when users log out or when documents are unset. This ensures immediate permission removal without requiring manual cleanup. [Learn more →](/key-concepts/overview#c-real-time-permission-provider)
* \[**Access Control**]: Added various Permission Provider events to monitor the sequence of permission check events for debugging and tracking purposes. [Learn more →](/api-reference/sdk/models/data-models#permissionproviderevent)
### Improvements
* \[**Access Control**]: Simplified Permission Provider implementation by removing `onResourceAccessRequired` call and signature handling from client SDK. Permission handling is now fully managed internally by the SDK. You no longer need to handle signatures or make `onResourceAccessRequired` calls. The SDK automatically handles permission caching, validation, and synchronization with the backend. [Learn more →](/api-reference/sdk/api/api-methods#setpermissionprovider)
### New Features
* \[**Recorder**]: Added video editor timeline image preview to display frame snapshots in the timeline. This helps you quickly navigate to specific scenes without scrubbing through the entire video.
```jsx theme={null}
```
```html theme={null}
```
The timeline preview only works when both `videoEditorTimelinePreview` and `videoEditor` are set to `true`.
### Improvements
* \[**Comments**]: Enhanced `selectCommentByAnnotationId()` to close the selected comment annotation when called with no arguments or an invalid ID.
```jsx theme={null}
// Using Hooks
const commentElement = useCommentUtils();
// Close the currently selected annotation
commentElement.selectCommentByAnnotationId();
commentElement.selectCommentByAnnotationId('invalid-id');
// Using API methods
const commentElement = client.getCommentElement();
// Close the currently selected annotation
commentElement.selectCommentByAnnotationId();
commentElement.selectCommentByAnnotationId('invalid-id');
```
```html theme={null}
```
### New Features
* \[**Access Control**]: Added early version of feature level permissions using Access Context. Access Context allows you to set granular, feature-level permissions using custom metadata. When configured, new feature data is added and existing feature data is fetched only for the access context values the current user has access to. [Learn more →](/key-concepts/overview#set-feature-level-permissions-using-access-context-custom-metadata)
### Improvements
* \[**Access Control**]: Added `source` field to Permission Provider requests to identify which method triggered the request. The `source` field helps you debug and trace which SDK method initiated the permission check. [Learn more →](/key-concepts/overview#c-real-time-permission-provider)
* \[**Access Control**]: Added various Permission Provider events to monitor the sequence of permission check events for debugging and tracking purposes. [Learn more →](/api-reference/sdk/models/data-models#permissionproviderevent)
### Bug Fixes
* \[**Notifications**]: Fixed notification fetching with Permission Provider when document IDs needed mapping to client document IDs.
### New Features
* \[**Comments**]: Added `markAsRead()` and `markAsUnread()` methods to mark comment annotations as read or unread for the current user.
```jsx theme={null}
// Using Hooks
const commentElement = useCommentUtils();
commentElement.markAsUnread("eUgq6G6zXxJmOT9eBXtT");
commentElement.markAsRead("eUgq6G6zXxJmOT9eBXtT");
// Using API methods
const commentElement = client.getCommentElement();
commentElement.markAsUnread("eUgq6G6zXxJmOT9eBXtT");
commentElement.markAsRead("eUgq6G6zXxJmOT9eBXtT");
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.markAsUnread("eUgq6G6zXxJmOT9eBXtT");
commentElement.markAsRead("eUgq6G6zXxJmOT9eBXtT");
```
### Improvements
* \[**Recorder**]: Added system sound capture when recording a browser tab.
* \[**Comments**]: You can now tag users by copy-pasting their email address. Previously, only manually typed emails worked for tagging users not on the contact list.
* \[**Comments**]: Added `viewedBy` and `reactionAnnotations` fields to comment annotation objects returned via Get Comment Annotations frontend api methods.
* **`viewedBy`**: An array of User objects representing who has seen and read the comment annotation. Use this to track engagement, identify which stakeholders have reviewed feedback, or build custom read receipt indicators.
* **`reactionAnnotations`**: An array of complete ReactionAnnotation objects containing the full reaction data for each message inside the thread. Use this to render reactions data (eg: reaction counts, show who reacted with what emoji) in your own components.
```typescript theme={null}
// Comment Annotation Object
{
...
comments: [
{
...
reactionAnnotations?: ReactionAnnotation[]; // Complete reaction objects with full details
}
],
viewedBy?: User[]; // Users who have seen and read this annotation
}
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue in the video editor where the playhead position was ignored after playback ended. When seeking or dragging the playhead after video completion, playback now correctly starts from the new position instead of always starting from the beginning.
### Improvements
* \[**Comments**]: Released v2 improved version of comment anchoring that makes comment positioning more dynamic and robust on more dynamic websites. This includes enhanced element detection for pin, text, and area comments, improved cursor display based on DOM element visibility, and better handling of image tag positioning with conditional relative positioning for container elements.
### Bug Fixes
* \[**Comments**]: Fixed an issue where draft comments with resolvers were incorrectly submitted before being finalized, causing data inconsistencies. Draft comments with resolvers now work properly and are only saved when explicitly published.
* \[**Access Control**]: Fixed an issue where organization access permissions were not being set correctly when using the permission provider. When users logged in, their organization-level permissions were failing to initialize properly. Organization access now gets assigned correctly during the identification process.
### New Features
* \[**Notifications**]: Added `triggerNotification` flag to the [Add Comment Annotations REST API](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations). When enabled, adding comments via the REST API will trigger in-app notifications, email notifications to relevant users and also trigger webhooks matching the SDK’s native notification behavior.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where marking all notifications as read in the SDK was not working correctly.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where a notification API was being called twice with the same request.
### Improvements
* \[**Notifications**]: In-app notifications and APIs now only create and retrieve notifications for documents the user has access to; notifications for inaccessible documents will no longer be generated or returned (including historical ones)
* \[**REST APIs**]: Added `verifyUserPermissions` flag to [`Add Notifications`](/api-reference/rest-apis/v2/notifications/add-notifications) and [`Update Notifications`](/api-reference/rest-apis/v2/notifications/update-notifications) REST APIs.
When enabled, notifications are only created or updated for users who have access to the specified document.
Note: If you are using the new `PermissionProvider` then this flag will not work, you will need to do the permissions validation on your end before adding or updating the notification.
### Bug Fixes
* \[**Core**]: Fixed document filtering logic that was incorrectly handling access permissions. Documents are now properly filtered based on user access rights.
### New Features
* \[**Access Control**]: Introduced a new **Permission Provider** approach for simpler, centralized access control. Key details:
* You no longer need to sync users into Velt or call the Permissions API directly.
* Configure an endpoint that Velt will call to verify user access to resources (organization, folder, or document).
* When a user logs in or tries to access a resource:
* Velt sends a request to your configured endpoint with relevant access details.
* Your endpoint evaluates access, signs the result using Velt's API, and returns the response.
* Velt validates the signature and updates access permissions accordingly.
* This creates a single, backend-controlled source of truth for permissions.
* You can use this approach instead of **user sync** and **on-demand** permission strategies.
* [Learn more](/key-concepts/overview#c-real-time-permission-provider).
### Improvements
* \[**Access Control**]: Added `maxDepth` parameter to [`Get Folders` REST API](/api-reference/rest-apis/v2/folders/get-folders). You can now retrieve nested subfolders at any depth level. The response includes `ancestors` array showing parent hierarchy and `inheritAccessFromParent` field indicating whether access is inherited.
* \[**Access Control**]: Added `inheritFromParent` parameter to the [Update Folder Access REST API](/api-reference/rest-apis/v2/folders/update-folder-access). You can now configure a folder to inherit access permissions from its parent folder.
* \[**Access Control**]: The [Update Folder REST API](/api-reference/rest-apis/v2/folders/update-folder) now handles folder moves correctly. When a folder is moved to a different parent, its `ancestors` array and `accessType` are automatically updated, and all subfolder information is updated accordingly.
* \[**Access Control**]: Added `accessType` parameter to the [Add Folder REST API](/api-reference/rest-apis/v2/folders/add-folder). You can now set a folder's access permissions during creation.
* \[**Core**]: The `setDocuments` method now filters out documents the user doesn't have access to instead of failing the entire operation. Previously, if any document in the array was inaccessible, the entire request would fail.
* \[**Access Control**]: Resources (Organizations, Folders, and Documents) follow a hierarchical permission model. By default, **each resource inherits its access type and user permissions** from its parent. If a resource defines its own access type or user permissions, those **explicit settings override the inherited values**. This precedence applies during both **permission evaluation and access enforcement**.
* \[**Access Control**]: Folder document limit set to 50 when using `setDocuments` with the `allDocuments` flag. When fetching all documents from a folder, only the first 50 documents are retrieved, and any documents the user doesn't have access to are filtered out.
### Bug Fixes
* \[**Live State Sync**]: Fixed `getLiveStateData()` so that with `listenToNewChangesOnly: true`, it now returns only new changes after subscribing—instead of including existing data.
### Bug Fixes
* \[**Comments**]: Fixed an issue where tagged users who don't exist in the database were not displaying correctly in email notifications. Comments now render with the proper format in both the UI and emails.
* \[**Comments**]: Fixed an issue where user mentions in email notifications were not resolving with correct user details. Tagged users in comments now display properly with accurate information in all email notifications.
* \[**Comments**]: Fixed an issue where user details were not resolving when a user from a different organization added comments in an accessible document. This previously caused skeleton loading states or fallback formatting for existing organization users, but now displays correctly.
### New Features
* \[**Live State Sync**]: Added `fetchLiveStateData()` method to retrieve synced state data. You can fetch all synced data or retrieve data for a specific `liveStateDataId`.
```jsx theme={null}
// Using Hooks
const liveStateSyncElement = useLiveStateSyncUtils();
/// To get all the data
const data = await liveStateSyncElement.fetchLiveStateData();
/// To get data for specific liveStateDataId
const specificData = await liveStateSyncElement.fetchLiveStateData({
liveStateDataId: "LIVE_STATE_DATA_ID"
});
// Using API methods
const liveStateSyncElement = client.getLiveStateSyncElement();
/// To get all the data
const data = await liveStateSyncElement.fetchLiveStateData();
/// To get data for specific liveStateDataId
const specificData = await liveStateSyncElement.fetchLiveStateData({
liveStateDataId: "LIVE_STATE_DATA_ID"
});
```
```jsx theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
// To get all the data
const data = await liveStateSyncElement.fetchLiveStateData();
// To get data for specific liveStateDataId
const specificData = await liveStateSyncElement.fetchLiveStateData({
liveStateDataId: "LIVE_STATE_DATA_ID"
});
```
### Bug Fixes
* \[**Live State Sync**]: The useServerConnectionStateChangeHandler hook was sometimes incorrectly reporting an offline state due to the behavior of our special listener in a multi-tab environment. When a browser throttled an idle tab, it would interfere with the shared network activity for this specific listener. This caused the listener in active tabs to momentarily and incorrectly return a false (disconnected) status, even though the primary database connection remained active. We added a 1.5s delay to resolve the network state before emitting the value in the hook.
### Improvements
* \[**Single Editor Mode**]: Improved offline handling for Single Editor Mode controls. When you're offline and attempt to use the "Edit Here" button or accept/reject actions, these operations are now prevented and a console warning is displayed.
* \[**Live State Sync**]: Added timestamp field to Redux action data in Live State Sync. Each action now includes a UTC timestamp for better tracking and debugging.
```json theme={null}
{
"id": "ACTION_ID",
"action": {
"type": "ACTION_TYPE",
"payload": "ACTION_PAYLOAD" // optional
},
"timestamp": 1759745729823 // UTC timestamp in milliseconds
}
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the control panel was not visible in thread mode.
### Bug Fixes
* \[**Comments**]: Fixed an issue with manual comment pin selection when working with draft comment annotations.
### Bug Fixes
* \[**Single Editor Mode**]: Fixed an issue related to switching editor access when heartbeat is disabled.
### New Features
* \[**Live State Sync**]: Added heartbeat feature for Single Editor Mode. The heartbeat mechanism provides more reliable presence detection in single editor mode scenarios. This feature is enabled by default when single editor mode is enabled.
If you want to disable heartbeat functionality, you must disable it before enabling single editor mode.
```jsx theme={null}
// Using Hooks
const liveStateSyncElement = useLiveStateSyncUtils();
liveStateSyncElement.enableHeartbeat();
liveStateSyncElement.disableHeartbeat();
// Using API methods
const liveStateSyncElement = client.getLiveStateSyncElement();
liveStateSyncElement.enableHeartbeat();
liveStateSyncElement.disableHeartbeat();
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where the comment dialog would auto-close after submitting the first comment in popover mode.
### Improvements
* \[**Comments**]: Added option to close persistent comment mode with ESC key, even when a thread is active. Use `forceCloseAllOnEsc` (prop/API).
```jsx theme={null}
// Using props on component
// Using API methods
const commentElement = client.getCommentElement();
commentElement.enableForceCloseAllOnEsc();
commentElement.disableForceCloseAllOnEsc();
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where `resolvedCommentsOnDom` was not applying to comment bubbles in the video player timeline.
### New Features
* \[**Comments**]: Added attachments data support in REST API endpoints for adding and updating comments. You can programmatically include attachment metadata when creating or modifying comments through the REST API.
The `attachments` field supports comprehensive metadata including:
* **File identification**: `attachmentId`, `name`, `bucketPath`
* **File properties**: `size`, `type`, `url`, `thumbnail`, `mimeType`
* **Custom metadata**: Flexible metadata object for dimensions, timestamps, and other custom properties
See the full REST API reference:
* [Add Comments API](/api-reference/rest-apis/v2/comments-feature/comments/add-comments)
* [Update Comments API](/api-reference/rest-apis/v2/comments-feature/comments/update-comments)
### Bug Fixes
* \[**UI Customization**]: Fixed an issue where inline comments section filter dropdown wireframe not rendering correctly.
### New Features
* \[**Notifications**]: Added ESC key support to close the notification panel.
* \[**Comments**]: Added unread badge in CommentPlayerTimeline for annotations.
* \[**Recorder**]: Added configuration to control playback behavior on preview click. You can now enable or disable the click-to-play/pause functionality on recording previews.
```jsx theme={null}
// Using props on component
// Using API methods
const recorderElement = client.getRecorderElement();
recorderElement.enablePlaybackOnPreviewClick();
recorderElement.disablePlaybackOnPreviewClick();
```
```html theme={null}
```
* \[**Notifications**]: Added `considerAllNotifications` configuration to the notifications tool to control the notification count and unread indicator. When enabled, the notification count and unread badge include items from all tabs; when disabled (default), they only include items from the “For You” tab.
```jsx theme={null}
```
```html theme={null}
```
* \[**Notifications**]: Added ability to filter notifications to the currently set document only. By default it shows notifications for the 15 most recently active documents accessible to the current user in the current organization.
```jsx theme={null}
const notificationElement = client.getNotificationElement();
notificationElement.enableCurrentDocumentOnly();
notificationElement.disableCurrentDocumentOnly();
```
```html theme={null}
```
### Improvements
* \[**Notifications**]: Added ability to hide notification tabs when only one tab is enabled.
* \[**UI Customization**]: Improved CSS variable handling in the ESC button for Persistent Comments Banner.
* \[**UI Customization**]: Removed extra space next to the notification icon.
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the recording edit done event was not triggering with the correct data.
* \[**Recorder**]: Fixed an issue where the countdown timer for the recorder was not working in Firefox.
### New Features
* \[**UI Customization**]: Added hide reply button wireframe for the comment dialog. [See wireframe customization →](/ui-customization/features/async/comments/comment-dialog-components#hidereply)
* \[**UI Customization**]: Added thread card reply button wireframe for the comment dialog to enable quick replies directly from comment thread cards. [See wireframe customization →](/ui-customization/features/async/comments/comment-dialog-components#threadcardreply)
* \[**UI Customization**]: Added filter dropdown wireframe for the inline comments section. [See wireframe customization →](/ui-customization/features/async/comments/inline-comments-section#filterdropdown-panel)
* \[**Comments**]: Added `allowedFileTypes` property to limit file types in comment attachments.
```jsx theme={null}
// Using props on component
// Using API method
const commentElement = client.getCommentElement();
commentElement.setAllowedFileTypes(['jpg', 'png']);
```
```html theme={null}
```
* \[**Comments**]: Added ability to display the attachment filename in the message when a file is attached.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments**]: Added setComposerFileAttachments() API method to programmatically add file attachments to the comment composer from your application instead of requiring users to select files from the file system.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.setComposerFileAttachments({
files: [file1, file2],
annotationId: 'annotation-123',
targetElementId: 'element-456',
});
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where floating mode prop was being incorrectly added in React when enabling embed mode in the sidebar.
### New Features
* \[**UI Customization**]: Added comment-level reply button wireframe. This button is hidden by default and can be enabled through wireframe customization.
* \[**UI Customization**]: Added hide reply button wireframes for the comment dialog body.
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the Recorder control panel not being draggable.
* \[**Access Control**]: Fixed an issue where Get User Permissions REST API was not returning `permission_denied` errors in the response
See the full REST API reference: Get Permissions .
### New Features
* \[**Comments**]: Added `readOnly` flag to Comment Bubble component to prevent users from replying or editing existing comments in the target bubble while still displaying them. This is useful when you want to display comments in a read-only mode where users can view but not modify or respond to comments.
```jsx theme={null}
// Using props on component
```
```html theme={null}
```
* \[**Comments**]: Added `disabled` flag to Comment Tool component to disable the comment tool and prevent users from adding new comments. This is helpful when you want to temporarily or conditionally restrict comment creation while still allowing users to view existing comments.
```jsx theme={null}
// Using props on component
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where clicking on one comment bubble would unintentionally open multiple comment bubbles. Now clicking on a comment bubble correctly opens only that specific bubble.
* \[**Comments**]: Fixed an issue where empty comments were being saved in some scenarios.
### Bug Fixes
* \[**Comments**]: Fixed an issue where empty comments were being saved in some scenarios.
* \[**Core**]: Released 4.5.2 stable version.
### Bug Fixes
* \[**Comments**]: Fixed a minor issue with the `fullScreen` prop in the Comments Sidebar.
### New Features
* \[**Comments**]: Added full-screen mode for Comments Sidebar to maximize space for reviewing and managing comments. This is particularly useful when working with large volumes of feedback or conducting detailed comment reviews. By default, this feature is disabled and only supported in default mode (floating and embed modes are not supported).
```jsx theme={null}
// Using props on component
// Using API methods
const commentElement = client.getCommentElement();
commentElement.enableFullScreenInSidebar();
commentElement.disableFullScreenInSidebar();
```
```html theme={null}
```
* \[**Comments**]: Added fixed annotation numbers to comment pins and dialogs that persist across sessions, making it easier to reference specific comments in discussions and documentation. This replaces the previous temporary numbering system that reset on page refresh.
* \[**Comments**]: Added search by annotation number in Comments Sidebar. You can now quickly find specific comments by typing their annotation number (e.g., `#2`) in the search field, making it faster to locate and navigate to referenced comments.
### UI Customization
* \[**Comments**]: Added new wireframes to customize the appearance of comment annotation numbers and full-screen button in the Comments Sidebar. [See comment dialog wireframes →](/ui-customization/features/async/comments/comment-dialog-components#commentnumber) [See comment pin wireframes →](/ui-customization/features/async/comments/comment-pin#number) [See sidebar wireframes →](/ui-customization/features/async/comments/comment-sidebar-components#fullscreenbutton)
### Bug Fixes
* \[**Recorder**]: Fixed Firefox compatibility issue that was preventing recordings from being stopped properly.
* \[**Notifications**]: Fixed an issue where Notification Panel was not closing if Velt was not initialized.
### Bug Fixes
* \[**Core**]: Fixed invalid user name validation to now accept email values in user names. Also improved URL handling by removing content after the last dot (.) to make URLs non-functional, and increased maximum character limit from 20 to 30.
### Improvements
* \[**Access Control**]: Updated Get User Permissions API to remove `userIds` from the request payload and now returns structured error codes alongside permission denied messages. This provides clearer error handling and makes it easier to diagnose permission issues programmatically.
```ts theme={null}
// Request Payload
interface GetUserPermissionsRequest {
organizationId?: string;
folderIds?: string[];
documentIds?: string[];
}
// Response Payload
interface GetUserPermissionsResponse {
[userId: string]: {
folders?: {
[folderId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
errorCode?: UserPermissionAccessRoleResult;
}
}
organization?: {
[organizationId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
errorCode?: UserPermissionAccessRoleResult;
}
}
documents?: {
[documentId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
errorCode?: UserPermissionAccessRoleResult;
}
}
}
}
enum UserPermissionAccessRoleResult {
DOES_NOT_EXIST = 'does_not_exist',
PERMISSION_DENIED = 'permission_denied',
SOMETHING_WENT_WRONG = 'something_went_wrong',
}
enum UserPermissionAccessRole {
EDITOR = 'editor',
VIEWER = 'viewer',
}
// Using API
await client.getUserPermissions(request as GetUserPermissionsRequest);
```
```ts theme={null}
// Request Payload
interface GetUserPermissionsRequest {
organizationId?: string;
folderIds?: string[];
documentIds?: string[];
}
// Response Payload
interface GetUserPermissionsResponse {
[userId: string]: {
folders?: {
[folderId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
errorCode?: UserPermissionAccessRoleResult;
}
}
organization?: {
[organizationId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
errorCode?: UserPermissionAccessRoleResult;
}
}
documents?: {
[documentId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
errorCode?: UserPermissionAccessRoleResult;
}
}
}
}
enum UserPermissionAccessRoleResult {
DOES_NOT_EXIST = 'does_not_exist',
PERMISSION_DENIED = 'permission_denied',
SOMETHING_WENT_WRONG = 'something_went_wrong',
}
enum UserPermissionAccessRole {
EDITOR = 'editor',
VIEWER = 'viewer',
}
// Using API
await Velt.getUserPermissions(request as GetUserPermissionsRequest);
```
* \[**Access Control**]: Users with Viewer access can no longer add comments or update metadata through the SDK. This ensures proper permission enforcement on Velt features.
### Bug Fixes
* \[**Core**]: Fixed document switch issue where comments were not properly disappearing when navigating between documents, ensuring clean state transitions.
* \[**Core**]: Fixed local cache comment persistence issue that was causing stale comment data to remain after document changes.
### New Features
* \[**Recorder**]: Added maximum recording length feature that allows you to set time limits on recordings. This helps manage storage costs and ensures recordings stay focused and relevant.
```jsx theme={null}
// Using props on components
// Using API methods
const recorderElement = client.getRecorderElement();
recorderElement.setMaxLength(10);
```
```html theme={null}
```
* \[**Recorder**]: Added Picture-in-Picture (PiP) mode for screen recordings when camera is enabled. This allows users to continue recording while multitasking, improving workflow efficiency for tutorial creation and documentation.
Picture-in-Picture is only supported in Chrome browsers and only works for screen recordings when the camera is active. This feature is disabled by default.
```jsx theme={null}
// Enable PiP on components
// Control PiP via API
const recorderElement = client.getRecorderElement();
recorderElement.enablePictureInPicture();
recorderElement.disablePictureInPicture();
recorderElement.openPictureInPicture();
recorderElement.exitPictureInPicture();
// Custom PiP button wireframe
Custom Pip Button
```
```html theme={null}
Custom Pip Button
```
* \[**Recorder**]: Added screen recording preview to let users see their screen recordings preview prior to staring the recording. This helps users make make necessary adjustments before starting the recording. You programmatically ask users for permissions using the new API.
```jsx theme={null}
// Request screen permission for preview
const recorderElement = client.getRecorderElement();
recorderElement.requestScreenPermission();
// Custom screen player wireframe
```
```html theme={null}
```
### Bug Fixes
* \[**Single Editor Mode**]: Fixed heartbeat handling issue when external heartbeat is passed to prevent conflicts with internal presence detection mechanisms.
* \[**UI Customization**]: Fixed Angular Material CSS scoping to prevent style conflicts by ensuring Angular Material styles are properly contained within Velt components.
### Bug Fixes
* \[**Single Editor Mode**]: Fixed issue where editor assignment process wasn't reinitiated when a user regained internet connection or became active again after being offline. This ensures proper editor role management in collaborative editing scenarios.
* \[**Single Editor Mode**]: Fixed "Edit here" functionality to properly assign editor role to users even when no existing data is available in the document.
* \[**Core**]: Fixed React hook `setDocumentSuccess` event that wasn't triggering properly by adding flush sync method for `initUpdate` events. This ensures proper event handling and state updates in React applications.
### New Features
* \[**Comments**]: Added `commentPlaceholder` and `replyPlaceholder` props on `VeltComments` so you can tailor input copy to your app’s voice and improve guidance for first-time users.
```jsx theme={null}
// Using Props on VeltComments
```
```html theme={null}
```
### Bug Fixes
* \[**Recorder**]: Fixed Recording Preview Dialog width issue.
### New Features
* \[**Access Control**]: Added User Permissions API to fetch a user's `editor`/`viewer` access across organizations, folders, and documents, for both permanent and temporary users. This helps you enforce read-only experiences.
Users with `viewer` access are read-only; write actions like adding comments are blocked in the SDK.
See the [Access Control overview](/key-concepts/overview#access-control) for concepts and detailed guidance.
#### REST API
Retrieves user permissions for organizations, folders, and documents in a single API call. Supports both regular and temporary users.
See the full REST API reference: Get Permissions .
#### SDK
```ts theme={null}
// Request Payload
interface GetUserPermissionsRequest {
organizationId?: string;
folderIds?: string[];
documentIds?: string[];
userIds?: string[];
}
// Response Payload
interface GetUserPermissionsResponse {
[userId: string]: {
folders?: {
[folderId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
}
}
organization?: {
[organizationId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
}
}
documents?: {
[documentId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
}
}
}
}
enum UserPermissionAccessRole {
EDITOR = 'editor',
VIEWER = 'viewer',
}
// Using API
await client.getUserPermissions(request as GetUserPermissionsRequest);
```
```ts theme={null}
// Request Payload
interface GetUserPermissionsRequest {
organizationId?: string;
folderIds?: string[];
documentIds?: string[];
userIds?: string[];
}
// Response Payload
interface GetUserPermissionsResponse {
[userId: string]: {
folders?: {
[folderId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
}
}
organization?: {
[organizationId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
}
}
documents?: {
[documentId: string]: {
accessRole?: UserPermissionAccessRole;
expiresAt?: number;
error?: string;
}
}
}
}
enum UserPermissionAccessRole {
EDITOR = 'editor',
VIEWER = 'viewer',
}
// Using API
await Velt.getUserPermissions(request as GetUserPermissionsRequest);
```
### New Features
* \[**Comments**]: Updated default placeholders and added props to customize placeholders for comment and reply inputs. Added wireframes to allow users to customize how it looks. [See wireframe customization →](/ui-customization/features/async/comments/comment-dialog-components#composerinput)
* Default placeholders:
* Comment: "Comment or add others with @"
* Reply: "Reply or add others with @"
* \[**Comments**]: Added hook to set and get UI state.
```jsx theme={null}
const { uiState, setUiState } = useUiState();
useEffect(() => {
console.log('uiState: ', uiState);
}, [uiState]);
setUiState({ a: 1, b: 2 })}>Set Data
setUiState({ a: null, b: null })}>Set Data Null
```
```html theme={null}
```
* \[**Comments**]: Added Edited chip in comment thread: Edits are now visibly transparent at a glance, improving auditability and helping reviewers quickly spot updated messages in long discussions. See [Comment Dialog components](/ui-customization/features/async/comments/comment-dialog-components).
**New wireframes added to customize "Edited" Chip:** [See wireframe customization →](/ui-customization/features/async/comments/comment-dialog-components#threadcardedited)
### Bug Fixes
* \[**Comments**]: In `InlineCommentsSection` single-thread mode, count now reflects the number of Comments vs Comment Annotations.
* \[**Recorder**]: Fixed an issue where longer recordings were not rendering the preview correctly in recording player and editor.
* \[**Recorder**]: Fixed an issue where the host app was not clickable when screen recording was used in floating mode.
### Improvements
* \[**Single Editor Mode**]: Added additional debugging logs for Single Editor Mode.
### Bug Fixes
* \[**UI**]: Scoped `.cdk-overlay-pane` CSS into `.velt-overlay-panel` class to avoid global style leakage.
### New Features
* \[**Comments**]: Added clickable links of text selection in comments and a link callback to handle link clicks programmatically.
```jsx theme={null}
// Link callback in comments
// Hook
const linkEvent = useCommentEventCallback('linkClicked');
useEffect(() => {
if (linkEvent) {
// Navigate with your router here
console.log('linkClicked', linkEvent);
}
}, [linkEvent]);
```
```html theme={null}
```
* \[**Recorder**]: Added `recorderId` prop to Video Editor.
```jsx theme={null}
// Video Editor
```
```html theme={null}
```
* \[**Comments**]: Slack-style link pasting: select text and paste a link to directly convert it into a clickable hyperlink. This speeds up authoring and provides a familiar UX for users.
### Improvements
* \[**Comments**]: Removed translation from group name in sidebar to allow special characters.
### Bug Fixes
* \[**Recorder**]: Fixed `recorderId` change detection in video player.
* \[**Recorder**]: Fixed thread mode UI for screen recordings.
### New Features
* \[**Comments**]: Added group support for custom lists. Useful for workflows like issue trackers (e.g., group by "Priority" or "Status"), allowing users to quickly refer and insert custom entities from your app. With this feature, you can combine multiple entity types into one drop down just like Linear. See full examples in Customize Behavior → [Create custom list data on comment](/async-collaboration/comments/customize-behavior#createcustomlistdataoncomment).
```ts theme={null}
const customListWithGroups = {
hotkey: "$",
type: "custom",
groups: [
{ id: "categories", name: "Categories" },
{ id: "priorities", name: "Priorities" }
],
data: [
// Categories group - 2 items
{
id: "bug_1",
name: "Bug 1",
description: "Bug report 1",
groupId: "categories",
icon: { svg: " " }
},
{
id: "feature_2",
name: "Feature Request 2",
description: "New feature 2",
groupId: "categories",
icon: { url: "https://random.png" }
},
// Priorities group - 2 items
{
id: "high_1",
name: "High Priority 1",
description: "Urgent task 1",
groupId: "priorities",
icon: { url: "https://random.png" }
},
{
id: "medium_2",
name: "Medium Priority 2",
description: "Normal task 2",
groupId: "priorities",
icon: { svg: " " }
},
// Ungrouped items - 2 items
{
id: "misc_1",
name: "Miscellaneous 1",
description: "Other items 1",
icon: { url: "https://random.png" }
},
{
id: "note_2",
name: "Note 2",
description: "General note 2",
icon: { svg: " " }
}
]
};
const commentElement = client.getCommentElement();
commentElement.createCustomListDataOnComment(customListWithGroups);
```
```js theme={null}
const customListWithGroups = {
hotkey: "$",
type: "custom",
groups: [
{ id: "categories", name: "Categories" },
{ id: "priorities", name: "Priorities" }
],
data: [
{ id: "bug_1", name: "Bug 1", description: "Bug report 1", groupId: "categories", icon: { svg: " " } },
{ id: "feature_2", name: "Feature Request 2", description: "New feature 2", groupId: "categories", icon: { url: "https://random.png" } },
{ id: "high_1", name: "High Priority 1", description: "Urgent task 1", groupId: "priorities", icon: { url: "https://random.png" } },
{ id: "medium_2", name: "Medium Priority 2", description: "Normal task 2", groupId: "priorities", icon: { svg: " " } },
{ id: "misc_1", name: "Miscellaneous 1", description: "Other items 1", icon: { url: "https://random.png" } },
{ id: "note_2", name: "Note 2", description: "General note 2", icon: { svg: " " } }
]
};
const commentElement = Velt.getCommentElement();
commentElement.createCustomListDataOnComment(customListWithGroups);
```
### Improvements
* \[**Single Editor Mode**]: Optimized and made Single Editor Mode more robust.
### New Features
* \[**Single Editor Mode**]: Added `updateUserPresence` to send heartbeat data from the host app. This helps Velt detect multi-tab activity and resolve who is the active editor when users switch tabs or devices and will use that as a fallback in rare edge cases if it fails to detect multi‑tab/device presence. Most apps don’t need this; use only if you see ambiguity in who’s the active editor.
```ts theme={null}
export interface LiveStateSingleEditorExternalUserPresence {
/** True if the same user is present on a different tab. */
sameUserPresentOnTab?: boolean;
/** True if a different user is present on a different tab. */
differentUserPresentOnTab?: boolean;
/** User IDs of users present on a different tab. */
userIds?: string[];
}
// API method
const liveStateSyncElement = client.getLiveStateSyncElement();
liveStateSyncElement.updateUserPresence({
sameUserPresentOnTab: false,
differentUserPresentOnTab: true,
userIds: ['user-2']
});
```
```js theme={null}
// API method
const liveStateSyncElement = Velt.getLiveStateSyncElement();
liveStateSyncElement.updateUserPresence({
sameUserPresentOnTab: false,
differentUserPresentOnTab: true,
userIds: ['user-2']
});
```
### Improvements
* \[**Single Editor Mode**]: Added validations in `setUserAsEditor` to set a user only if no editor is currently assigned.
```ts theme={null}
export interface SetUserAsEditorResponse {
error?: ErrorEvent;
}
export type ErrorEvent = {
error?: string;
code: string;
message?: string;
source?: string;
};
// Method signature
// public setUserAsEditor: () => Promise;
// Usage with response handling
const liveStateSyncElement = client.getLiveStateSyncElement();
const result = await liveStateSyncElement.setUserAsEditor();
if (result?.error) {
if (result.error.code === 'same_user_editor_current_tab') {
// Same user is already editor in current tab
} else if (result.error.code === 'same_user_editor_different_tab') {
// Same user is already editor in different tab
} else if (result.error.code === 'another_user_editor') {
// Another user is already editor
}
}
```
```ts theme={null}
// Possible error values
// For same user current tab error
{
code: 'same_user_editor_current_tab',
message: 'Same user is already editor in current tab.',
source: 'setUserAsEditor',
}
// For same user different tab error
{
code: 'same_user_editor_different_tab',
message: 'Same user is already editor in different tab.',
source: 'setUserAsEditor',
}
// For another user error
{
code: 'another_user_editor',
message: 'Another user is already editor.',
source: 'setUserAsEditor',
}
```
```js theme={null}
// Method usage
const liveStateSyncElement = Velt.getLiveStateSyncElement();
const result = await liveStateSyncElement.setUserAsEditor();
if (result && result.error) {
if (result.error.code === 'same_user_editor_current_tab') {
// Same user already editor in current tab
} else if (result.error.code === 'same_user_editor_different_tab') {
// Same user already editor in different tab
} else if (result.error.code === 'another_user_editor') {
// Another user already editor
}
}
```
* \[**Single Editor Mode**]: Optimized and made Single Editor Mode more robust.
### New Features
* \[**Access Control**]: Added support for viewer and editor roles for permanent and temporary users.
**Editor vs Viewer roles**
* **Editor**: Full read/write access to collaboration data on the resource (create/edit/delete comments and replies, add reactions, start recordings, etc.).
* **Viewer**: Read-only access to collaboration data (view comments and replies, presence, cursors, recordings, notifications) without the ability to modify.
Why this matters: You can enforce read-only experiences, safely grant temporary edit windows, and align Velt features with your app’s permission model.
For implementation details, see the v2 Auth APIs:
* Generate Token: [/api-reference/rest-apis/v2/auth/generate-token](/api-reference/rest-apis/v2/auth/generate-token)
* Add Permissions: [/api-reference/rest-apis/v2/auth/add-permissions](/api-reference/rest-apis/v2/auth/add-permissions)
`accessRole` is configurable only via the v2 Users and Auth Permissions REST APIs. Frontend SDK methods do not accept or change `accessRole`.
Relevant endpoints:
* [/api-reference/rest-apis/v2/users/add-users](/api-reference/rest-apis/v2/users/add-users)
* [/api-reference/rest-apis/v2/users/update-users](/api-reference/rest-apis/v2/users/update-users)
* [/api-reference/rest-apis/v2/auth/add-permissions](/api-reference/rest-apis/v2/auth/add-permissions)
* [/api-reference/rest-apis/v2/auth/generate-token](/api-reference/rest-apis/v2/auth/generate-token)
See the [Access Control overview](/key-concepts/overview#access-control) for concepts and detailed guidance.
### Improvements
* \[**Single Editor Mode**]: Optimized and made Single Editor Mode more robust.
### New Features
* \[**Comments**]: Launched a purpose built library for adding comments to Lexical Editor. [Learn more](/async-collaboration/comments/setup/lexical).
### Improvements
* \[**Comments**]: Optimized how comment read/unread state is updated internally.
### Bug Fixes
* \[**UI Customization**]: Fixed an issue where `Velt Button` was not passing internal state to its children in some scenarios.
### Bug Fixes
* \[**Core**]: Resolved a race condition in resolving user data. Applied the same fix that was added to the `4.5.0-beta.66-patch.1` version.
Note after this update, if you plan to go back to older version of the SDK before `4.5.0-beta.43`, then please reach out to Velt Support for a graceful rollback.
### Bug Fixes
* \[**Core**]: Resolved a race condition in resolving user data.
### Improvements
* \[**Comments**]: Updated comment annotation count subscription. It now supports getting total and unread Comment Annotations count across Organization, Folder, Document and Multiple Documents levels.
```jsx theme={null}
// Organization level
const commentElement = client.getCommentElement();
commentElement.getCommentAnnotationsCount({ organizationId: 'org1' }).subscribe((data) => {
console.log('getCommentAnnotationsCount with organizationId', data);
});
// Folder level
commentElement.getCommentAnnotationsCount({ organizationId: 'org1', folderId: 'folder2', allDocuments: true }).subscribe((data) => {
console.log('getCommentAnnotationsCount with organizationId and folderId and allDocuments', data);
});
// Document Level
commentElement.getCommentAnnotationsCount().subscribe((data) => {
console.log('getCommentAnnotationsCount', data);
});
// Aggregate Across Multiple Documents
commentElement.getCommentAnnotationsCount({ aggregateDocuments: true }).subscribe((data) => {
console.log('getCommentAnnotationsCount with aggregateDocuments', data);
});
```
```js theme={null}
// Organization level
const commentElement = Velt.getCommentElement();
commentElement.getCommentAnnotationsCount({ organizationId: 'org1' }).subscribe((data) => {
console.log('getCommentAnnotationsCount with organizationId', data);
});
// Folder level
commentElement.getCommentAnnotationsCount({ organizationId: 'org1', folderId: 'folder2', allDocuments: true }).subscribe((data) => {
console.log('getCommentAnnotationsCount with organizationId and folderId and allDocuments', data);
});
// Document Level
commentElement.getCommentAnnotationsCount().subscribe((data) => {
console.log('getCommentAnnotationsCount', data);
});
// Aggregate Across Multiple Documents
commentElement.getCommentAnnotationsCount({ aggregateDocuments: true }).subscribe((data) => {
console.log('getCommentAnnotationsCount with aggregateDocuments', data);
});
```
Note after this update, if you plan to go back to older version of the SDK before `4.5.0-beta.43`, then please reach out to Velt Support for a graceful rollback.
### New Features
* \[**Comments**]: Added support to filter out ghost comments when retrieving comment annotations count.
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.getCommentAnnotationsCount({ filterGhostComments: true }).subscribe((data) => {
console.log('getCommentAnnotationsCount with filterGhostComments', data);
});
```
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.getCommentAnnotationsCount({ filterGhostComments: true }).subscribe((data) => {
console.log('getCommentAnnotationsCount with filterGhostComments', data);
});
```
### New Features
* \[**Recorder**]: Added API to programmatically request audio and video permissions from the user.
```jsx theme={null}
const recorderElement = useRecorderUtils();
recorderElement.askDevicePermission({
audio: true,
video: true
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.askDevicePermission({
audio: true,
video: true
});
```
### Improvements
* \[**UI Customization**]: Added an alternative approach to evaluate `Velt If` conditions. The default method could be blocked by some strict Content Security Policies (CSP) that disallow `unsafe-eval`.
```javascript theme={null}
client.enableSafeEval();
client.disableSafeEval();
```
```javascript theme={null}
Velt.enableSafeEval();
Velt.disableSafeEval();
```
* \[**Multiplayer Editing**]: Now you can encrypt CRDT data before it’s stored in Velt by registering a custom encryption provider. For CRDT methods, input data is of type `Uint8Array | number[]`. [Learn more](/realtime-collaboration/crdt/setup/core#custom-encryption)
### Improvements
* \[**Comments**]: Added `filterGhostCommentsInSidebar` config in comment sidebar to hide ghost comments from sidebar. Default: `false`
**Using Props:**
```jsx theme={null}
// Use either of the following
```
**Using APIs:**
```javascript theme={null}
const commentElement = client.getCommentElement();
commentElement.enableFilterGhostCommentsInSidebar();
commentElement.disableFilterGhostCommentsInSidebar();
```
**Using Props:**
```html theme={null}
```
**Using APIs:**
```javascript theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableFilterGhostCommentsInSidebar();
commentElement.disableFilterGhostCommentsInSidebar();
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the `recordingEditDone` event was not firing correctly under certain conditions.
### New Features
* \[**Notifications**]: Added wireframes to modify the title text of the Velt Notification Panel.
* \[**Video Editor**]: Introduced the `VeltVideoEditor`, a new embeddable component for viewing and editing video recordings directly in your application. It can be initialized using a URL, blob, or recorder ID.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Auth**]: When re-authentication occurs after a token expires, `setDocuments` now automatically restores the previous set of documents to maintain session continuity.
### New Features
* \[**Auth**]: Introduced `authProvider` for more robust and flexible authentication management. This includes automatic token refresh with configurable retry logic. [Learn more](/key-concepts/overview#sign-in-a-user).
* \[**Access Control**]: Added a new permissions API to [grant](/api-reference/rest-apis/v2/auth/add-permissions) and [revoke](/api-reference/rest-apis/v2/auth/remove-permissions) user permissions dynamically on demand.
* Grant permissions on demand vs syncing users to Velt.
* Grant temporary time based permissions or permanent permissions to Organizations, Folders and Documents.
* [Learn more](/key-concepts/overview#access-control).
### New Features
* \[**Multiplayer Editing**]: Introduced versioning support for CRDT stores. You can now save snapshots of your collaborative data, list previously saved versions, and restore the store to a specific version. This feature is currently supported for `text` and `array` data types.
### Improvements
* \[**Recorder**]: Expanded recorder functionality with a comprehensive set of lifecycle events, allowing you to build more integrated and responsive recording experiences. The following events are now available:
* `recordingEditDone`: Triggered when the "Done" button is clicked in the recording editor. Fires after edits are saved, or immediately if no edits were made.
* `recordingStarted`: Triggered when the recording starts.
* `recordingPaused`: Triggered when the recording is paused.
* `recordingCancelled`: Triggered when the recording is cancelled.
* `recordingStopped`: Triggered when the recording is stopped.
* `recordingResumed`: Triggered when the recording is resumed after being paused.
### New Features
* \[**Auth**]: Introduced a new streamlined authentication flow to simplify user session management within Velt.
### New Features
* \[**Auth**]: Added support for on-demand access control. Instead of syncing users to Velt, you can now grant permanent or temporary access to resources (organizations, folders, documents) to users on the fly.
* If you pass `organizationId` in the `identify` method and set `requireJwtToken` to `true` (in console), you **must** also include access to the same `organizationId` in the `resources` array of your permissions. Otherwise, it will throw an error.
```ts theme={null}
const authRequest: VeltAuthTokenRequest = {
apiKey: "API_KEY",
userId: "USER_ID",
userProperties: {
isAdmin: false,
name: "USER_NAME",
email: "USER_EMAIL"
},
permissions: {
resources: [
{
type: "organization",
id: "ORGANIZATION_ID"
},
{
type: "document",
id: "DOCUMENT_ID",
organizationId: "ORGANIZATION_ID",
expiresAt: Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
}
]
}
};
try {
const response = await fetch('https://api.velt.dev/v2/auth/generate_token', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(authRequest),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseJson = await response.json();
const token = responseJson?.result?.data?.token;
return token;
} catch (error) {
console.error("Error:", error);
}
```
```ts theme={null}
const token = await __yourBackendEndpointToGenerateToken__();
await client.identify(user, {
authToken: token,
})
```
* \[**Multiplayer Editing (CRDT)**]: Launched Yjs based CRDT libraries for:
* `@veltdev/crdt`: Framework-agnostic CRDT stores (array, map, text, xml), versioning, subscriptions, and syncing via the Velt client.
* `@veltdev/crdt-react`: React Hook wrapper for CRDT integration.
* `@veltdev/tiptap-crdt`: Purpose built CRDT library for Tiptap Editor to enable multiplayer editing.
### Improvements
* \[**REST APIs**]: Added support for `context` field on:
* `Comment` `add` and `update` APIs: Now you can pass `context` field at the comment level.
* `CommentAnnotation` `add` API: Now you can pass `context` field not only at the comment annotation level but also at the comment level.
### Bug Fixes
* \[**Recorder**]: Fixed edge cases in Firefox where devices were not appearing in settings, video sometimes remained off during video recording, and screen recording would not start until the browser was focused.
* \[**Recorder**]: Fixed an issue where the author was incorrectly shown for audio recordings in floating player.
* \[**Notifications**]: Fixed an issue where the Notification Panel and Comments Sidebar would not close if the document was not set.
* \[**Seen By**]: Fixed an issue where the "Seen by" list incorrectly included the author.
### Improvements
* \[**Comments Sidebar**]: Added `sidebarButtonCountType` config to control the count shown on the sidebar button.
* `default`: Shows the total count of comments in open and in progress states.
* `filter`: Shows the count of filtered comments.
**Using Props:**
```jsx theme={null}
```
**Using APIs:**
```jsx theme={null}
const commentElement = useCommentUtils();
commentElement.setSidebarButtonCountType('filter');
```
**Using Props:**
```html theme={null}
```
**Using APIs:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.setSidebarButtonCountType('filter');
```
### Improvements
* \[**User**]: Avatar colors are now generated consistently across frontend and backend when not set manually.
* \[**Self-hosting**]: Now we are returning author `userId` in comment and reaction resolver APIs for more flexible querying and filtering.
### Improvements
* \[**Core**]: Added internal optimzations for realtime data fetching.
### New Features
* \[**Comments**]: Added "Involved" filter to the comment sidebar to easily find comments where a user is author, mentioned, or assigned.
### Bug Fixes
* \[**Recorder**]: Fixed issues with audio/video capture and permission prompts in Firefox.
### Improvements
* \[**Notifications**]: Added an option to filter notifications based on type in the `getNotificationsData` method.
* Now this method/hook can accept an optional `GetNotificationsDataQuery` object, which supports a `type` property:
* `'all'` (default): returns all notifications from the documents the user has access to.
* `'forYou'`: returns notifications where the current user is involved.
* `'documents'`: returns notifications for documents where the current user has access to.
```jsx theme={null}
const notificationsData = useNotificationsData({ type: 'forYou' });
useEffect(() => {
if (notificationsData) {
console.log(notificationsData);
}
}, [notificationsData]);
```
```js theme={null}
const notificationElement = Velt.getNotificationElement();
notificationElement.getNotificationsData({ type: 'forYou' }).subscribe((notifications) => {
console.log(notifications);
});
```
### Bug Fixes
* \[**Comments**]: Fixed an issue with aggregated comments dialog, where hitting the reply button would close the dialog.
* \[**Notifications**]: Fixed empty state issue in the notification API where the data would first return an empty array before returning the actual data.
### Bug Fixes
* \[**Comments**]: Fixed an issue with the comment tool in popover mode where the tool would not hide sometimes even when the comment was added.
### Improvements
* \[**Video Editor**]: Added robustness for edge cases with short videos (\< 2-3s) that could cause unexpected behavior in the editor. eg: zooming, trimming, etc.
* \[**Video Editor**]: Added robustness to playhead action buttons group and tooltip display and position logic for edge cases.
### Improvements
* \[**Core**]: Added internal optimzations for realtime data fetching.
### Updates
* \[**Huddle**]: Added `serverFallback` config in huddle. By default Huddle service is peer-to-peer. This will enable or disable the fallback to server-side for connectivity if peer-to-peer fails.
Default: `true`
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Notifications**]: Updated `documentName` rendering logic in the notification panel to only render the component if the `documentName` is available. Earlier it was falling back to the `documentId`.
* \[**Notifications**]: Added support to open and close the notification panel using APIs.
```jsx theme={null}
const notificationElement = useNotificationUtils()
notificationElement.openNotificationsPanel()
notificationElement.closeNotificationsPanel()
```
```html theme={null}
const notificationElement = Velt.getNotificationElement();
notificationElement.openNotificationsPanel();
notificationElement.closeNotificationsPanel();
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where DOM changes detection was not working for text comments in some scenarios.
* \[**Video Editor**]: Hide video editor tooltips and selection during playback.
* \[**Notifications**]: Fixed an issue where notification panel was not closing when clicked on notification tool.
### New Features
* \[**Video Editor**]: Updated video editor with new UI design for improved UX including tooltips, floating action buttons, active/inactive states, etc.
* \[**Video Editor**]: Added a retake button on video editor.
**Using Props (use any one of the following):**
```jsx theme={null}
```
**Using APIs:**
```jsx theme={null}
const recorderElement = useRecorderUtils();
// Enable/disable retake button on video editor
recorderElement.enableRetakeOnVideoEditor();
recorderElement.disableRetakeOnVideoEditor();
```
**Using Attributes (use any one of the following):**
```html theme={null}
```
**Using APIs:**
```jsx theme={null}
const recorderElement = Velt.getRecorderElement();
// Enable/disable retake button on video editor
recorderElement.enableRetakeOnVideoEditor();
recorderElement.disableRetakeOnVideoEditor();
```
* \[**Video Editor**]: Added onboarding tooltip for new users.
**Using APIs:**
```jsx theme={null}
const recorderElement = useRecorderUtils();
// Enable/disable onboarding tooltip
recorderElement.enableOnboardingTooltip();
recorderElement.disableOnboardingTooltip();
```
**Using APIs:**
```jsx theme={null}
const recorderElement = Velt.getRecorderElement();
// Enable/disable onboarding tooltip
recorderElement.enableOnboardingTooltip();
recorderElement.disableOnboardingTooltip();
```
### Improvements
* \[**Core**]: Added central DB for user metadata. Going forward, if user metadata changes via REST API or via identify method, it will be reflected across all data rendered in the UI.
Note after this update, if you plan to go back to older version of the SDK before `4.5.0-beta.43`, then please reach out to Velt Support for a graceful rollback.
### Bug Fixes
* \[**Comments**]: Fixed the issue where context was not applied to freestyle area comments.
### New Features
* \[**Comments**]: Added support to filter and aggregate comment annotations by `locationId` in `InlineCommentsSection`, `VeltCommentPin` and `VeltCommentBubble` components. `folderId` or `documentId` is required.
**Inline Comments Section:**
```jsx theme={null}
// folderId + locationId
// documentId + locationId
```
**Comment Tool - Comment Bubble for popover comments:**
```jsx theme={null}
// folderId + locationId
// documentId + locationId
```
**Comment Pin:**
```jsx theme={null}
// folderId + locationId
// documentId + locationId
```
**Inline Comments Section:**
```html theme={null}
```
**Comment Tool - Comment Bubble:**
```html theme={null}
```
**Comment Pin:**
```html theme={null}
```
* \[**Comments**]: Add ability to filter and aggregate comments by mulitple conditions: `folderId`/`documentId`/`locationId` and `context`. Only those comment annotations that satisfy all the conditions will be visible.
**Inline Comments Section:**
```jsx theme={null}
```
**Comment Tool - Comment Bubble:**
```jsx theme={null}
```
**Comment Pin:**
```jsx theme={null}
```
**Inline Comments Section:**
```jsx theme={null}
```
**Comment Tool - Comment Bubble:**
```jsx theme={null}
```
**Comment Pin:**
```jsx theme={null}
```
* \[**Comments**]: Added support to add context to comment tool in freestyle mode.
* \[**Core**]: Added window load event for Velt object after it is initialized. This is useful for developers using pure HTML/JS packages.
```jsx theme={null}
window.addEventListener('onVeltLoad', (event) => {
console.log('onVeltLoad', window.Velt);
});
```
### Bug Fixes
* \[**Comments**]: Fixed issue where bubble count was showing the total comments count instead of the non-terminal state comments count.
* \[**Comments**]: Fixed issue where area comments were not respecting dom id boundaries.
### New Features
* \[**Notifications**]: Added support to enable self notifications. By default notifications api and components exclude notifications where the current user is the action user.
**Using props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const notificationElement = useNotificationUtils();
notificationElement.enableSelfNotifications(); // Enables self notifications.
notificationElement.disableSelfNotifications(); // Disables self notifications.
```
**Using attributes:**
```html theme={null}
```
**Using API:**
```jsx theme={null}
const notificationElement = Velt.getNotificationElement();
notificationElement.enableSelfNotifications(); // Enables self notifications.
notificationElement.disableSelfNotifications(); // Disables self notifications.
```
### Bug Fixes
* \[**Comments**]: Fixed issue where comment text and HTML content was not being properly stripped during deletion for self-hosted data.
### New Features
* \[**CRDT**]: Added Yjs based CRDT library (\`@veltdev/tiptap-crdt) for Tiptap Editor (in beta).
* This enables live cursors and collaborative editing out of the box.
* This is framework agnostic and can be used with any Tiptap editor.
* [Learn more](/realtime-collaboration/crdt/setup/tiptap)
* \[**Video Editor**]: Added zoom button in video editor with [wireframes support](/ui-customization/features/async/recorder/video-editor#addzoombutton).
* \[**Recorder**]: Added API to turn on/off the microphone on the recorder control panel.
**Using API:**
```jsx theme={null}
const recorderElement = useRecorderUtils();
recorderElement.enableRecordingMic(); // Enables the microphone.
recorderElement.disableRecordingMic(); // Disables the microphone.
```
**Using API:**
```jsx theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.enableRecordingMic(); // Enables the microphone.
recorderElement.disableRecordingMic(); // Disables the microphone.
```
### Improvements
* \[**Video Editor**]: Enabled zoom options dropdown to be positioned at the center of the zoom track.
* \[**Comments**]: Enabled standalone comment composer width to be set to 100% of the parent container.
### New Features
* \[**Comments**]: Added reply avatar component with [wireframes support](/ui-customization/features/async/comments/comment-dialog/subcomponents/body/subcomponents/replyavatars). This shows the avatars of unique users who have replied to a comment.
**Using props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentsElement = client.getCommentElement();
commentsElement.enableReplyAvatars(); // Enables the reply avatar component.
commentsElement.disableReplyAvatars(); // Disables the reply avatar component.
commentsElement.setMaxReplyAvatars(2); // Sets the maximum number of reply avatars to show.
```
**Using props:**
```html theme={null}
```
**Using API:**
```jsx theme={null}
const commentsElement = Velt.getCommentElement();
commentsElement.enableReplyAvatars(); // Enables the reply avatar component.
commentsElement.disableReplyAvatars(); // Disables the reply avatar component.
commentsElement.setMaxReplyAvatars(2); // Sets the maximum number of reply avatars to show.
```
* \[**Comments**]: Added template variable `totalReplies` for comment wireframes. It displays the total number of replies for a comment.
### Bug Fixes
* \[**Comments**]: Fixed locationName logic to use locationId in sidebar for 'This Page' filter.
* \[**Comments**]: Fixed "@" dropdown issue where it was not opening after user selection.
### New Features
* \[**Cursors**]: Added wireframes support for `Cursors` component.
### Improvements
* \[**Comments**]: Added sidebar filter dropdown into overlays for better handling of screen and UI edges.
### Bug Fixes
* \[**Comments**]: Fixed an issue where inline comments section width was not being set correctly.
### New Features
* \[**Core**]: Added MCP Servers for:
* **Velt REST APIs**: Allows you to interact with your Velt data directly from various MCP compatible clients, such as Cursor, Windsurf, Claude Desktop, etc. [Learn More](/mcp/mcp).
* **Velt Docs**: Allows you to search and browse Velt's documentation directly within your MCP-compatible IDEs, like Cursor, Windsurf etc. This enables you to quickly find information about our SDKs, APIs, and features without having to leave your editor, streamlining your development workflow when implementing Velt. [Learn More](/mcp/mcp).
### Bug Fixes
* \[**Comments**]: Minor UX fixes:
* Fixed an issue where composer auto-scroll was not working in sidebar embed mode.
* The composer now auto-scrolls to the text cursor when a long message is pasted.
* Mentions are now added at the cursor's position, and the cursor is placed after the mention, not at the end of the input.
### Improvements
* \[**Comments**]: Comment timestamps now show the created timestamp by default, and the last updated timestamp as fallback.
* \[**Core**]: Added improved error messages for identify method so it's easier to debug scenarios like domain mismatch, token expired, organization mismatch, missing token etc.
* \[**Comments**]: Added tooltips to an overlay.
### New Features
* \[**REST APIs**]: Extended v2 endpoints for all APIs. These come with additional schema validation.
* The previous v1 endpoints will work as usual.
### Bug Fixes
* \[**Core**]: Fixed an issue where location metadata, reset etc was not working as expected in some scenarios.
### Bug Fixes
* \[**Comments**]: Minor UX fixes:
* Fixed mention dropdown opening when typing space at the very start of input.
* Fixed mention dropdown opening when @ was added at the end of a word. Exceptions: (`,`, `;`, `:`).
* Updated custom list dropdowns to behave like the default @ dropdown.
* Fixed `ESC` key not closing the mentions dropdown.
* When clicking on collapsed composer, it will bring it into view.
* The composer will auto-scroll to the text cursor when the inputted text is very long.
* Comment dialog will come into view when returning from focused thread mode.
* Fixed an issue where custom filter was getting reset on page refresh.
* Fixed search placeholder type in React ` `.
* Fixed composer collapsing when clicking away with text or attachments present - now only collapses when empty.
### New Features
* \[**Debugger**]: Added [Activity Logs](https://console.velt.dev/dashboard/debugger/activity-logs) to the Console.
* Review the historical Velt events stream of your users.
* This can help debug issues, user behaviour on Velt features, etc.
* Currently supports the last 30 days of events.
* There is a 24-48 hour delay in the events being shown in the console.
### Improvements
* \[**Comments**]: Improved autoscroll behavior in comment sidebar for new comment additions, comment dialog clicks, and composer clicks.
* \[**Comments**]: Added `system` type support in `filterConfig` for default filters
### Bug Fixes
* \[**Comments**]: Fixed autocomplete dropdown opening while typing `@` in a word.
* \[**Comments**]: Fixed autocomplete dropdown not closing after `@` followed by immediate space.
* \[**Comments**]: Fixed comment sidebar filter type menu closing on outside clicks.
* \[**Comments**]: Fixed comment sidebar filter search toggle functionality.
### Improvements
* \[**Comments**]: Added support to aggregate and show comment annotations by `folderId`, `documentId` and `context` in `InlineCommentSection`, `VeltCommentPin`, `VeltCommentTool` and `VeltCommentBubble` components.
* All the comment annotations matching these props will be aggregated and shown in the component.
* This will only work when the documents or the relevant folder are initialized first.
**Inline Comments Section:**
```jsx theme={null}
// Filter by folderId
// Filter by documentId
// Filter by context
```
**Comment Tool (Works only in popover mode). This will attach these props to the new comment annotation created by the tool.**
```jsx theme={null}
// Filter by folderId
// Filter by documentId
// Filter by context
```
**Comment Bubble:**
```jsx theme={null}
// Filter by folderId
// Filter by documentId
// Filter by context
```
**Comment Pin:**
```jsx theme={null}
// Filter by folderId
// Filter by documentId
// Filter by context
```
**Inline Comments Section:**
```html theme={null}
```
**Comment Tool:**
```html theme={null}
```
**Comment Bubble:**
```html theme={null}
```
**Comment Pin:**
```html theme={null}
```
### Improvements
* \[**Notifications**]: Modified `getSettings` in notifications from a one-time call to a subscription and added a new hook for it.
**Using Hook:**
```jsx theme={null}
const { setSettingsInitialConfig, setSettings, settings } = useNotificationSettings();
// setSettingsInitialConfig will set the default settings for the notifications.
// setSettings will update the settings for the notifications in the server.
// settings will return the current settings for the notifications. Initially it will be null and will be updated when the settings are fetched.
```
**Using API:**
```jsx theme={null}
const notificationsElement = client.getNotificationElement();
notificationsElement.getSettings().subscribe((settings) => {
console.log('current settings are', settings);
});
```
**Using API:**
```jsx theme={null}
const notificationsElement = Velt.getNotificationElement();
notificationsElement.getSettings().subscribe((settings) => {
console.log('current settings are', settings);
});
```
* \[**Documents**]: Added support for `folderId` in `updateDocuments` method.
```jsx theme={null}
await client.updateDocuments({
organizationId: 'ORGANIZATION_ID',
folderId: 'FOLDER_ID',
documents: [
{
documentId: 'DOCUMENT_ID',
}
]
});
```
```jsx theme={null}
await Velt.updateDocuments({
organizationId: 'ORGANIZATION_ID',
folderId: 'FOLDER_ID',
documents: [
{
documentId: 'DOCUMENT_ID',
}
]
});
```
### Bug Fixes
* \[**UI Customization**]: Fixed an issue where the internal state for User variable was not being cleaned up in Velt If component.
### Improvements
* \[**Comments**]: Added support for multiple text format selection and multiple paragraphs selection in [tiptap-velt-comments](https://www.npmjs.com/package/@veltdev/tiptap-velt-comments).
### Bug Fixes
* \[**Comments**]: Reverted back sidebar filter panel default layout to checkbox from dropdown.
### Improvements
* \[**Comments**]: Added default value for sidebar filter search input.
* \[**Recorder**]: Updated the video editor apply button behavior. Now it will always be enabled whether or not the user edited the video.
* \[**Comments**]: Added ability to add custom filters via `FilterConfig`.
1. First enable custom actions.
2. Add custom filters in filter config.
3. Add [custom filters wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/custom) to the sidebar wireframe.
4. Add callbacks to update sidebar data when the filters are used.
**1. Enable Custom Actions:**
```jsx theme={null}
```
**2. Add custom filters in filter config:**
```jsx expandable lines theme={null}
const filterConfig = {
location: {
name: 'Pages',
enable: true,
multiSelection: true,
enableGrouping: true,
placeholder: 'Search pages',
},
// ... Remaining filters
deviceType: { // Custom Filter 1
name: 'Device Type',
enable: true,
multiSelection: true,
enableGrouping: false,
placeholder: 'Search device type',
id: 'deviceType',
type: 'custom',
options: [
{
id: 'desktop',
name: 'Desktop',
},
{
id: 'mobile',
name: 'Mobile',
},
{
id: 'tablet',
name: 'Tablet',
},
]
},
otherCustomFilter: { // Custom Filter 2
name: 'Other Custom Filter',
enable: true,
multiSelection: false,
enableGrouping: false,
placeholder: 'Search any field',
id: 'otherCustomFilter',
type: 'custom',
options: [
{
id: 'field1',
name: 'Field 1',
},
{
id: 'field2',
name: 'Field 2',
},
{
id: 'field3',
name: 'Field 3',
},
{
id: 'field4',
name: 'Field 4',
},
{
id: 'field5',
name: 'Field 5',
},
]
},
};
```
**3. Add [custom filters wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/custom) for each custom filter you added in filter config:**
```jsx theme={null}
{/* ... remaining filters */}
```
**4. Add callbacks to update sidebar data when the filters are used:**
* The following callbacks will contain the `customFilters` field with the custom filter id and its options indicating which options are selected:
* `commentSidebarDataUpdate`
* `commentSidebarDataInit`
* `veltButtonClick`
```jsx theme={null}
const commentElement = client.getCommentElement();
// Button click callback
client.on('veltButtonClick').subscribe(contextData => {
console.log('veltButtonClick', contextData);
});
// Sidebar data callbacks
commentElement.on('commentSidebarDataUpdate').subscribe((contextData) => {
console.log('commentSidebarDataUpdate', contextData);
});
commentElement.on('commentSidebarDataInit').subscribe((contextData) => {
console.log('commentSidebarDataInit', contextData);
});
```
**1. Enable Custom Actions:**
```html theme={null}
```
**2. Add Custom Filters:**
```javascript expandable lines theme={null}
const filterConfig = {
location: {
name: 'Pages',
enable: true,
multiSelection: true,
enableGrouping: true,
placeholder: 'Search pages',
},
// ... other default filters
deviceType: { // Custom Filter 1
name: 'Device Type',
enable: true,
multiSelection: true,
enableGrouping: false,
placeholder: 'Search device type',
id: 'deviceType',
type: 'custom',
options: [
{
id: 'desktop',
name: 'Desktop',
},
{
id: 'mobile',
name: 'Mobile',
},
{
id: 'tablet',
name: 'Tablet',
},
]
},
anyField: { // Custom Filter 2
name: 'Any Field',
enable: true,
multiSelection: false,
enableGrouping: false,
placeholder: 'Search any field',
id: 'anyField',
type: 'custom',
options: [
{
id: 'field1',
name: 'Field 1',
},
{
id: 'field2',
name: 'Field 2',
},
{
id: 'field3',
name: 'Field 3',
},
{
id: 'field4',
name: 'Field 4',
},
{
id: 'field5',
name: 'Field 5',
},
]
},
};
const commentsSidebar = document.querySelector(`velt-comments-sidebar`);
commentsSidebar?.setAttribute("filter-config", JSON.stringify(filterConfig));
```
**3. Add custom filters wireframe to the sidebar wireframe:**
```html theme={null}
```
**4. Add callbacks to update sidebar data when the filters are used:**
```javascript theme={null}
const commentElement = Velt.getCommentElement();
// Button click callback
client.on('veltButtonClick').subscribe(contextData => {
console.log('veltButtonClick', contextData);
});
// Sidebar data callbacks
commentElement.on('commentSidebarDataUpdate').subscribe((contextData) => {
console.log('commentSidebarDataUpdate', contextData);
});
commentElement.on('commentSidebarDataInit').subscribe((contextData) => {
console.log('commentSidebarDataInit', contextData);
});
```
### Improvements
* \[**Comments**]: Renamed `groupMultipleMatch` config to `groupMatchedComments` config for grouping multiple comment annotations in Comment Bubble component when multiple annotations match the provided `context` or `targetElementId`. The old config is still supported.
* Default: `false`
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableGroupMatchedComments();
commentElement.disableGroupMatchedComments();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableGroupMatchedComments();
commentElement.disableGroupMatchedComments();
```
### New Features
* \[**Presence**]: Added wireframes support for `Presence` component.
### Improvements
* \[**Comments**]: Added support for setting custom filter placeholders through `filterConfig`. This is used when `filterOptionLayout` is set to `dropdown`.
```jsx theme={null}
const filterConfig = {
location: {
name: 'Pages',
enable: true,
placeholder: 'Custom Location'
},
document: {
name: 'Documents',
enable: true,
placeholder: 'Custom Document'
}
};
```
```js theme={null}
const filterConfig = {
location: {
name: 'Pages',
enable: true,
placeholder: 'Custom Location'
},
document: {
name: 'Documents',
enable: true,
placeholder: 'Custom Document'
}
};
const commentsSidebar = document.querySelector(`velt-comments-sidebar`);
commentsSidebar?.setAttribute("filter-config", JSON.stringify(filterConfig));
```
* \[**Comments**]: Added a `filteredCommentAnnotationsCount` template variable for visible comments in sidebar when a filter is applied.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the assign input was not closing when clicked outside in the sidebar.
* \[**Comments**]: Fixed an issue where the reaction panel tool was getting hidden when hovered out.
* \[**Comments**]: Fixed an issue where users set through `updateContactList` were not visible in tagged and assigned filters.
### New Features
* \[**Comments**]: Added support for grouping multiple comment annotations in Comment Bubble component when multiple annotations match the provided `context` or `targetElementId`
* Default: `false`
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableGroupMultipleMatch();
commentElement.disableGroupMultipleMatch();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableGroupMultipleMatch();
commentElement.disableGroupMultipleMatch();
```
### Bug Fixes
* \[**Comments**]: Fixed an issue that happened on sidebar where the recording service got into a bad state when the user cancelled the recording.
### Improvements
* \[**Comments**]: Added support for `filterCommentsOnDom` when custom sidebar filters are configured.
* \[**Comments**]: Added additional testIds in following components:
* Message input (element with class `velt-composer-input--message`)
* Container for author (element with class `velt-thread-card--author`)
* Confirmation dialog (element with class `velt-confirm-dialog`)
* Velt buttons (elements with class `velt-button`)
* \[**Comments**]: Removed quotes from the display message when `notificationSource` is `custom`.
### Improvements
* \[**Core**]: Added `unsetLocationsIds` method to unset locations by passing location IDs as parameters
```jsx theme={null}
client.unsetLocationsIds(['location1', 'location2', 'location3'])
```
### Improvements
* \[**Core**]: Improved SDK initialization layer and added new core methods to improve developer experience:
* Added `rootDocumentId` param in `setDocuments` method to specify which document should act as root. By default 1st document will be set as root document unless `rootDocumentId` is specified.
```jsx theme={null}
client.setDocuments([{id:'doc1'},{id:'doc2'}], {rootDocumentId:'doc2'})
```
* Added `setRootDocument` method to set a different document as root. If user has passed multiple documents and wants to switch the root document:
```jsx theme={null}
client.setRootDocument({id:'doc2'})
// There won't be any change in the other documents internally. They will remain as is.
```
* Added `setLocations` method to add multiple locations:
```jsx theme={null}
client.setLocations([
{id:'location1', locationName:'location1'},
{id:'location2', locationName:'location2'}
], {rootLocationId: 'location2'})
// By default 1st location will be set as root location unless rootLocationId is specified.
// If User wants to append new locations then 'appendLocation' flag needs to be passed.
client.setLocations([
{id:'location3', locationName:'location3'},
{id:'location4', locationName:'location4'}
], {appendLocation: true})
```
* Added `setRootLocation` method to set a different location as root:
```jsx theme={null}
client.setRootLocation({id:'location3', locationName:'location3'})
// There won't be any change in the other locations internally. They will remain as is.
```
* Added `removeLocations` method to remove multiple locations:
```jsx theme={null}
client.removeLocations(
{id:'location3', locationName:'location3'},
{id:'location4', locationName:'location4'}
)
```
* Most init methods now return promises:
* `await setDocuments`
* `await setDocument`
* `await setDocumentId`
* `await setRootDocument`
* `await setLocation`
* `await setLocations`
* `await setRootLocation`
* Added a listener event on `ON` method to subscribe to init changes:
```jsx theme={null}
Velt.on('initUpdate').subscribe((e) => console.log('New init event', e))
```
* Most Analytics events now trigger a common naming pattern:
* `setDocumentsTriggered`
* `setRootDocumentTriggered`
* `unsetDocumentsTriggered`
* `setLocationsTriggered`
* `setRootLocationTriggered`
* `removeLocationsTriggered`
* `setDocumentsSuccess`
* `unsetDocumentsSuccess`
* `setLocationsSuccess`
* `removeLocationsSuccess`
### New Features
* \[**Self-hosting**]: Added support to disable getting user resolver requests for organization, document and folder users. Helps optimize performance by avoiding unnecessary user data requests
* Sometimes you may have a lot of users within your organization, document or folder. In such cases, you may want to disable user resolver requests for organization, document and folder users and instead use [custom autocomplete feature](/async-collaboration/comments/customize-behavior#customautocompletesearch).
* Learn more about [user data provider](/self-host-data/users)
```jsx {19-29} expandable lines theme={null}
const formatUsersToRecord = (users) => {
// Format users array into a Record object with userId as key and user data as value
return users.reduce((record, user) => {
record[user.userId] = {
userId: user.userId,
name: user.name,
// any other fields
};
return record;
}, {});
};
const fetchUsersFromDB = async (userIds) => {
// Fetch users from your DB
const usersData = await __getUsersFromYourDB__(userIds);
return formatUsersToRecord(usersData);
};
const userDataProvider: UserDataProvider = {
get: fetchUsersFromDB,
config: {
resolveUsersConfig: {
organization: false, // Disable organization user requests
folder: false, // Disable folder user requests
document: true // Enable document user requests
}
}
};
```
```js {19-28} expandable lines theme={null}
const formatUsersToRecord = (users) => {
// Format users array into a Record object with userId as key and user data as value
return users.reduce((record, user) => {
record[user.userId] = {
userId: user.userId,
name: user.name,
// any other fields
};
return record;
}, {});
};
const fetchUsersFromDB = async (userIds) => {
// Fetch users from your DB
const usersData = await __getUsersFromYourDB__(userIds);
return formatUsersToRecord(usersData);
};
const userDataProvider = {
get: fetchUsersFromDB,
config: {
resolveUsersConfig: {
organization: false, // Disable organization user requests
folder: false, // Disable folder user requests
document: true // Enable document user requests
}
}
};
Velt.setDataProviders({
user: userDataProvider
});
```
### New Features
* \[**Comments**]: Added custom metadata support for comment tool and comment bubble components.
* Works only with `popover` mode comments. Perfect for complex tables with filtering and segmentation needs.
* Set flexible comment anchoring and filtering logic at the cell level using key-value pairs.
* **Supports aggregate views:** Eg: comments added in day view can appear in week/month views automatically.
* **Two new props:**
* `context`: key-value object for metadata
* `contextOptions`: matching behavior (default: full match, or set `partialMatch: true` for flexible matching)
* **How Partial Match Works:**
* A comment will match if ALL provided filter criteria exist in the comment's context
* Extra fields in the comment's context don't prevent matching
* Missing fields in the comment's context prevent matching
* Example: Comment has `{ day: "01", week: "01", month: "jan", product: "cheese", location: "zurich" }`
* Filter `{ day: "01", product: "cheese" }` → ✅ matches (both fields exist in comment)
* Filter `{ day: "01", category: "dairy" }` → ❌ no match (category doesn't exist in comment)
* **Partial Match Examples:**
* Comment has `{ day: "01", week: "01", month: "jan", product: "cheese" }`
* Filter with `{ day: "01", week: "01", month: "jan", product: "cheese" }` → matches (full)
* Filter with `{ week: "01", month: "jan", product: "cheese" }` → matches (partial)
* Filter with `{ day: "01", week: "01", month: "jan", product: "cheese", location: "zurich" }` → no match
```jsx theme={null}
// Full match
// Partial match
```
```html theme={null}
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue where sometimes the recorder service would not update the saved file url after the processing was completed due to a race condition.
### New Features
* \[**Comments**]: Added ability to filter out comments in the DOM when they are filtered in the sidebar.
* When the sidebar is closed (default mode) or unmounted (embed mode), the DOM filter will be removed.
* Default: `false`
**Using Props:**
```jsx theme={null}
```
**Using API:**
```javascript theme={null}
const commentElement = client.getCommentElement();
commentElement.enableFilterCommentsOnDom();
commentElement.disableFilterCommentsOnDom();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableFilterCommentsOnDom();
commentElement.disableFilterCommentsOnDom();
```
### New Features
* \[**Notifications**]: Added notification settings feature to allow users to configure their notification preferences.
* Enable this feature in the [Velt Console](https://console.velt.dev/dashboard/config/notification).
* Then enable settings on the notifications tool or panel in the UI. [Learn more](/async-collaboration/notifications/customize-behavior#enablesettings)
```jsx theme={null}
// You can enable this on either the tool or the panel
```
```html theme={null}
```
* Configure default settings with `setSettingsInitialConfig()` API and extend it to add more channels where you intend to send notifications to your users. [Learn more](/async-collaboration/notifications/customize-behavior#setsettingsinitialconfig)
* Update settings programmatically with `setSettings()` API. [Learn more](/async-collaboration/notifications/customize-behavior#setsettings)
* Get current settings with `getSettings()` API. [Learn more](/async-collaboration/notifications/customize-behavior#getsettings)
* Subscribe to settings updates with `settingsUpdated` event. [Learn more](/async-collaboration/notifications/customize-behavior#on)
* Added wireframe components for notifications panel settings UI. [Learn more](/ui-customization/features/async/notifications/notifications-panel#settings)
* Added Backend APIs to set and get notifications settings. [Learn more](/api-reference/rest-apis/v2/notifications/set-config)
### Improvements
* \[**DevTools**]: Exposed more events and improved the event stream ordering. The latest version is `1.1.6`
* \[**Recorder**]: Added `transcription` field to `RecorderDataAsset`.
```typescript theme={null}
class RecorderDataAsset {
// ...
transcription: RecorderDataTranscription;
// ...
}
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the mini player wouldn't play or pause the video on click.
* \[**Recorder**]: Fixed an issue where the mini player was not playing the latest edited version of the video.
* \[**Recorder**]: Fixed the alignment of the play button in the mini player.
* \[**Recorder**]: Fixed the responsive behavior of the expanded player.
* \[**Comments**]: Fixed an issue in composer wireframe where clicking on attachments, recordings, avatar components inside the composer was closing the composer.
### Bug Fixes
* \[**Comments**]: Reopen the resolved thread if someone replies.
### New Features
* \[**Recorder**]: Added a feature to download the latest version of a recording.
* It's available in the UI by default. Wireframe added.
* Added an API method to trigger download programmatically:
```js theme={null}
const recorderElement = client.getRecorderElement();
await recorderElement.downloadLatestVideo('RECORDER_ID');
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
await recorderElement.downloadLatestVideo('RECORDER_ID');
```
### Improvements
* \[**Recorder**]: Changed the background of removed video segments to a diagonal stripe pattern to clearly indicate removal (instead of waveform/thumbnails).
* \[**Recorder**]: Added a center focal point to the zoom selection rectangle for better UX.
* \[**Recorder**]: Added Edit button Wireframe in the recorder player.
### Bug Fixes
* \[**Recorder**]: Implemented several UX fixes for the video editor:
* Maintained correct aspect ratio for the zoom selection rectangle during resizing.
* Ensured the zoom selection rectangle maintains its original position while resizing with aspect ratio locked.
* Ensured video preview displays correct content at responsive widths.
* Prevented the zoom selection rectangle from moving outside the video content area.
* Corrected corner dragging behavior to ensure the zoom selection stays within boundaries.
* Fixed an issue where a small zoom selection at the boundary would go out of bounds when decreasing scale.
* Updated `recorderTime.duration` logic to store precise millisecond values.
* Resolved an issue where dragging the trim/scale handler during video playback affected both operations simultaneously.
* Reduced the drag threshold for improved responsiveness of drag operations.
* \[**Recorder**]: Fixed an issue where playback on the mini video player would not stop when the video player was expanded.
### Bug Fixes
* \[**Comments**]: Fixed an issue where folderId was getting unset when switching between documents too quickly.
### Improvements
* \[**Comments**]: Render default comment sidebar only when it is opened. Note this is not applicable for sidebar in embed mode.
* \[**Comments**]: Added a dynamic class for unread comment annotation in comment pin component: `velt-comment-pin-unread-comment`.
### New Features
* \[**Presence**]: Added event to track changes in user's presence state: `online`, `offline`, `away`.
**Using Hook:**
```jsx theme={null}
const userStateChangeEventData = usePresenceEventCallback('userStateChange');
useEffect(() => {
if (userStateChangeEventData) {
console.log('userStateChange: ', userStateChangeEventData);
}
}, [userStateChangeEventData]);
```
**Using API:**
```ts theme={null}
const presenceElement = client.getPresenceElement();
presenceElement.on("userStateChange").subscribe((data: PresenceUserStateChangeEvent) => {
console.log("userStateChange", data);
});
```
```ts theme={null}
export interface PresenceUserStateChangeEvent {
user: PresenceUser; // Object containing user details
state: string; // New state of the user, e.g., 'online', 'away', 'offline'
};
```
```ts theme={null}
const presenceElement = Velt.getPresenceElement();
presenceElement.on("userStateChange").subscribe((data: PresenceUserStateChangeEvent) => {
console.log("userStateChange", data);
});
```
* \[**Presence**]: Added api and hook to fetch presence data with query filters.
**Using Hook:**
```jsx theme={null}
// Fetch online users
const onlineUsersPresenceData = usePresenceData({ statuses: ['online'] });
useEffect(() => {
if (onlineUsersPresenceData && onlineUsersPresenceData.data) {
console.log('Presence data (online users): ', onlineUsersPresenceData.data);
}
}, [onlineUsersPresenceData]);
// Fetch all users (no query)
const allUsersPresenceData = usePresenceData();
useEffect(() => {
if (allUsersPresenceData && allUsersPresenceData.data) {
console.log('Presence data (all users): ', allUsersPresenceData.data);
}
}, [allUsersPresenceData]);
```
**Using API:**
```ts theme={null}
const presenceElement = client.getPresenceElement();
// Fetch online users
presenceElement.getData({ statuses: ['online'] }).subscribe((response: GetPresenceDataResponse) => {
console.log("Presence data (online users): ", response.data);
});
// Fetch all users (no query)
presenceElement.getData().subscribe((response: GetPresenceDataResponse) => {
console.log("Presence data (all users): ", response.data);
});
```
```ts theme={null}
PresenceRequestQuery {
documentId?: string;
organizationId?: string;
statuses?: string[]; // Filter by statuses, e.g., ['online', 'away', 'offline']
}
GetPresenceDataResponse {
data: PresenceUser[] | null; // Array of PresenceUser objects or null
}
```
```ts theme={null}
const presenceElement = Velt.getPresenceElement();
// Fetch online users
presenceElement.getData({ statuses: ['online'] }).subscribe((response: GetPresenceDataResponse) => {
console.log("Presence data (online users): ", response.data);
});
// Fetch all users (no query)
presenceElement.getData().subscribe((response: GetPresenceDataResponse) => {
console.log("Presence data (all users): ", response.data);
});
```
```ts theme={null}
PresenceRequestQuery {
documentId?: string;
organizationId?: string;
statuses?: string[]; // Filter by statuses, e.g., ['online', 'away', 'offline']
}
GetPresenceDataResponse {
data: PresenceUser[] | null; // Array of PresenceUser objects or null
}
```
* \[**Workspace**]: Added API to add and delete domains to the allowed domains list. [Learn more](/api-reference/rest-apis/v2/workspace/add-domain)
### New Features
* \[**Comments**]: Added a new `Document Filter Dropdown` component to the Comment Sidebar. This feature allows users to filter comments based on documents.
* By default, this filter is disabled. You need to enable it in filter config.
* This works when you are using multiple documents or folders.
```jsx theme={null}
const filterConfig = {
document: {
enable: true,
name: "Documents", // Optional: Customize the display name of the filter
enableGrouping: true, // Optional: Enable grouping of documents
multiSelection: true, // Optional: Allow multiple documents to be selected
}
};
```
```js theme={null}
const filterConfig = {
document: {
enable: true,
name: "Documents", // Optional: Customize the display name of the filter
enableGrouping: true, // Optional: Enable grouping of documents
multiSelection: true, // Optional: Allow multiple documents to be selected
}
};
const commentsSidebar = document.querySelector(`velt-comments-sidebar`);
commentsSidebar?.setAttribute("filter-config", JSON.stringify(filterConfig));
```
### Improvements
* \[**Comments**]: Added support to add `context` in `Velt Comment Tool` component as well. Previously, context for comments could only be added via event callbacks or APIs. With this update, you can now predefine context directly within the `Velt Comment Tool` component itself. Currently, this feature is specific to popover comments, allowing you to, for example, assign unique context to each cell in a table.
```jsx theme={null}
```
```html theme={null}
```
* \[**Recorder**]: Enhanced error handling for the recorder by introducing a new `RecordingErrorEvent` event. It will be triggered when the following errors occur:
* `transcriptionFailed`: When transcription fails.
* `editFailed`: When a video edit operation fails.
* `recordingFailed`: When a recording fails.
```jsx theme={null}
// Hook
const recorderErrorEvent = useRecorderEventCallback('error');
useEffect(() => {
if (recorderErrorEvent) {
if (recorderErrorEvent.type === 'transcriptionFailed') {
console.error('Transcription failed:', recorderErrorEvent);
} else if (recorderErrorEvent.type === 'editFailed') {
console.error('Edit failed:', recorderErrorEvent);
} else if (recorderErrorEvent.type === 'recordingFailed') {
console.error('Recording failed:', recorderErrorEvent);
}
}
}, [recorderErrorEvent]);
// API Method
const recorderElement = client.getRecorderElement();
recorderElement.on('error').subscribe((event) => {
if (event.type === 'transcriptionFailed') {
console.error('Transcription failed:', event);
} else if (event.type === 'editFailed') {
console.error('Edit failed:', event);
} else if (event.type === 'recordingFailed') {
console.error('Recording failed:', event);
}
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.on('error').subscribe((event) => {
if (event.type === 'transcriptionFailed') {
console.error('Transcription failed:', event);
} else if (event.type === 'editFailed') {
console.error('Edit failed:', event);
} else if (event.type === 'recordingFailed') {
console.error('Recording failed:', event);
}
});
```
* \[**Recorder**]: Added `RecordingSaveInitiatedEvent` event. This event is dispatched before the actual save operation begins for original or edited recording.
```jsx theme={null}
// Using the hook
const recordingSaveInitiatedEvent = useRecorderEventCallback('recordingSaveInitiated');
useEffect(() => {
if (recordingSaveInitiatedEvent) {
if (recordingSaveInitiatedEvent.type === 'edit') {
console.log('Edited recording save initiated);
} else {
console.log('New recording save initiated.');
}
}
}, [recordingSaveInitiatedEvent]);
// Using the API method
const recorderElement = client.getRecorderElement();
recorderElement.on('recordingSaveInitiated').subscribe((event) => {
if (event.type === 'edit') {
console.log('Edited recording save initiated');
} else {
console.log('New recording save initiated');
}
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.on('recordingSaveInitiated').subscribe((event) => {
if (event.type === 'edit') {
console.log('Edited recording save initiated');
} else {
console.log('New recording save initiated.');
}
});
```
* \[**Recorder**]: Changed background color for video in the editor to gray when the editor is resized to a smaller size.
* \[**Recorder**]: Video playback now stops upon reaching the end and the player state is reset.
* \[**Recorder**]: Improved type for the legacy `onRecordedData` callback.
### Bug Fixes
* \[**Single Editor Mode**]: Fixed an issue where the editor sometimes lost edit access when opening the document in another tab when additional users were also present in the document.
### Improvements
* \[**Recorder**]: Added wireframes for Timeline and Zoom feature.
### Features
* \[**SDK**]: Added `apiProxyDomain` support, allowing Velt API calls to be proxied through your own domain. [Learn more](/security/proxy-server#proxying-velt-api-calls).
```jsx theme={null}
```
```jsx theme={null}
const client = await initVelt('YOUR_API_KEY', {
apiProxyDomain: 'https://api.yourdomain.com'
});
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where Thread Card Dropdown Options: Delete Thread and Delete Comment button wireframes text was not updated correctly.
### Improvements
* \[**Recorder**]: Improved the post processing speed for zoom in effect by 90%.
* \[**Recorder**]: Added configuration API to specify [recording quality constraints](/async-collaboration/recorder/customize-behavior#setrecordingqualityconstraints) and [encoding options](/async-collaboration/recorder/customize-behavior#setrecordingencodingoptions). This provides finer control over the video and audio output of recordings.
```jsx theme={null}
const recorderElement = useRecorderUtils();
recorderElement.setRecordingQualityConstraints({
'other': {
'video': {
width: { exact: 3024 },
height: { exact: 1542 },
frameRate: { exact: 20 },
}
}
});
recorderElement.setRecordingEncodingOptions({
'other': {
videoBitsPerSecond: 2000000,
audioBitsPerSecond: 128000
}
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.setRecordingQualityConstraints({
'other': {
'video': {
width: { exact: 3024 },
height: { exact: 1542 },
frameRate: { exact: 20 },
}
}
});
recorderElement.setRecordingEncodingOptions({
'other': {
videoBitsPerSecond: 2000000,
audioBitsPerSecond: 128000
}
});
```
### Bug Fixes
* \[**Recorder**]: Removed loading animation in the apply button which was causing performance issues.
* \[**Recorder**]: Fixed the issue where disabled buttons were still clickable.
### Improvements
* \[**Presence**]: Added ability to configure when user is considered offline after being inactive. Default value: `600000`ms (10 minutes).
```jsx theme={null}
```
```html theme={null}
```
* \[**Presence**]: Added `isUserIdle` and `isTabAway` fields in presence user objects.
* When a user navigates away from the current tab, their status will be marked as `away`, and the `isTabAway` field in their presence object will be set to `true`.
* If a user remains inactive (no mouse movement) for a duration defined by `inactivityTime` (default: 5 minutes), their status will be set to `away`, and the `isUserIdle` field in their presence object will be set to `true`.
* \[**Single Editor Mode**]: Added new events for Single Editor Mode, enabling finer-grained tracking of collaborative states and user interactions.
The new events are:
* `accessRequested`: When a user requests editor access.
* `accessRequestCanceled`: When a user cancels their request for editor access.
* `accessAccepted`: When a user's request for editor access is accepted.
* `accessRejected`: When a user's request for editor access is rejected.
* `editorAssigned`: When a user is assigned as the editor.
* `viewerAssigned`: When a user is assigned as a viewer.
* `editorOnDifferentTabDetected`: When the current editor navigates away to a different browser tab or window.
**Using Hooks:**
```jsx theme={null}
// Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
const eventData = useLiveStateSyncEventCallback("EVENT_TYPE");
useEffect(() => {
if (eventData) {
console.log("EVENT_TYPE data: ", eventData);
}
}, [eventData]);
```
**Using API:**
```jsx theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
// Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
liveStateSyncElement.on("EVENT_TYPE").subscribe((eventData) => {
console.log("EVENT_TYPE data: ", eventData);
});
```
```javascript theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
// Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
liveStateSyncElement.on("EVENT_TYPE").subscribe((eventData) => {
console.log("EVENT_TYPE data: ", eventData);
});
```
* \[**Comments**]: The `updateContext` method is now supported even when multiple documents are initialized.
### New Features
* \[**Recorder**]: Added zooming functionality. Users can now add smooth zoom in and out effects on the video scenes. Note: This is in beta and currently the processing time for zoom in effect is quite high. We are working to optimize it.
### Improvements
* \[**Recorder**]: Improved wireframes for video editor elements with mouse events to remove all styles if children are provided in the wireframe. Earlier for these elements, some residual styles were required to be overridden via CSS.
* \[**Recorder**]: Added the `recordingEditDone` event. This event fires the recording edits are done processing and the new recording file is generated.
```jsx theme={null}
const recordingEditDoneEvent = useRecorderEventCallback('recordingEditDone');
useEffect(() => {
if (recordingEditDoneEvent) {
console.log('recordingEditDone: ', recordingEditDoneEvent);
}
}, [recordingEditDoneEvent]);
```
```javascript theme={null}
const recorderelement = Velt.getRecorderElement();
recorderelement.on('recordingEditDone').subscribe((event) => {
console.log('recording edit done:' , event);
});
```
* \[**Recorder**]: Added the `deleteRecordings` API method to delete recordings.
**Using API:**
```ts theme={null}
const recorderelement = client.getRecorderElement();
await recorderelement.deleteRecordings({
recorderIds: ['RECORDER_ID_1']
});
```
```js theme={null}
const recorderelement = Velt.getRecorderElement();
await recorderelement.deleteRecordings({
recorderIds: ['RECORDER_ID_1']
});
```
### Bug Fixes
* \[**Core**]: Release 4.4.0 stable version.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the `velt-comment-sidebar-filter-item--Added` class was not being applied correctly in the people filter.
* \[**Notifications**]: Fixed an issue where `setNotificationAsRead` was not working as expected in some scenarios.
### Bug Fixes
* \[**Live State Sync**]: Fixed an issue where `serverConnectionState` value was not updating correctly in some scenarios.
### Improvements
* \[**Comments**]: Added support to specify whether the system filters (e.g., "me", "this page") should be combined with an "and" or "or" operator. Default is `and`.
**Using Props:**
```tsx theme={null}
```
**Using API:**
```ts theme={null}
const commentElement = client.getCommentElement();
commentElement.setSystemFiltersOperator('or');
```
```html theme={null}
```
* \[**Comments**]: Improved filters for comments author in the sidebar.
### Bug Fixes
* \[**SDK**]: Fixed an issue where delete notification REST API was causing some older notifications to be hidden in the frontend component.
### Improvements
* \[**REST APIs**]: Added support for persisting read notifications in "For You" tab through `Update Notifications` REST API. You can pass `persistReadForUsers` param as true.
### Bug Fixes
* \[**SDK**]: Fixed an issue where the `setAllNotificationsAsRead` SDK API method was not working as expected.
### Improvements
* \[**REST APIs**]: Added realtime updates to SDK when notifications are deleted via the REST APIs.
* \[**REST APIs**]: Added support for marking notifications as read through `Update Notifications` REST API.
### Bug Fixes
* \[**Single Editor Mode**]: Fixed accept reject buttons disappearing issue when multiple users request access in single editor mode.
### Improvements
* \[**Live State Sync**]: Improved types in `getLiveStateData`, `setLiveStateData` methods and hooks.
* \[**Comments Sidebar**]: Ensured that "me" and "this page" options are on the top of the comments sidebar filters.
* \[**Notifications**]: Added missing `panelOpenMode` prop to `VeltNotificationsPanel` in React.
```tsx theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an edge case where Tiptap comments were not being added when `commentToNearestAllowedElement` is enabled and comment mode is on.
### New Features
* \[**Comments**]: Added ability to attach comment pins to the closest allowed element when clicking on a non-allowed element. Default: `false`
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableCommentToNearestAllowedElement();
commentElement.disableCommentToNearestAllowedElement();
```
```html theme={null}
```
**Using API:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableCommentToNearestAllowedElement();
commentElement.disableCommentToNearestAllowedElement();
```
* \[**Comments**]: Added ability to enable/disable draft mode in comments. Default: `true`
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDraftMode();
commentElement.disableDraftMode();
```
```html theme={null}
```
**Using API:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDraftMode();
commentElement.disableDraftMode();
```
* \[**Presence**]: Added ability to turn off shadow DOM in Presence component. Default: `true`
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Webhooks**]: Added `assigned: true` in comment added event when someone is assigned to the comment during creation.
* \[**Live State Sync**]: Enhanced Redux middleware with support to dynamically update configuration options:
* `allowedActionTypes`
* `disabledActionTypes`
* `allowAction`
### Bug Fixes
* \[**Core**]: Added additional compatibility for Safari v16 and older versions.
### Improvements
* \[**Recorder**]: Added ability to control whether recorded videos play in full-screen mode.
* You can use this prop on any of the following components:
* `Velt Recorder Notes`
* `Velt Recorder Control Panel`
* `Velt Recorder Player`
* Its turned on by default for Comments feature and off by default for standalone Recorder feature.
```jsx theme={null}
// Change behaviour globally
// Change behaviour for specific player
```
```html theme={null}
```
* \[**Recorder**]: Added keyboard shortcuts in video editor for improved editing experience:
* `s`: Split the video at the selected frame.
* `d`, `delete`, or `backspace`: Delete the selected video section.
* `space`: Toggle video play/pause.
* \[**Single Editor Mode**]: Added wireframe for Single Editor Mode Panel.
* \[**Single Editor Mode**]: Added embeddable component for Single Editor Mode Panel.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**UI Customization**]: Added dynamic key support and square bracket notation support in Velt If conditions for more flexible conditional logic.
### Bug Fixes
* \[**Follow Mode**]: Fixed an issue where follow mode was not working as expected.
* \[**Comments**]: Fixed "All" status filter not displaying all comments in sidebar.
### New Features
* \[**UI Customization**]: Added support of adding dynamic id on Velt Button. You can use template variables to set the id.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Recorder**]: Improved video editor UX for trimming and splitting videos.
* \[**Single Editor Mode**]: Return `undefined` value in `isUserEditor` when there are no current editors available in single editor mode.
* \[**Presence**]: Added `locationId` support in presence component.
```jsx theme={null}
```
```html theme={null}
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where comment dialog composer in sidebar was getting autofocused when `fullExpanded` mode was enabled.
### New Features
* \[**UI Customization**]: Added `resetVeltButtonState` method to reset the state of Velt Button components.
```ts theme={null}
// Reset state for a specific button in a given group
client.resetVeltButtonState({id: 'openSidebar', group: 'multi'})
// Reset state for all buttons in a given group
client.resetVeltButtonState({group: 'multi'})
// Reset state for a specific button in all groups
client.resetVeltButtonState({id: 'openSidebar'})
// Reset state for all buttons
client.resetVeltButtonState()
```
### Improvements
* \[**Comments**]: Updated copy and icons for sidebar filter buttons.
* \[**Recorder**]: Made the transition to editor smoother when `autoOpenVideoEditor` is enabled.
* \[**Recorder**]: Improved the control panel embedded settings menu UX.
* \[**Recorder**]: Added keyboard shortcuts for Play, Pause, Split, and Delete actions in video editor.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the dialog might close if the user clicked outside the dialog while a recording was in progress.
### New Features
* \[**Self Hosting**]: Added support for self-hosting file attachments in comments.
### Improvements
* \[**Self Hosting**]: Added retries for GET operations.
* \[**Analytics**]: Exposed additional events to Velt Debugger.
* \[**Live State Sync**]: Added config to choose between merging and replacing live state data.
**Using Hooks:**
```ts theme={null}
const liveStateData = {
'yourKey': 'yourValue'
};
useSetLiveStateData('LIVE_STATE_DATA_KEY',liveStateData, { merge: true });
```
**Using API:**
```ts theme={null}
const liveStateSyncElement = useLiveStateSyncUtils();
const liveStateData = {
'yourKey': 'yourValue'
};
liveStateSyncElement.setLiveStateData('LIVE_STATE_DATA_KEY', liveStateData, {
merge: true
});
```
```ts theme={null}
const liveStateSyncElement = Velt.getLiveStateSyncElement();
const liveStateData = {
'yourKey': 'yourValue'
};
liveStateSyncElement.setLiveStateData('LIVE_STATE_DATA_KEY', liveStateData, {
merge: true
});
```
### Bug Fixes
* \[**Live State Sync**]: Fixed an issue in the Redux Middleware where the middleware was not initializing when Velt Provider was added conditionally.
### Bug Fixes
* \[**Single Editor Mode**]: Fixed an issue where `contenteditable` div's access was not syncing properly when editor state changed.
### Improvements
* \[**Recorder**]: Added missing audio waveform visualization for screen recordings.
* \[**Recorder**]: Made query parameter optional for `fetchRecordings` method.
* \[**Recorder**]: Improved metadata handling to ensure clean object state.
```typescript theme={null}
export class RecorderData {
recorderId!: string;
from?: User | null;
metadata?: RecorderMetadata;
assets: RecorderDataAsset[] = [];
transcription: RecorderDataTranscription = new RecorderDataTranscription();
}
```
### Improvements
* \[**Recorder**]: Added additional props in `Recorder Control Panel` component:
* `settingsEmbedded`: If set to true, the settings will be embedded in the control panel vs a menu. Default is `false`. Please use this together with the Control Panel Wireframes so that you can move the settings panel in a different part of the control panel UI.
* `autoOpenVideoEditor`: If set to true, the video editor will be opened automatically when the recording is done. Default is `false`.
```jsx theme={null}
```
```html theme={null}
```
* \[**Recorder**]: Added wireframe for recording preview in recording control panel.
```jsx theme={null}
```
```html theme={null}
```
### New Features
* \[**Comments**]: Added `fullExpanded` mode to control the expanded state of comments.
* When enabled, comments will be shown in fully expanded state by default.
* Available on all comment-related components and can be controlled via props or API methods.
Using Props:
```jsx theme={null}
// Apply this change globally to all types of comments
// Apply this change only in comments sidebar
// Apply this change only in inline comments section
// Apply this change only in the standalone comment thread
```
Using API:
```js theme={null}
const commentElement = client.getCommentElement();
commentElement.enableFullExpanded();
commentElement.disableFullExpanded();
```
Using Props:
```html theme={null}
```
Using API:
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableFullExpanded();
commentElement.disableFullExpanded();
```
### New Features
* \[**Dev Tools**]: Published beta version of Velt Devtools Chrome extension. It allows you to inspect and debug your Velt implementation. If you are interested in trying it out, please reach out to us.
* \[**Console**]: Added support for viewing folders, recordings and notifications data in the console data page.
* \[**Console**]: Added support for additional filters in the console data page: organizationId, folderId, documentId, userId.
* \[**Comments**]: Added support for folder users in `@` mentions dropdown.
* \[**Recorder**]: Added an API to subscribe to all recorder data in the current document.
* Param: `RecorderRequestQuery`, optional, Gets the data for the provided query.
* Returns: `Observable`, Subscription of the recorder data.
**Using Hook:**
```jsx theme={null}
const recordings = useRecordings();
useEffect(() => {
console.log('recordings', recordings);
}, [recordings]);
```
**Using API:**
```js theme={null}
const recorderElement = useRecorderUtils();
recorderElement.getRecordings().subscribe((data) => {
console.log('recordings', data);
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.getRecordings().subscribe((data) => {
console.log('recordings', data);
});
```
* \[**Recorder**]: Extended the `on` event subscription API to include:
* `recordingDone`: Triggered when recording is completed.
* `deleteRecording`: Triggered when a recording is deleted.
**Using Hook:**
```jsx theme={null}
const recordingDoneEvent = useRecorderEventCallback('recordingDone');
const recordingDeleteEvent = useRecorderEventCallback('deleteRecording');
useEffect(() => {
console.log('recordingDoneEvent', recordingDoneEvent);
}, [recordingDoneEvent]);
useEffect(() => {
console.log('recordingDeleteEvent', recordingDeleteEvent);
}, [recordingDeleteEvent]);
```
**Using API:**
```js theme={null}
const recorderElement = useRecorderUtils();
recorderElement.on('recordingDone').subscribe((data) => {
console.log('recordingDone:' , data);
});
recorderElement.on('deleteRecording').subscribe((data) => {
console.log('deleteRecording:' , data);
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.on('deleteRecording').subscribe((data) => {
console.log('deleteRecording:' , data);
});
recorderElement.on('recordingDone').subscribe((data) => {
console.log('recordingDone:' , data);
});
```
### Improvements
* \[**UI**]: Made custom list search case insensitive.
* \[**Recorder**]: Make the video preview responsive in video editor.
* \[**Recorder**]: Improved types for all APIs and Props for the recording feature.
### New Features
* \[**Core**]: Added the following events in `on` method of Velt client:
* `userUpdate`: Triggered when the Velt user is updated.
* `documentInit`: Triggered when the Velt document is initialized.
* `error`: Triggered when an error occurs. Currently this is only triggered for `token_expired` error. In future, this will be extended for other relevant errors.
**Using Hook:**
```jsx theme={null}
const userUpdateEvent = useVeltEventCallback('userUpdate');
const documentInitEvent = useVeltEventCallback('documentInit');
const errorEvent = useVeltEventCallback('error');
useEffect(() => {
console.log('userUpdate', userUpdateEvent);
}, [userUpdateEvent]);
useEffect(() => {
console.log('documentInit', documentInitEvent);
}, [documentInitEvent]);
useEffect(() => {
// for token expired, error.code = 'token_expired'
console.log('error', errorEvent);
}, [errorEvent]);
```
**Using API:**
```js theme={null}
client.on('userUpdate').subscribe((user) => {
console.log('userUpdate', user);
});
client.on('documentInit').subscribe((documentInit) => {
console.log('documentInit', documentInit);
});
client.on('error').subscribe((error) => {
// for token expired, error.code = 'token_expired'
console.log('error', error);
});
```
```js theme={null}
Velt.on('userUpdate').subscribe((user) => {
console.log('userUpdate', user);
});
Velt.on('documentInit').subscribe((documentInit) => {
console.log('documentInit', documentInit);
});
Velt.on('error').subscribe((error) => {
// for token expired, error.code = 'token_expired'
console.log('error', error);
});
```
* \[**Recorder**]: Added ability to enable/disable video editor via props on `Velt Recorder Player` and `Velt Recorder Control Panel` components. You could use any of these.
```jsx theme={null}
```
```html theme={null}
```
* \[**Recorder**]: Added `recordingCountdown` and `recordingTranscription` props on `Velt Recorder Notes` and `Velt Recorder Control Panel` components.
```jsx theme={null}
```
```html theme={null}
```
* \[**Recorder**]: Added video editor wireframes.
### Improvements
* \[**Core**]: Improved types for data resolvers.
* \[**Recorder**]: Removed drag functionality from recording control panel and player.
* \[**Recorder**]: If a `null` value is set for `recorder Id` prop in `Velt Recorder Player` component, a warning will be thrown.
### Bug Fixes
* \[**Comments**]: Added a check to prevent an error in @mentions dropdown if the user group has no users and the `expandMentionGroups` config is set to true.
### New Features
* \[**Comments**]: Added support for new filters in `fetchCommentAnnotations` method:
* `resolvedBy`: `string`, Filter comments by user who resolved the comment
* `userIds`: `string[]`, Filter comments by comment annotation author
* `mentionedUserIds`: `string[]`, Filter comments if the provided users are tagged in the comment
* \[**Contacts**]: Added API and Hook to get the list of users added to organization, document, user groups or the ones overwritten using the `updateContactList` API.
```jsx theme={null}
export interface UserGroup {
groupId!: string;
groupName!: string;
users?: User[];
}
export interface GetContactListResponse {
organizationUsers?: User[];
folderUsers?: User[];
documentUsers?: User[];
userGroups?: UserGroup[];
updatedContactList?: User[];
}
// React Hook
const contactList = useContactList();
console.log(contactList); // initial value will be null
// API method
const contactElement = client.getContactElement();
contactElement.getContactList().subscribe((response) => {
console.log(response); // initial value will be null
});
```
```js theme={null}
export interface UserGroup {
groupId!: string;
groupName!: string;
users?: User[];
}
export interface GetContactListResponse {
organizationUsers?: User[];
folderUsers?: User[];
documentUsers?: User[];
userGroups?: UserGroup[];
updatedContactList?: User[];
}
// API method
const contactElement = Velt.getContactElement();
contactElement.getContactList().subscribe((response) => {
console.log(response); // initial value will be null
});
```
### New Features
* \[**Comments**]: Added a config to show expanded user groups inside the @mentions dropdown menu.
* Added [new wireframes](/ui-customization/features/async/comments/comment-dialog/subcomponents/autocomplete-group-option) to customize the display of user groups.
* Here are some props to control the display of user groups:
* `expandMentionGroups`: Whether to expand the user groups and show individual users inside the groups in the @mentions dropdown menu.
* `showMentionGroupsFirst`: Whether to show the user groups in the @mentions dropdown menu before the non-group users.
* `showMentionGroupsOnly`: Whether to show only the user groups in the @mentions dropdown menu and not the non-group users.
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments Sidebar**]: Added two new default system filters and their wireframes to the sidebar:
* `Tagged`: Filter comments by specifying the user who was tagged in the comment. ([Wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/tagged))
* `Assigned`: Filter comments by specifying the user who was assigned to the comment. ([Wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/assigned))
```typescript theme={null}
class CommentSidebarFilterConfig {
// ... existing properties ...
tagged?: FilterTypeConfig;
assigned?: FilterTypeConfig;
// ... existing properties ...
}
```
* \[**Comments Sidebar**]: Added another UI pattern for filters options: searchable dropdown with checklist. ([Example Wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/tagged))
```jsx theme={null}
```
```html theme={null}
```
* \[**Comments Sidebar**]: Added a reset filter button. ([Wireframe](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/reset-button))
* \[**Comments Sidebar**]: Added prop to disable count calculation for sidebar filter options.
```jsx theme={null}
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Updated `getCommentAnnotations()` method to now [query](/api-reference/sdk/models/data-models#commentrequestquery) and subscribe to comments data even if its not the currently set document.
* \[**Comments**]: Improved comment dialog positioning on text comments when the text is at the bottom edge of the page.
* \[**Notifications**]: Added support in `getNotificationsData()` for retrieving custom notifications created with `notifyAll: false`.
* \[**REST API**]: Improved the performance of [GDPR Delete API](/api-reference/rest-apis/v2/gdpr/delete-all-user-data-gdpr) and made it 90%+ faster and more efficient.
### Bug Fixes
* \[**Comments**]: Fixed an issue with SlateJS comments where comment was not being added when the text was selected from right to left.
### Improvements
* \[**Recorder**]: Optimized the video recording performance: 50% smaller files with 90% faster uploads!
### Improvements
* \[**Core**]: Upgraded several dependency packages to new versions.
### Improvements
* \[**REST API**]: Added support to get all user data for GDPR requests. [Learn more](/api-reference/rest-apis/v2/gdpr/get-all-user-data-gdpr).
### Improvements
* \[**Localization**]: Added additional null checks for provided localization strings.
* \[**REST API**]: Added support for special characters (`_`, `-`) while creating custom notifications.
### New Features
* \[**Localization**]: Added support for localization for all static strings visible in the SDK Components.
* You can get the complete list of strings that can be localized [here](https://firebasestorage.googleapis.com/v0/b/snippyly.appspot.com/o/external%2Flocalization-strings-map.json?alt=media\&token=0cdd2b52-10ed-4033-a08a-5c2b622ce7df).
```jsx theme={null}
// Provide the localization object for the languages you want to support.
client.setTranslations({
'en': {
'All comments': 'All comments',
},
'fr': {
'All comments': 'Tous les commentaires',
},
// Add more languages as needed.
});
// Set one of the languages you've provided the translations for.
client.setLanguage('en');
```
```js theme={null}
// Provide the localization object for the languages you want to support.
Velt.setTranslations({
'en': {
'All comments': 'All comments',
},
'fr': {
'All comments': 'Tous les commentaires',
},
// Add more languages as needed.
});
// Set one of the languages you've provided the translations for.
Velt.setLanguage('en');
```
* \[**Comments**]: Added support for `.txt` file attachments in Comments.
### New Features
* \[**Comments, Notifications**]: Added a first-party [extension](https://www.npmjs.com/package/@veltdev/slate-velt-comments) for [SlateJS](https://www.slatejs.org/) to allow comments and notifications on text content.
### Bug Fixes
* \[**UI Customization**]: Fixed an issue where empty wireframes for repeated components were not being rendered in some scenarios.
### Improvements
* \[**Comments**]: Changed default value of `pin-highlighter` to false for better initial experience.
* \[**REST API**]: Added `deleteAll` parameter to [`/v1/organizations/usergroups/users/delete`](/api-reference/rest-apis/v2/user-groups/delete-users-from-group) remove all users from a group.
* \[**REST API**]: Added `metadata` field on the returned comment annotation objects. It will contain documentId, organizationId, folderId, etc.
### Improvements
* \[**Recorder**]: Improved the video editor to be smoother and enabled "select and delete" feature.
* \[**Recorder**]: Added support for audio waveform visualization in video recordings.
* \[**Recorder**]: Reduced the size of the video recordings by 50%.
### New Features
* \[**Reactions**]: Added support for self hosting reactions data.
### Improvements
* \[**Core**]: Improved signature of User Resolver (used for self hosting user PII) for better consistency with backward compatibility.
* \[**Core**]: Extended data resolvers with an option to configure retries and optimized operation order to prioritize client-side execution. Learn more [here](/api-reference/sdk/models/data-models#resolverconfig).
* \[**REST API**]: Added support for updating users in existing comment annotations via REST APIs. Learn more [here](/api-reference/rest-apis/v2/comments-feature/comment-annotations/update-comment-annotations#param-update-users).
### Improvements
* \[**Core**]: Added support for automatic folder creation from the frontend when folders don't exist. New documents without existing data are now automatically added to the currently set folder.
* \[**REST API**]: Added support for user mentions via REST APIs. Learn more [here](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations).
### Bug Fixes
* \[**Comments**]: Fixed an issue where deleting inline comments in multi-document scenarios wasn't always updating the UI state.
### New Features
* \[**Core**]: Added `updateLocations` method to update location(s) metadata. Previously this could only be done via REST APIs.
```js theme={null}
client.updateLocations({
organizationId: 'org1',
documentIds: ['doc1', 'doc2'],
locations: [{
id: 'location1',
locationName: 'MyLocation'
}]
})
export interface UpdateLocationsRequest {
organizationId?: string;
documentIds?: string[];
locations?: UpdateLocationMetadata[];
}
export interface UpdateLocationMetadata {
id: string;
[key: string]: T | string;
}
```
### Improvements
* \[**Core**]: Improved types for `updateDocuments` method.
```js theme={null}
client.updateDocuments({
organizationId: 'org1',
documents: [{
documentId: 'doc1',
documentName: 'MyDoc'
}]
})
export interface UpdateDocumentsRequest {
organizationId?: string;
documents?: UpdateDocumentMetadata[];
}
export interface UpdateDocumentMetadata {
documentId: string;
[key: string]: T | string;
}
```
* \[**Core**]: Added support for location persistence by after debounced document updates.
* \[**Core**]: Show the most updated location metadata in features like sidebar and notifications even if the features have stale data.
* \[**Core**]: `client.getMetadata()` method now provides most updated information about organization, folders documents and locations for debugging purposes.
* \[**Core**]: Added logs for get API methods like `fetchCommentAnnotations` if it's called without an authenticated user.
* \[**Core**]: SDK state and location are preserved after auto-relogin when browser extensions clear indexedDb during idle periods of >1hr.
* \[**Core**]: Enhanced `disableLogs` method with more granular control:
* `disableLogs()`: turns off only warnings
* `disableLogs({suppressAll: true})`: turns off all logs
* `disableLogs({warning: false, suppressAll: true})`: Keeps the warning logs but turns off all other logs
### Improvements
* \[**Core**]: Auto-relogin user in rare scenarios where after an hour of idle time indexedDb is cleared by browser extensions.
### New Features
* \[**Comments**]: Added `svgAsImg` configuration in comments to treat SVGs as images instead layered elements. Default is `false`.
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableSvgAsImg();
commentElement.disableSvgAsImg();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```html theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableSvgAsImg();
commentElement.disableSvgAsImg();
```
### Improvements
* \[**Comments**]: Added `data-velt-comment-dialog-comments-priority` attribute in comment dialog if a comment priority is set. This can be used to style comments differently based on their priority.
* \[**Notifications**]: Changed min height to 300px in Notification panel.
* \[**Comments**]: Added ability to apply filters via API before mounting the Sidebar (in embed mode).
### Improvements
* \[**Comments**]: Now, if both inline and popover comments are used, clicking on a comment in the inline comments section will not activate comment bubble.
* \[**Comments**]: Added additional classes to inline comments section's container divs to allow for more precise custom styling.
* \[**Core**]: Added configuration options to aggressively suppress error messages especially on Safari related to index db and network errors.
```js theme={null}
client.disableLogs({suppressAll:true})
```
### Bug Fixes
* \[**Comments**]: Added missing type for `placeholder` field in `createCustomListDataOnAnnotation` in React.
* \[**Comments**]: Fixed minor bugs with Custom Lists wireframes.
### Improvements
* \[**Notifications**]: Added minimum height to notification panel to prevent layout shift when there is just one notification.
* \[**Notifications**]: Removed default styling from notifications tool when Wireframe is used.
* \[**Comments**]: Added active state support in Tiptap comments mark tags.
### Bug Fixes
* \[**Comments**]: Fixed an issue with `onCommentAdd` event that was not triggered once the comments component was unmounted and remounted during the session.
### New Features
* \[**Debugging**]: Added events for `unsetDocuments` and `unsetLocations`.
* \[**Comments**]: Added type for `fetchCommentAnnotations` in `CommentElement` for React.
* \[**Documents**]: Added `updateDocuments` method to update document(s) metadata.
```js theme={null}
client.updateDocuments({
organizationId: 'org1',
documents: [
{
documentId: 'document-id',
documentName: 'document-name'
},
{
documentId: 'document-id-2',
documentName: 'document-name-2'
}
]
});
export interface UpdateDocumentsRequest {
organizationId?: string;
documents?: {documentId: string; [key: string]: any}[];
}
```
* \[**Debugging**]: Added `folderId` property in `Velt.getMetadata()` response.
### Improvements
* \[**Notifications**]: Improved the notification tool component to prevent layout shift when notification count was updated.
### Bug Fixes
* \[**Comments**]: Added missing type definition for Sidebar Filter Config in React to include status property.
### Improvements
* \[**Comments**]: Improved type names for comment resolver.
```ts theme={null}
// Data models
export interface VeltDataProvider {
comment?: CommentAnnotationDataProvider;
user?: UserDataProvider;
}
export interface CommentAnnotationDataProvider {
get: (request: GetCommentResolverRequest) => Promise>;
save: (request: SaveCommentResolverRequest) => Promise;
delete: (request: DeleteCommentResolverRequest) => Promise;
resolveTimeout?: number; // optional. In milliseconds. Expected timeout to get a response from your API.
}
export interface GetCommentResolverRequest {
organizationId: string;
commentAnnotationIds?: string[];
documentIds?: string[];
folderId?: string;
allDocuments?: boolean;
}
export interface SaveCommentResolverRequest {
commentAnnotation: { [key: string]: PartialCommentAnnotation };
}
export interface PartialComment {
commentId: string | number;
commentHtml?: string;
commentText?: string;
}
export interface PartialCommentAnnotation {
annotationId: string;
metadata?: VeltMetadata;
comments: {
[commentId: string]: PartialComment;
};
}
```
### New Features
* \[**Recorder**]: Added Video Editing feature allowing users to edit recorded videos.
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const recorderElement = client.getRecorderElement();
recorderElement.enableVideoEditor();
recorderElement.disableVideoEditor();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```html theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.enableVideoEditor();
recorderElement.disableVideoEditor();
```
### Improvements
* \[**Recorder**]: Improved Recorder player UX.
### New Features
* \[**Comments**]: Comments Self Hosting now available. Store and retrieve comment and notification text on your own backend while using Velt's UI components.
* Combined with existing user PII self hosting, you can now store all sensitive data on your infrastructure.
* Support for self-hosted attachments and recordings coming soon.
```jsx theme={null}
// Define your comment data provider
const commentDataProvider = {
get: async (request) => {
const result = await getDataFromYourServer(request);
// Ensure the result is in the required format
return result;
},
save: async (request) => {
const result = await saveDataOnYourServer(request);
return result;
},
delete: async (request) => {
await deleteDataFromYourServer(request.commentAnnotationId);
},
};
// Set the data provider
{/* Your app content */}
```
**Request Objects:**
```js theme={null}
// GET Request:
{
organizationId: string;
commentAnnotationIds?: string[];
documentIds?: string[];
folderId?: string;
allDocuments?: boolean;
}
// Save Request:
{
commentAnnotation: { [key: string]: IStrippedCommentAnnotation };
}
// IStrippedCommentAnnotation:
{
annotationId: string;
metadata?: any;
comments: {
[commentId: string]: StrippedComment;
};
}
// StrippedComment:
{
commentId: string | number;
commentHtml?: string;
commentText?: string;
}
// Delete Request:
{
commentAnnotationId: string;
metadata?: any;
}
```
```js theme={null}
// Define your comment data provider
const commentDataProvider = {
get: async (request) => {
const result = await getDataFromYourServer(request);
// Ensure the result is in the required format
return result;
},
save: async (request) => {
const result = await saveDataOnYourServer(request);
return result;
},
delete: async (request) => {
await deleteDataFromYourServer(request.commentAnnotationId);
},
};
// Set the data provider
client.setDataProviders({
comment: commentDataProvider,
});
```
**Request Objects:**
```js theme={null}
// GET Request:
{
organizationId: string;
commentAnnotationIds?: string[];
documentIds?: string[];
folderId?: string;
allDocuments?: boolean;
}
// Save Request:
{
commentAnnotation: { [key: string]: IStrippedCommentAnnotation };
}
// IStrippedCommentAnnotation:
{
annotationId: string;
metadata?: any;
comments: {
[commentId: string]: StrippedComment;
};
}
// StrippedComment:
{
commentId: string | number;
commentHtml?: string;
commentText?: string;
}
// Delete Request:
{
commentAnnotationId: string;
metadata?: any;
}
```
### Improvements
* \[**Comments**]: Added mobile support for inline comment section, improving the user experience on smaller screens.
* \[**Debugging**]: Now `client.getMetadata()` method only returns the currently set documents instead of all documents used in the current session.
### New Features
* \[**Comments**]: Tiptap comments marks are now persisted automatically by default. This simplifies implementation as you no longer need to store marks yourself or modify your editor's content handling.
* \[**Comments**]: Added Sorting Dropdown Wireframe support for inline comments section. [Learn more](/ui-customization/features/async/comments/inline-comments-section/subcomponents/panel/sorting-dropdown)
### Improvements
* \[**Comments**]: Made the freestyle comment pins adapt to DOM elements with complex layouts.
* \[**Comments**]: Improved loading skeleton in inline comments section to match comment card width and adapt responsively to smaller widths.
* \[**Comments**]: Improved the alignment of the assign to dropdown in inline comments section when the width of the section is large.
* \[**Comments**]: Added dark mode styling for the new sorting dropdown in inline comments section.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the comment bubble was not keeping the dialog open when a fresh annotation was added.
### New Features
* \[**Comments**]: Added a default sorting UI component in the inline comments section. This was one of the most requested features.
* \[**Comments**]: Added config to prevent deleting the entire thread when the first comment is deleted.
```jsx theme={null}
```
```js theme={null}
```
### Improvements
* \[**Comments**]: Improved the overall UI/UX of the inline comments section based on user feedback, including: loading state, inner padding, layout shift etc.
### Bug Fixes
* \[**Comments**]: Fixed an issue where inline comments sorting was not working when the prop was changed dynamically.
* \[**Comments**]: Fixed issue in single-threaded mode where delete thread label now correctly appears on the root comment regardless of sorting order.
### New Features
* \[**Core**]: Added ability to enable or disable Velt's logs/warnings in Browser Console.
```jsx theme={null}
client.enableLogs();
client.disableLogs();
```
```js theme={null}
Velt.enableLogs();
Velt.disableLogs();
```
### Improvements
* \[**Core**]: Added performance improvements for scenarios where the user accesses and switches between large number of documents at a high frequency.
### Improvements
* \[**Recorder**]: Improved [`TranscriptionDoneEvent`](/api-reference/sdk/models/data-models#transcriptiondoneevent) object.
* \[**Comments**]: Fixed `useSetDocuments` hook to properly support `setDocuments` method in dependency array in React `useEffect`:
```jsx theme={null}
const { setDocuments } = useSetDocuments();
useEffect(() => {
setDocuments(yourDocuments);
}, [setDocuments]);
```
* \[**Comments**]: Made small UI improvements:
* Removed 0 from Velt bubble count.
* Fixed Velt bubble wireframe layout shift issue.
### Bug Fixes
* \[**Comments**]: Fixed small UI issues:
* Fixed an issue where draft comments were showing up when reaction was updated.
* Fixed an issue where selecting a comment in inline comment section was also opening the Velt Comment bubble.
* Fixed an issue where focused thread mode scroll was not working as expected.
### Bug Fixes
* \[**Comments**]: Fixed an issue where the SeenBy dropdown wasn't opening on the first click.
* \[**Comments**]: Fixed an issue where clicking @mention button was not opening the mention dropdown. Typing @ was working as expected.
* \[**Comments**]: Fixed a re-rendering issue when adding reactions.
### New Features
* \[**Core**]: Added `getUser` method to get the current user.
```jsx theme={null}
const user = client.getUser();
console.log(user);
```
```js theme={null}
const user = Velt.getUser();
console.log(user);
```
* \[**UI Customization**]: Simplified how UI can be customized using wireframes.
* Conditional Component Rendering: Conditionally render any component directly without needing to specify parent or sibling wireframes.
* Conditional CSS Classes: Classes can now be conditionally applied to components based on the data in the component.
* Wireframe CSS Classes Support: CSS Classes added to wireframes components are automatically applied to the related rendered components.
**Conditional Component Rendering:**
```jsx theme={null}
// Old
// New
```
**Conditional CSS Classes:**
```jsx theme={null}
```
**Wireframe CSS Classes Support:**
```jsx theme={null}
```
**Conditional Component Rendering:**
```html theme={null}
```
**Conditional CSS Classes:**
```jsx theme={null}
```
**Wireframe CSS Classes Support:**
```html theme={null}
```
* \[**UI Customization**]: Set and render custom state data into Velt components. This data is available in all Velt Wireframes, Velt If and Velt Data components.
```jsx theme={null}
// Setter
client.setUiState({
dashboardName: 'MRR Growth',
anyKey: 'anyValue'
});
// Getter
client.getUiState().subscribe((data) => {
console.log('UI State: ', data);
});
// Use it in Velt Wireframe
```
```js theme={null}
// Setter
Velt.setUiState({
dashboardName: 'MRR Growth',
anyKey: 'anyValue'
});
// Getter
Velt.getUiState().subscribe((data) => {
console.log('UI State: ', data);
});
// Use it in Velt Wireframe
```
* \[**Comments**]: Added support for custom autocomplete search for contact list or [custom lists](/async-collaboration/comments/customize-behavior#createcustomlistdataoncomment). You should use this if you have a large dataset that you want to plug into the autocomplete dropdown, and search directly your own data source.
**Enable the feature:**
```jsx theme={null}
// Enable via props
// Or, enable via Comment Element API
const commentElement = client.getCommentElement();
commentElement.enableCustomAutocompleteSearch();
commentElement.disableCustomAutocompleteSearch();
```
**Set initial list:**
```jsx theme={null}
// For @mentions feature
contactElement.updateContactList(users);
// For custom list feature
commentElement.createCustomListDataOnComment({
hotkey: "#",
type: "custom",
data: customListData,
});
```
**Handle search event:**
```jsx theme={null}
commentElement.on('autocompleteSearch').subscribe(async (inputData) => {
const searchText = inputData.searchText;
// For @mentions feature
if (inputData.type === 'contact') {
const filteredUsersData = await __your_api_call__(searchText);
contactElement.updateContactList(filteredUsersData, { merge: false });
}
// For custom list feature
if (inputData.type === 'custom') {
const filteredListData = await __your_api_call__(searchText, autocompleteData);
commentElement.createCustomListDataOnComment({
hotkey: "#",
type: "custom",
data: filteredListData,
});
}
});
interface AutocompleteSearchEvent {
event: KeyboardEvent | InputEvent | Event;
searchText: string;
type?: 'contact' | 'custom';
metadata?: VeltEventMetadata;
}
```
**Enable the feature:**
```js theme={null}
// Enable via attribute
// Or, enable via Comment Element API
const commentElement = Velt.getCommentElement();
commentElement.enableCustomAutocompleteSearch();
commentElement.disableCustomAutocompleteSearch();
```
**Set initial list:**
```js theme={null}
// For @mentions feature
contactElement.updateContactList(users);
// For custom list feature
commentElement.createCustomListDataOnComment({
hotkey: "#",
type: "custom",
data: customListData,
});
```
**Handle search event:**
```js theme={null}
commentElement.on('autocompleteSearch').subscribe(async (inputData) => {
const searchText = inputData.searchText;
// For @mentions feature
if (inputData.type === 'contact') {
const filteredUsersData = await __your_api_call__(searchText);
contactElement.updateContactList(filteredUsersData, { merge: false });
}
// For custom list feature
if (inputData.type === 'custom') {
const filteredListData = await __your_api_call__(searchText, autocompleteData);
commentElement.createCustomListDataOnComment({
hotkey: "#",
type: "custom",
data: filteredListData,
});
}
});
interface AutocompleteSearchEvent {
event: KeyboardEvent | InputEvent | Event;
searchText: string;
type?: 'contact' | 'custom';
metadata?: VeltEventMetadata;
}
```
* \[**Comments**]: Added `composerClicked` event to detect when comment composer is clicked.
```jsx theme={null}
const composerClickEvent = useCommentEventCallback('composerClicked');
```
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.on('composerClicked').subscribe((data) => {
console.log('Composer clicked', data);
});
```
### Bug Fixes
* \[**Comments**]: Fixed assign comment input double border CSS issue.
### New Features
* \[**Folders**]: Introducing Folders! Organize documents hierarchically with granular access control. Modeled after Google Drive's folder structure.
* Folders can contain documents and other folders.
* Uses same permission model as Organizations and Documents.
* APIs:
* **Frontend**: Subscribe to folders and query by folderId.
* **Backend**: CRUD operations for folders and access control. [Learn More](/api-reference/rest-apis/v2/folders/add-folder)
* Added folder support to existing relevant APIs.
* \[**Folders**]: API to subscribe to a particular folder and all its documents at the same time.
**Using Hooks:**
```jsx theme={null}
const { setDocuments } = useSetDocuments();
{/* Subscribe to a folder and all its documents */}
const rootDocument = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
}
];
setDocuments(
rootDocument,
{
folderId: 'folder1',
allDocuments: true
}
);
{/* Subscribe to a folder and some documents */}
const documents = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
},
{
id: 'document-2',
metadata: {
documentName: 'Document 2'
}
}
];
setDocuments(
documents,
{
folderId: 'folder1',
}
);
```
**Using API:**
```jsx theme={null}
{/* Subscribe to a folder and all its documents */}
client.setDocuments(
rootDocument,
{
folderId: 'folder1',
allDocuments: true
}
);
{/* Subscribe to a folder and some documents */}
client.setDocuments(
documents,
{
folderId: 'folder1',
}
);
```
```js theme={null}
// Subscribe to a folder and all its documents
const rootDocument = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
}
];
Velt.setDocuments(
rootDocument,
{
folderId: 'folder1',
allDocuments: true
}
);
// Subscribe to a folder and some documents
const documents = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
},
{
id: 'document-2',
metadata: {
documentName: 'Document 2'
}
}
];
Velt.setDocuments(
documents,
{
folderId: 'folder1',
}
);
```
* \[**Folders**]: Added an API to fetch folder metadata and subfolders by organizationId, folderId with pagination.
```jsx theme={null}
// Get all folders for a specific organization
client.fetchFolders({
organizationId: 'org1'
});
// Get a specific folder's metadata with its immediate subfolders
client.fetchFolders({
organizationId: 'org1',
folderId: 'folder1'
});
interface FetchFoldersRequest {
organizationId?: string;
folderId?: string;
}
interface FetchFoldersResponse {
data: Record | null;
nextPageToken: string;
}
```
```js theme={null}
// Get all folders for a specific organization
Velt.fetchFolders({
organizationId: 'org1'
});
// Get a specific folder's metadata with its immediate subfolders
Velt.fetchFolders({
organizationId: 'org1',
folderId: 'folder1'
});
interface FetchFoldersRequest {
organizationId?: string;
folderId?: string;
}
interface FetchFoldersResponse {
data: Record | null;
nextPageToken: string;
}
```
* \[**Documents**]: Added an API to fetch document metadata by organizationId, folderId or documentIds with pagination.
```jsx theme={null}
// Get all documents for a specific folder
client.fetchDocuments({
organizationId: 'org1',
folderId: 'folder1',
allDocuments: true
});
// Get specific documents by IDs
client.fetchDocuments({
organizationId: 'org1',
documentIds: ['doc1', 'doc2']
});
interface FetchDocumentsRequest {
organizationId?: string;
documentIds?: string[];
folderId?: string;
allDocuments?: boolean;
}
interface FetchDocumentsResponse {
data: Record | null;
nextPageToken: string;
}
```
```js theme={null}
// Get all documents for a specific folder
Velt.fetchDocuments({
organizationId: 'org1',
folderId: 'folder1',
allDocuments: true
});
// Get specific documents by IDs
Velt.fetchDocuments({
docElement.fetchDocuments({
organizationId: 'org1',
documentIds: ['doc1', 'doc2']
});
interface FetchDocumentsRequest {
organizationId?: string;
documentIds?: string[];
folderId?: string;
allDocuments?: boolean;
}
interface FetchDocumentsResponse {
data: Record | null;
nextPageToken: string;
}
```
* \[**Comments**]: Added a new API to fetch comment annotations by organizationId, folderId or documentIds with pagination and filtering options. This is different from the existing subscription API which susbcribes to realtime changes to the comments data.
```jsx theme={null}
// Get all annotations for a specific folder
const commentElement = client.getCommentElement();
commentElement.fetchCommentAnnotations({
organizationId: 'org1',
folderId: 'folder1',
allDocuments: true
});
// Get annotations for specific documents
const commentElement = client.getCommentElement();
commentElement.fetchCommentAnnotations({
organizationId: 'org1',
documentIds: ['doc1', 'doc2']
});
interface FetchCommentAnnotationsRequest {
createdAfter?: number;
createdBefore?: number;
updatedAfter?: number;
updatedBefore?: number;
statusIds?: string[];
order?: 'asc' | 'desc';
pageToken?: string;
allDocuments?: boolean;
pageSize?: number;
organizationId?: string;
locationId?: string;
documentIds?: string[];
folderId?: string;
}
interface FetchCommentAnnotationsResponse {
data: Record | null;
nextPageToken: string;
}
```
```js theme={null}
// Get all annotations for a specific folder
const commentElement = Velt.getCommentElement();
commentElement.fetchCommentAnnotations({
organizationId: 'org1',
folderId: 'folder1',
allDocuments: true
});
// Get annotations for specific documents
const commentElement = Velt.getCommentElement();
commentElement.fetchCommentAnnotations({
organizationId: 'org1',
documentIds: ['doc1', 'doc2']
});
interface FetchCommentAnnotationsRequest {
createdAfter?: number;
createdBefore?: number;
updatedAfter?: number;
updatedBefore?: number;
statusIds?: string[];
order?: 'asc' | 'desc';
pageToken?: string;
allDocuments?: boolean;
pageSize?: number;
organizationId?: string;
locationId?: string;
documentIds?: string[];
folderId?: string;
}
interface FetchCommentAnnotationsResponse {
data: Record | null;
nextPageToken: string;
}
```
### Features
* \[**Self-hosting**] You can now self-host your user PII metadata.
* Send only userId instead of full user object and a client side data provider.
* Components will automatically fetch the user details from the provider and hydrate the user object.
* The metadata will not be sent to Velt servers.
```jsx theme={null}
const fetchUsersFromDB = async (userIds) => {
// Fetch users from your DB
const usersData = await getUsersFromYourDB(userIds);
return formatUsersToRecord(usersData);
};
const formatUsersToRecord = (users) => {
// Format users array into a Record object with userId as key and user data as value
return users.reduce((record, user) => {
record[user.userId] = {
userId: user.userId,
name: user.name,
// any other fields
};
return record;
}, {});
};
```
```js theme={null}
Velt.setUserDataProvider({
getUsers: fetchUsersFromDB
});
function fetchUsersFromDB(userIds) {
// Fetch users from your DB
const usersData = getUsersFromYourDB(userIds);
return formatUsersToRecord(usersData);
}
function formatUsersToRecord(users) {
// Format users array into a Record object with userId as key and user data as value
return users.reduce((record, user) => {
record[user.userId] = {
userId: user.userId,
name: user.name,
// any other fields
};
return record;
}, {});
}
```
### Improvements
* \[**Core**]: Added core performance improvements throughout the SDK.
* \[**Recorder**]: Added new API methods to get the recorded data.
```jsx theme={null}
const recorderElement = client.getRecorderElement();
await recorderElement.getRecordingData({
recorderIds: ['RECORDER_ID']
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
await recorderElement.getRecordingData({
recorderIds: ['RECORDER_ID']
});
```
* \[**Recorder**] Added missing props to disable shadow DOM for recorder components in React SDK.
```jsx theme={null}
```
### Bug Fixes
* \[**Recorder**]: Fixed Safari recording compatibility issues across some macOS versions.
### Features
* \[**Core**]: Added support for React v19 in `sdk-react` library.
### Improvements
* \[**Authentication**]: Made `organizationId` mandatory in `identify` method.
* \[**New Accounts**]: New accounts or API keys will have advanced queries turned on by default.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where notifications were not working when organizationId was not set.
* \[**REST API**]: Fixed an issue where the REST API for adding or updating organizations and documents did not allow custom fields in metadata.
### Improvements
* \[**Comments**]: Improved comments performance with optimistic local-first reads and writes.
* \[**Notifications**]: Added `documentMetadata` object in the properties that are sent to SendGrid for emails.
### Bug Fixes
* \[**Comments**]: Fixed an issue where custom metadata added to a comment using `addContext` was not sent to the first notification event (`newlyAdded`).
### Features
* \[**Comments**]: Added a `transcriptionDone` event callback when recording transcription is done.
**Using Hooks:**
```jsx theme={null}
const onTranscriptionDone = useRecorderEventCallback('transcriptionDone');
useEffect(() => {
console.log(onTranscriptionDone);
}, [onTranscriptionDone])
```
**Using API:**
```jsx theme={null}
const recorderElement = client.getRecorderElement();
recorderElement.on('transcriptionDone').subscribe((data) => {
console.log('transcriptionDone', data);
});
```
**Using API:**
```js theme={null}
const recorderElement = Velt.getRecorderElement();
recorderElement.on('transcriptionDone').subscribe((data) => {
console.log('transcriptionDone', data);
});
```
### Improvements
* \[**Comments**]: Disabled @here in the contacts dropdown by default. You can turn it on using [this](/async-collaboration/comments/customize-behavior#enableathere).
### Bug Fixes
* \[**Comments**]: Removed virtual scroll from autocomplete panel and removed fixed height. This was causing weird UI issues. We are rewriting the autocomplete panel with virtual scroll.
* \[**Comments**]: Fixed an issue where horizontal scroll was visible on sidebar in some scenarios.
* \[**Comments**]: Fixed an issue where the `shadowDom` prop was not passed down to page mode composer in comment sidebar.
* \[**Comments**]: Fixed an issue where `sortData` prop was not working in comments sidebar.
### Bug Fixes
* \[**Comments**]: Fixed an issue where comments was not working for fresh documents if the advanced queries option wasn't enabled. Note this is mandatory for all versions of v4 SDK.
* \[**Comments**]: Fixed an issue where [updateContactList](/async-collaboration/comments/customize-behavior#updatecontactlist) was not working.
* \[**Comments**]: Fixed an issue where in inline comments, the resolve button was visible for all messages in a thread.
### Features
* \[**Self-hosting**] You can now self-host your user PII metadata.
* Send only userId instead of full user object and a client side data provider.
* Components will automatically fetch the user details from the provider and hydrate the user object.
* The metadata will not be sent to Velt servers.
```jsx theme={null}
const fetchUsersFromDB = async (userIds) => {
// Fetch users from your DB
const usersData = await getUsersFromYourDB(userIds);
return formatUsersToRecord(usersData);
};
const formatUsersToRecord = (users) => {
// Format users array into a Record object with userId as key and user data as value
return users.reduce((record, user) => {
record[user.userId] = {
userId: user.userId,
name: user.name,
// any other fields
};
return record;
}, {});
};
```
```js theme={null}
Velt.setUserDataProvider({
getUsers: fetchUsersFromDB
});
function fetchUsersFromDB(userIds) {
// Fetch users from your DB
const usersData = getUsersFromYourDB(userIds);
return formatUsersToRecord(usersData);
}
function formatUsersToRecord(users) {
// Format users array into a Record object with userId as key and user data as value
return users.reduce((record, user) => {
record[user.userId] = {
userId: user.userId,
name: user.name,
// any other fields
};
return record;
}, {});
}
```
### Improvements
* \[**Core**]: Added core performance improvements throughout the SDK.
* \[**Recorder**]: Added new API methods to get the recorded data.
```jsx theme={null}
const recorderElement = client.getRecorderElement();
await recorderElement.getRecordingData({
recorderIds: ['RECORDER_ID']
});
```
```js theme={null}
const recorderElement = Velt.getRecorderElement();
await recorderElement.getRecordingData({
recorderIds: ['RECORDER_ID']
});
```
* \[**Recorder**] Added missing props to disable shadow DOM for recorder components in React SDK.
```jsx theme={null}
```
### Bug Fixes
* \[**Recorder**]: Fixed Safari recording compatibility issues across some macOS versions.
### Features
* \[**Core**]: Added support for React v19 in `sdk-react` library.
### Improvements
* \[**Authentication**]: Made `organizationId` mandatory in `identify` method.
* \[**New Accounts**]: New accounts or API keys will have advanced queries turned on by default.
### Bug Fixes
* \[**Notifications**]: Fixed an issue where notifications were not working when organizationId was not set.
* \[**REST API**]: Fixed an issue where the REST API for adding or updating organizations and documents did not allow custom fields in metadata.
### Improvements
* \[**Comments**]: Improved comments performance with optimistic local-first reads and writes.
* \[**Notifications**]: Added `documentMetadata` object in the properties that are sent to SendGrid for emails.
### Bug Fixes
* \[**Comments**]: Fixed an issue where custom metadata added to a comment using `addContext` was not sent to the first notification event (`newlyAdded`).
### Bug Fixes
* \[**Comments**]: Fixed an issue with `getCommentAnnotationsCount` API when filtering by specific document IDs in the query.
### Improvements
* \[**Security**]: Merged security patch in the React package
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the floating recording player was visible for threaded recorder notes
* \[**Comments**]: Fixed an issue where the sidebar button border color was using light mode colors in dark mode
## New APIs
### 1. setDocuments
* Set multiple documents at the same time. You can specify 30 documents at a time.
* The first document in the list will be considered as the root document.
* For features like comments, notifications, recorder, reactions etc. you will be able to read and write to multiple documents at the same time.
* For features like cursors, presence, huddle, live state sync etc. it will default to the root document.
* Sidebar will automatically show data from all the documents.
* Params:
* `documents`: [Document\[\]](/api-reference/sdk/models/data-models#document)
* `options?`: [SetDocumentsRequestOptions](/api-reference/sdk/models/data-models#setdocumentsrequestoptions)
**Using Hooks:**
```jsx theme={null}
const documents = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
},
{
id: 'document-2',
metadata: {
documentName: 'Document 2'
}
}
];
const { setDocuments } = useSetDocuments();
setDocuments(documents);
```
**Using API:**
```jsx theme={null}
const documents = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
},
{
id: 'document-2',
metadata: {
documentName: 'Document 2'
}
}
];
client.setDocuments(documents);
```
**Using API:**
```js theme={null}
const documents = [
{
id: 'document-1',
metadata: {
documentName: 'Document 1'
}
},
{
id: 'document-2',
metadata: {
documentName: 'Document 2'
}
}
];
Velt.setDocuments(documents);
```
### 2. getCommentAnnotations
* Get all the comment annotations for all the specified documents.
* You can specify 30 documents at a time.
* If you don't specify any query, it will return data from the documents specified in the `setDocuments` method.
**Using Hooks:**
```jsx theme={null}
const { data } = useGetCommentAnnotations(query);
// initial data value will be null while the request is in progress
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.getCommentAnnotations(query).subscribe((response) => {
console.log(response.data);
// initial data value will be null while the request is in progress
});
```
```jsx theme={null}
CommentRequestQuery {
documentIds!: string[],
locationIds!: string[],
statusIds!: string[]
};
GetCommentAnnotationsResponse {
data: Record | null; // Key: documentId, Value: CommentAnnotation[]
};
```
**Using API:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.getCommentAnnotations(query).subscribe((response) => {
console.log(response.data);
// initial data value will be null while the request is in progress
});
```
```js theme={null}
CommentRequestQuery {
documentIds!: string[],
locationIds!: string[],
statusIds!: string[]
};
GetCommentAnnotationsResponse {
data: Record; // Key: documentId, Value: CommentAnnotation[]
};
```
### 3. getCommentAnnotationsCount
* Get the total and unread comment annotations count of all the comment annotations for all the specified documents.
* You can specify 30 documents at a time.
* If you don't specify any query, it will return data from the documents specified in the `setDocuments` method.
**Using Hooks:**
```jsx theme={null}
const { data } = useCommentAnnotationsCount(query);
// initial data value will be null while the request is in progress
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.getCommentAnnotationsCount(query).subscribe((response) => {
console.log(response.data);
// initial data value will be null while the request is in progress
});
```
```jsx theme={null}
CommentRequestQuery {
documentIds!: string[],
locationIds!: string[],
statusIds!: string[]
};
GetCommentAnnotationsCountResponse {
data: Record | null; // Key: documentId, Value: CommentAnnotationsCount
};
CommentAnnotationsCount {
unread: number,
total: number
}
```
**Using API:**
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.getCommentAnnotationsCount(query).subscribe((response) => {
console.log(response.data);
});
```
```js theme={null}
CommentRequestQuery {
documentIds!: string[],
locationIds!: string[],
statusIds!: string[]
};
GetCommentAnnotationsCountResponse {
data: Record; // Key: documentId, Value: CommentAnnotationsCount
};
CommentAnnotationsCount {
unread: number,
total: number
}
```
### 4. Read/Write data from multiple documents on the same page
* If you want to display data (eg: comments) from multiple documents on the same page, you can add `data-velt-document-id` attribute to the container that contains the `document`.
* It will be used to identify which part of the DOM belongs to which document.
```html theme={null}
...
...
...
```
## Other updates
### New Features
* \[**Comments**]: Added support for Status Filter in Comments Sidebar's main filter menu:
* By default, the status filter is disabled in the main filter menu.
* Currently, it doesn't support grouping.
* Added Wireframe support for this. [Learn more](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/status).
* If you were using wireframes before, you will add this to your wireframes.
```jsx theme={null}
const filterConfig = {
status: {
enable: true,
name: "Status",
multiSelection: true,
}
};
```
```jsx theme={null}
const filterConfig = {
status: {
enable: true,
name: "Status",
multiSelection: true,
}
};
const commentsSidebar = document.querySelector(`velt-comments-sidebar`);
commentsSidebar?.setAttribute("filter-config",JSON.stringify(filterConfig));
```
### Bug Fixes
* \[**Comments**]: Fixed an issue where empty state visibility was not visible when filter is applied and grouping was disabled.
* \[**Comments**]: Fixed an issue where users could click on the comment in the sidebar and navigate to comments when they shouldn't.
# Tiptap CRDT Library
Source: https://docs.velt.dev/release-notes/version-4/tiptap-changelog
Release Notes of Changes Affecting Velt Tiptap CRDT Library
### Libraries
* `@veltdev/tiptap-crdt`
* `@veltdev/tiptap-crdt-react`
### Improvements
* \[**Core**]: Added production test cases for tiptap-crdt. Improved CRDT store initialization by using `veltInitState` for streamlined internal store creation.
### Bug Fixes
* \[**Core**]: Fixed multi-tab synchronization for same user. When the same user edits content in multiple tabs, all tabs now sync correctly.
**Tiptap v3 Required**: This release upgrades Tiptap from v2 to v3. You must update your Tiptap dependency to v3 to use the latest CRDT packages.
### Improvements
* \[**Core**]: Released stable version 4.5.8 with Tiptap v3 support. Fixed initialContent issue in tiptap-crdt-react that prevented proper initialization of editor content.
### New Features
* \[**Developer Tools**]: Added `window.VeltCrdtStoreMap` global interface to inspect and monitor CRDT stores during development. Access store values directly in the browser console using `VeltCrdtStoreMap.get(id)` or `VeltCrdtStoreMap.getAll()`. Subscribe to store updates and monitor registration events for debugging collaborative data synchronization. [Learn more](/realtime-collaboration/crdt/setup/core#veltcrdtstoremap)
### Bug Fixes
* \[**Core**]: Fixed `initialContent` not being applied when no server-side data exists. You can now set `initialContent` in TipTap CRDT, and it will be used when the document is empty.
```jsx theme={null}
const { VeltCrdt } = useVeltTiptapCrdtExtension({
editorId: "UNIQUE_EDITOR_ID",
initialContent: "Hello World
",
});
```
```javascript theme={null}
const veltCrdt = veltClient.getVeltTiptapCrdtExtension({
editorId: "UNIQUE_EDITOR_ID",
initialContent: "Hello World
",
});
```
### Improvements
* \[**Core**]: Improved the API signatures and naming to improve developer experience.
### Improvements
* \[**Core**]: Released a purpose built react package (`@veltdev/tiptap-crdt-react`) that reduced the implementation code by 95%.
### Bug Fixes
* \[**Core**]: Fixed an issue where last keystroke was not synced in some cases.
### New Features
* \[**Core**]: Introduced purpose built CRDT library for Tiptap Editor to enable multiplayer editing. This is based on the [Yjs](https://docs.yjs.dev/) library. [Learn more](/realtime-collaboration/crdt/setup/tiptap).
# Upgrade Guide
Source: https://docs.velt.dev/release-notes/version-4/upgrade-guide
## Overview
* **Key improvements in this series will focus on**:
* Advanced data querying capabilities
* Enhanced security features
* Powerful customization capabilities that will require significantly less code than before
* We are adding support for multiple document operations:
* **Multi-Document Handling**:
* Work with multiple Velt documents on a single page simultaneously
* Subscribe, view, and perform CRUD operations across multiple documents in real-time
* Comments from multiple documents are now automatically rendered in the Sidebar
* **Enhanced Querying**:
* Query comments, comment counts, unread counts across multiple documents with a simpler API
* Filter by document IDs, location IDs, and status IDs
## Breaking Changes
* \[**Authentication**] Made `organizationId` mandatory in `identify` method.
* \[**UI Customization**] If you applied CSS to wireframe component selectors, you need to update them to target Velt component selectors directly.
* Wireframe components are no longer rendered within Velt components. In previous versions, there were a few wireframe components that were rendered within Velt components.
* If you are an existing customer who is impacted, reach out and we will provide you the updated CSS for your implementation.
* \[**REST APIs**]: v1 GET APIs will not work once you deploy to this series. Use v2 GET APIs instead. All the other REST APIs should work as is.
## How to Upgrade
Use these steps with your test API key first. Once you are ready, follow the same steps on all your production API keys as well.
1. Ensure you are using `organizationId` vs `groupId` in the `identify` method. If you have existing data with groupId, then first migrate your data using [this guide](#migrating-from-groupid-to-organizationid).
2. Enable 'Advanced Queries and Filters' in the Velt Console [here](https://console.velt.dev/dashboard/config/appconfig). It will take 15-30 mins for this to be enabled in your API key depending on the size of your data.
3. Once, the feature is enabled, deploy the latest version of the Velt SDK to your product. Do this within couple of hours of enabling the feature.
### Migrating from `groupId` to `organizationId`
Use these steps with your test API key first. Once you are ready, follow the same steps on all your production API keys as well.
1. Change the `groupId` field to `organizationId` in the `identify` method. Once you do this, you will stop seeing the existing data locally. This is expected.
2. Goto the [data page in Velt Console](https://console.velt.dev/dashboard/data/organization). You will see an option to migrate data to `organizationId` structure.
3. Click on the `Migrate` button. The migration will take 15-30 mins to complete depending on the size of your data.
4. Once, the migration is done, you will start seeing the data. Your original `groupId` data will be retained as backup.
5. Deploy your changes to production. Note: Deploy soon after the migration completes to minimize any data inconsistency.
# Velt SDK Changelog
Source: https://docs.velt.dev/release-notes/version-5/sdk-changelog
Release Notes of changes added to the core Velt SDK
### Libraries
* `@veltdev/react`
* `@veltdev/client`
* `@veltdev/sdk`
### 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.
### 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.
### New Features
* \[**UI Customization**]: Added 13 standalone autocomplete primitive components (`VeltAutocompleteOption`, `VeltAutocompleteChip`, `VeltAutocompleteEmpty`, etc.) for building fully custom autocomplete UIs without requiring the full `` 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.
### 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.
```jsx theme={null}
// Using API methods
const commentElement = client.getCommentElement();
commentElement.enableVisibilityOptions();
commentElement.disableVisibilityOptions();
```
```html theme={null}
```
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()`.
```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'],
},
});
```
```html theme={null}
```
### 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.
```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]);
```
```html theme={null}
```
* \[**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.
### 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.
```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) => { /* ... */ },
},
});
```
```html theme={null}
```
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).
### 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.
```jsx theme={null}
// Using hook
const event = useCommentEventCallback('commentSaveTriggered');
useEffect(() => {
if (event) {
console.log('Save triggered for annotation:', event.annotationId);
}
}, [event]);
```
```html theme={null}
```
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.
### 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.
```tsx theme={null}
// Declarative prop
// 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]);
```
```html theme={null}
```
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.
### 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.
### 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.
```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();
```
```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();
```
* \[**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).
```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);
});
```
```ts theme={null}
const commentElement = Velt.getCommentElement();
commentElement.on('commentSaved').subscribe((event) => {
console.log('Comment saved:', event.annotationId);
console.log('Annotation:', event.commentAnnotation);
});
```
* \[**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)
```json theme={null}
{
"webhookId": "notif-123",
"usersOrganizationNotificationsConfig": {
"user-1": { "inbox": "ALL", "email": "MINE" },
"user-2": { "inbox": "ALL", "email": "MINE" }
}
}
```
```json theme={null}
{
"webhookId": "notif-456",
"usersDocumentNotificationsConfig": {
"user-1": { "inbox": "ALL", "email": "MINE" },
"user-2": { "inbox": "ALL", "email": "MINE" }
}
}
```
* \[**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)
```json theme={null}
{
"data": {
"organizationId": "org-123",
"userIds": ["user-1", "user-2"],
"config": {
"inbox": "ALL",
"email": "NONE"
}
}
}
```
```json theme={null}
{
"data": {
"organizationId": "org-123",
"userId": "user-1",
"getOrganizationConfig": true
}
}
```
### 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.
### 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 ` ` 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.
### New Features
* \[**Comments**]: Added `enableAttachmentDownload()` / `disableAttachmentDownload()` API methods and an `attachmentDownload` prop on `` 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.
```jsx theme={null}
// Declarative prop
// 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]);
```
```html theme={null}
```
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.
### 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`).
```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
});
```
```html theme={null}
```
### 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.
### 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.
### 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.
```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' }
});
```
```html theme={null}
```
* \[**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).
```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']
});
```
```html theme={null}
```
* \[**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.
### 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.
### New Features
* \[**Comments**]: Added rich text formatting toolbar for comment composers. Users can apply bold, italic, underline, and strikethrough formatting to emphasize text.
```jsx theme={null}
// Using Hooks
import { VeltComments } from '@veltdev/react';
// 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 }
});
```
```html theme={null}
```
* \[**Comments**]: Added `clearComposer()` method to programmatically reset composer state (text, attachments, recordings, tagged users).
```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' });
```
```html theme={null}
```
* \[**Comments**]: Added `getComposerData()` method to retrieve current composer state synchronously without subscribing to events.
```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' });
```
```html theme={null}
```
* \[**Comments**]: Added `context` prop to `VeltCommentDialogComposer` to attach custom context data to comments created from standalone composers.
```jsx theme={null}
```
```html theme={null}
```
### 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.
```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);
});
```
```html theme={null}
```
* \[**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.
### 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.
```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
```
Using props:
```html theme={null}
Using API methods:
```
* \[**Comments**]: Added `setAssignToType()` method to switch the assignment UI between dropdown and checkbox modes. Checkbox mode allows quick self-assignment with a toggle.
```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
```
Using props:
```html theme={null}
Using API methods:
```
* \[**Comments**]: Added "Assigned to me" filter option in the sidebar to filter comments by assignment.
```jsx theme={null}
import { VeltCommentsSidebarWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**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.
```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');
});
```
```html theme={null}
```
* \[**Comments**]: Added `readOnly` prop to control read-only mode at the component level. The local prop takes precedence over global settings when explicitly set.
```jsx theme={null}
import { VeltCommentComposer, VeltInlineCommentsSection } from '@veltdev/react';
```
```html theme={null}
```
* \[**Comments**]: Added wireframe component to customize the assign button on thread cards.
```jsx theme={null}
import { VeltCommentDialogWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**Comments**]: Added wireframe component to customize the edit composer within comment thread cards.
```jsx theme={null}
import { VeltCommentDialogWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**Comments**]: Added reaction pin component to pin specific reactions and exclude them from the general list.
```jsx theme={null}
import { VeltCommentDialogWireframe } from '@veltdev/react';
```
```html theme={null}
```
* \[**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.
```jsx theme={null}
import { VeltCommentComposer } from '@veltdev/react';
// Before (deprecated)
// After (required)
```
```html theme={null}
```
* \[**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.
### 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.
```jsx theme={null}
{/* or */}
// Using API methods
const notificationElement = client.getNotificationElement();
// Enable organization-level settings
notificationElement.enableSettingsAtOrganizationLevel();
// Disable organization-level settings
notificationElement.disableSettingsAtOrganizationLevel();
```
```html theme={null}
```
### 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.
### Improvements
* \[**Comments**]: Added ability to enable/disable Private Comments feature in [Velt Console](https://console.velt.dev/dashboard/config/appconfig)
### 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.
```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
});
```
```html theme={null}
```
**Return Format:**
```typescript theme={null}
{
data: {
"doc-1": { total: 10, unread: 2 },
"doc-2": { total: 15, unread: 5 }
}
}
```
### 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.
### 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)
```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'
});
```
```html theme={null}
```
### 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.
### 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.
```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?',
});
}
});
```
```html theme={null}
```
### Improvements
* \[**Comments**]: Added `setContextProvider` method to set a global context provider for all comment annotations. Also added `useSetContextProvider` hook for React applications.
```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?',
}
});
```
```html theme={null}
```
### Bug Fixes
* \[**Core**]: Fixed package integrity issue in v5.0.0-beta.5.
### 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.
### 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.
### 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.
### 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.
```jsx theme={null}
// Using VeltProvider
{/* Your app content */}
// Using API method
const client = useVeltClient();
client.initConfig('API_KEY', {
globalStyles: false
});
```
```html theme={null}
```
* \[**Comments**]: Added `submitComment(targetElementId)` method to programmatically trigger comment submission. Enables custom buttons or keyboard shortcuts for submitting comments.
```jsx theme={null}
// Using hook
const commentElement = useCommentUtils();
commentElement.submitComment('composer-1');
// Using API method
const client = useVeltClient();
const commentElement = client.getCommentElement();
commentElement.submitComment('composer-1');
```
```html theme={null}
Send 1
Send 2
```
* \[**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)
```jsx theme={null}
```
```html theme={null}
```
* \[**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)
```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);
});
```
```html theme={null}
```
### 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.
```jsx theme={null}
// Pattern 1: ID-Based (Standalone)
// Pattern 2: Context Wrapper
```
```html theme={null}
```
**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)
# Upgrade Guide
Source: https://docs.velt.dev/release-notes/version-5/upgrade-guide
## Overview
* **Key improvements in this series**:
* 115+ primitive components for building custom comment dialogs
* Each subcomponent can now be used independently without requiring the full dialog structure
* Two usage patterns: ID-Based (Standalone) and Context Wrapper
* Full control over comment dialog layout and styling
## Breaking Changes
* \[**CSS Element Names**] If you applied CSS to comment dialog element selectors, you need to update them to target the new element names.
* Replace `snippyly-comment-dialog` with `velt-comment-dialog-internal`
* Replace `app-comment-dialog-*` pattern with `velt-comment-dialog-*-internal`
* For example: `app-comment-dialog-composer` becomes `velt-comment-dialog-composer-internal`
* **Important**: Only element names need to be updated. Class names remain unchanged.
* \[**`CommentVisibilityType` Renamed Values**] The `type` field values for [`updateVisibility()`](/async-collaboration/comments/customize-behavior#updatevisibility) and [`PrivateModeConfig`](/api-reference/sdk/models/data-models#privatemodeconfig) have been renamed.
* `'organization'` → `'organizationPrivate'`
* `'self'` → `'restricted'`
**Before:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.updateVisibility({ annotationId: 'id', type: 'organization', organizationId: 'org-123' });
commentElement.updateVisibility({ annotationId: 'id', type: 'self', userIds: ['user-1'] });
```
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.updateVisibility({ annotationId: 'id', type: 'organization', organizationId: 'org-123' });
commentElement.updateVisibility({ annotationId: 'id', type: 'self', userIds: ['user-1'] });
```
**After:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.updateVisibility({ annotationId: 'id', type: 'organizationPrivate', organizationId: 'org-123' });
commentElement.updateVisibility({ annotationId: 'id', type: 'restricted', userIds: ['user-1'] });
```
```js theme={null}
const commentElement = Velt.getCommentElement();
commentElement.updateVisibility({ annotationId: 'id', type: 'organizationPrivate', organizationId: 'org-123' });
commentElement.updateVisibility({ annotationId: 'id', type: 'restricted', userIds: ['user-1'] });
```
## How to Upgrade
1. Search your CSS files for any element selectors using `snippyly-comment-dialog` or `app-comment-dialog-*` patterns.
2. Update the element selectors using the following replacements:
| Old Element Name | New Element Name |
| ----------------------------- | --------------------------------------- |
| `snippyly-comment-dialog` | `velt-comment-dialog-internal` |
| `app-comment-dialog-composer` | `velt-comment-dialog-composer-internal` |
| `app-comment-dialog-header` | `velt-comment-dialog-header-internal` |
| `app-comment-dialog-body` | `velt-comment-dialog-body-internal` |
3. Deploy the latest version of the Velt SDK to your product.
If you are only using class selectors (e.g., `.comment-dialog-composer`), no changes are required.
# Generating Auth Tokens
Source: https://docs.velt.dev/security/auth-tokens
Open the Velt Console at [console.velt.dev](https://console.velt.dev)
Under the Auth Token section, click on Generate Token button:
# Content security policy
Source: https://docs.velt.dev/security/content-security-policy
## Whitelisting Rules for Content Security Policy (CSP)
If you have a Content Security Policy (CSP) enabled in your app configuration, ensure that the following URLs are whitelisted:
### script-src
* `*.velt.dev`
* `*.api.velt.dev`
* `*.firebaseio.com`
* `*.googleapis.com`
* `wss://*.firebaseio.com`
* `wss://*.firebasedatabase.app`
### connect-src
* `*.velt.dev`
* `*.api.velt.dev`
* `*.firebaseio.com`
* `*.googleapis.com`
* `wss://*.firebaseio.com`
* `wss://*.firebasedatabase.app`
### img-src
* `storage.googleapis.com`
* `firebasestorage.googleapis.com`
### media-src
* `storage.googleapis.com`
* `firebasestorage.googleapis.com`
# JWT Tokens
Source: https://docs.velt.dev/security/jwt-tokens
Generate JWT Tokens for additional security
## Overview
`JWT Tokens` is an optional feature to add additional authentication security to our `client.identify()` method to prevent user impersonation.
Go to [https://console.velt.dev](https://console.velt.dev/dashboard/config/general) and enable the toggle for `Require JWT Token`. The toggle is listed at the very bottom of the page.
JWT Tokens won't work unless you enable it in your console.
Create a server endpoint that will be used to generate and send a `JWT Token` to the client.
Example server endpoint code:
```jsx theme={null}
app.get('/generate-velt-jwt-token', async (req,res) => {
const veltAuthToken = await generateVeltAuthToken(req.body.userId)
res.json(veltAuthToken)
})
```
In your server endpoint, call our `https://api.velt.dev/v1/auth/token/get` endpoint to generate a `JWT Token`.
Example server code:
```jsx theme={null}
async function generateVeltAuthToken(userId: string) {
const url = "https://api.velt.dev/v1/auth/token/get";
const body = {
data: {
userId: userId, // Unique user id of your user
apiKey: "YOUR_VELT_API_KEY",
authToken: "YOUR_CLIENT_AUTH_TOKEN", // Get this token from console.velt.dev
userProperties: {
isAdmin: true, // Set to true if you want to set user as admin
organizationId: "YOUR_ORGANIZATION_ID", // If organizationId is provided here then we will validate it with the organizationId used in the identify call
email: "USER_EMAIL", // If email is provided here then we will validate it with the email used in the identify call
}
},
};
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data?.result?.data?.token;
} catch (error) {
console.error("Error:", error);
}
}
```
### Request Body:
To get your Auth Token that is required for your request body, [read here](/security/auth-tokens).
| Field | Required | Description |
| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey` | Yes | Velt API Key |
| `authToken ` | Yes | Auth Token from the Velt console |
| `userId ` | Yes | Unique user id of the user |
| `userProperties.organizationId` | Yes | The ogranizationId should match the organizationId used in the `identify` call. |
| `userProperties.isAdmin` | No | Set to `true` if you want to set user as `admin`. This is the only way to set a user as an `admin` User. Please do not set this property in the `identify` call as this will unset the `isAdmin` property. |
| `userProperties.email` | No | If `email` is provided, it will be validated with the `email` used in the identify call. Recommended if you are setting email. |
```jsx theme={null}
{
"data": {
"apiKey": "YOUR_API_KEY", //Velt API Key
"authToken": "YOUR_AUTH_TOKEN", // Auth Token from the Velt console
"userId": "yourUserId", // unique user id of the user you are generating a JWT Token for
"userProperties": {
isAdmin: true, // Set to true if you want to set user as admin
organizationId: "YOUR_ORGANIZATION_ID", // If organizationId is provided here then we will validate it with the organizationId used in the identify call
email: "USER_EMAIL", // If email is provided here then we will validate it with the email used in the identify call
}
}
}
```
### Success Response:
```jsx theme={null}
{
"result": {
"status": "success",
"message": "Token generated successfully.",
"data": {
"token": "YOUR_JWT_TOKEN"
}
}
}
```
### Failure Response:
```jsx theme={null}
{
"error": {
"message": "Auth token not found.",
"status": "INVALID_ARGUMENT"
}
}
```
Make sure to generate the JWT Token from your server, not your client. Otherwise, your JWT Token will not be secure.
Call your server endpoint from your client to pass your `JWT Token` to your client.
```jsx theme={null}
const yourJWTToken = await callToYourServerToGetJWTToken(userId)
```
Once the JWT Token is generated, you can pass it into the `client.identify()` method. The `client.identify()` method has an optional second parameter that takes in a configuration object that includes the `JWT Token` as a field.
```jsx theme={null}
const yourJWTToken = await callToYourServerToGetJWTToken(userId)
client.identify(user, {
authToken: yourJWTToken,
});
```
The JWT token has the following lifecycle:
* Tokens expire after 48 hours from generation
* When a token expires, Velt emits a `token_expired` error event
* Your application should:
1. Subscribe to the `error` event to detect expired tokens
2. Generate a new token via your server endpoint when expiry occurs
3. Call `identify()` with the fresh token to re-authenticate the user
**Using Hook:**
```jsx theme={null}
const errorEvent = useVeltEventCallback('error');
useEffect(() => {
if (errorEvent?.code === "token_expired") {
// Generate new Velt Auth Token
// Call identify with the new token
}
}, [errorEvent]);
```
**Using API:**
```js theme={null}
client.on('error').subscribe((error) => {
if (error?.code === "token_expired") {
// Generate new Velt Auth Token
// Call identify with the new token
}
});
```
```js theme={null}
Velt.on('error').subscribe((error) => {
if (error?.code === "token_expired") {
// Generate new Velt Auth Token
// Call identify with the new token
}
});
```
You are all done! Now you have added an additional level of security with `JWT Tokens`.
```jsx Server Code theme={null}
import express from 'express';
const app = express();
const PORT = 8080;
async function generateVeltAuthToken(userId) {
const url = "https://api.velt.dev/v1/auth/token/get";
const body = {
data: {
userId: userId, // Unique user id of your user
apiKey: "YOUR_VELT_API_KEY",
authToken: "YOUR_CLIENT_AUTH_TOKEN", // Get this token from console.velt.dev
userProperties: {
isAdmin: true, // Set to true if you want to set user as admin
organizationId: "YOUR_ORGANIZATION_ID", // If organizationId is provided here then we will validate it with the organizationId used in the identify call
email: "USER_EMAIL", // If email is provided here then we will validate it with the email used in the identify call
}
},
};
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data?.result?.data?.token;
} catch (error) {
console.error("Error:", error);
}
}
app.get('/generate-velt-jwt-token', async (req,res) => {
const veltAuthToken = await generateVeltAuthToken(req.body.userId)
res.json(veltAuthToken)
})
app.listen(PORT, () => {
console.log(`JWT Server listening on port ${PORT}`);
});
```
```jsx Client Code theme={null}
import { useVeltClient } from "@veltdev/react";
import { useEffect, useState } from "react";
export default function YourAuthComponent() {
let [user, setUser] = useState(null);
const userService = () => {
return {
uid: "user1",
displayName: "User 1",
email: "user1@velt.dev",
photoURL: "https://i.pravatar.cc/301",
organizationId: "YOUR_ORGANIZATION_ID"
};
};
// Fetch user data from user service
let yourAuthenticatedUser = userService();
// Get the Velt Client
const { client } = useVeltClient();
// Call to your Server to get JWT Token
async function callToYourServerToGetJWTToken(userId){
let baseUrl = "your-server.com"
let result = await fetch(`${baseUrl}/generate-velt-jwt-token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId:userId
}),
})
let data = await result.json();
return data;
}
useEffect(() => {
const initVelt = async () => {
if (client && yourAuthenticatedUser) {
const { uid, displayName, email, photoURL } = yourAuthenticatedUser;
// Create the Velt user object
const user = {
userId: uid,
name: displayName,
email: email,
photoUrl: photoURL,
organizationId: "YOUR_ORGANIZATION_ID"
};
//Get JWT Token from your server
const yourJWTToken = await callToYourServerToGetJWTToken(user.userId)
// Identify the user with the Velt client, with JWT Token
await client.identify(user, {
authToken: yourJWTToken,
});
setUser(user);
}
};
initVelt().catch(console.error);
}, [client]);
return User: {user?.userId}
;
}
```
# Proxy Server
Source: https://docs.velt.dev/security/proxy-server
You can route Velt SDK events and API calls via a proxy to your own domain. This means you will have most Velt-related network calls branded to your domain.
There are two main aspects to this:
1. Proxying the Velt SDK
2. Proxying Velt API calls
### Proxying the Velt SDK
To serve the Velt SDK via your own proxy server (e.g., `nginx`) instead of Velt's servers, provide your proxy's base URL.
* Velt will automatically append `/lib/sdk@[VERSION_NUMBER]/velt.js` to your `proxyDomain` to determine the full URL for fetching the SDK.
* If `proxyDomain` is `https://cdn.yourdomain.com`, the SDK will be loaded from `https://cdn.yourdomain.com/lib/sdk@[VERSION_NUMBER]/velt.js`.
* Your proxy server must be configured to to forward requests from `[your_proxyDomain]` to `https://cdn.velt.dev` without any modifications to headers or any other content.
```jsx theme={null}
```
```jsx theme={null}
const client = await initVelt('YOUR_API_KEY', {
proxyDomain: 'https://cdn.yourdomain.com',
});
```
### Proxying Velt API calls
To serve the Velt APIs via your own proxy server (e.g., `nginx`) instead of Velt's servers, provide your proxy's base URL.
* If `apiProxyDomain` is `https://api.yourdomain.com`, the APIs will be loaded from `https://api.yourdomain.com`.
* Your proxy server should be configured to forward requests from `[your_apiProxyDomain]` to `https://api.velt.dev` without any modifications to headers or any other content.
```jsx theme={null}
```
```jsx theme={null}
const client = await initVelt('YOUR_API_KEY', {
apiProxyDomain: 'https://api.yourdomain.com'
});
```
### Integrity Check
To ensure the integrity of the Velt SDK, especially when served via a proxy, Velt leverages Subresource Integrity (SRI).
Subresource Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch (for example, from a CDN or your proxy server) are delivered without unexpected manipulation.
* Default: `false`
```jsx theme={null}
```
```js theme={null}
let client = await initVelt("YOUR_API_KEY", {
integrity: true,
});
```
# Supported Regions
Source: https://docs.velt.dev/security/supported-regions
Persistent features like Comments, Notifications, Recording, etc. are available in the following regions:
## North America
| Region name | Region description |
| ----------------------- | ------------------ |
| us-west1 | Oregon |
| us-west2 | Los Angeles |
| us-west3 | Salt Lake City |
| us-west4 | Las Vegas |
| us-central1 | Iowa |
| northamerica-northeast1 | Montréal |
| northamerica-northeast2 | Toronto |
| northamerica-south1 | Queretaro |
| us-east1 | South Carolina |
| us-east4 | Northern Virginia |
| us-east5 | Columbus |
| us-south1 | Dallas |
## South America
| Region name | Region description |
| ------------------ | ------------------ |
| southamerica-west1 | Santiago |
| southamerica-east1 | São Paulo |
## Europe
| Region name | Region description |
| ----------------- | ------------------ |
| europe-west2 | London |
| europe-west1 | Belgium |
| europe-west4 | Netherlands |
| europe-west8 | Milan |
| europe-southwest1 | Madrid |
| europe-west9 | Paris |
| europe-west12 | Turin |
| europe-west10 | Berlin |
| europe-west3 | Frankfurt |
| europe-north1 | Finland |
| europe-north2 | Stockholm |
| europe-central2 | Warsaw |
| europe-west6 | Zürich |
## Middle East
| Region name | Region description |
| ----------- | ------------------ |
| me-central1 | Doha |
| me-central2 | Dammam |
| me-west1 | Tel Aviv |
## Asia
| Region name | Region description |
| --------------- | ------------------ |
| asia-south1 | Mumbai |
| asia-south2 | Delhi |
| asia-southeast1 | Singapore |
| asia-southeast2 | Jakarta |
| asia-east2 | Hong Kong |
| asia-east1 | Taiwan |
| asia-northeast1 | Tokyo |
| asia-northeast2 | Osaka |
| asia-northeast3 | Seoul |
## Australia
| Region name | Region description |
| -------------------- | ------------------ |
| australia-southeast1 | Sydney |
| australia-southeast2 | Melbourne |
## Africa
| Region name | Region description |
| ------------- | ------------------ |
| africa-south1 | Johannesburg |
## Multi-Regions
| Multi-region name | Multi-region description | Read-Write regions | Witness region |
| ----------------- | ------------------------ | ------------------------------------------------------------- | ------------------------- |
| eur3 | Europe | europe-west1 (Belgium), europe-west4 (Netherlands) | europe-north1 (Finland) |
| nam5 | United States | us-central1 (Iowa), us-central2 (Oklahoma—private GCP region) | us-east1 (South Carolina) |
# Attachments
Source: https://docs.velt.dev/self-host-data/attachments
Self-host your comments file attachments data while using Velt's components. Keep attachment storage on your infrastructure with minimal metadata stored on Velt servers.
* This is currently only compatible with `setDocuments` method.
* Ensure that the data providers are set prior to calling `identify` method.
* The data provider methods must return the correct status code (e.g. 200 for success, 500 for errors) and success boolean in the response object. This ensures proper error handling and retries.
# Overview
Velt supports self-hosting your comments file attachments data:
* Attachments can be stored on your own infrastructure, with only necessary identifiers on Velt servers.
* Velt Components automatically hydrate attachment data in the frontend by fetching from your configured data provider.
* This gives you full control over attachment data while maintaining the file attachment features.
# How does it work?
When users upload or delete attachments:
1. The SDK uses your configured [`AttachmentDataProvider`](/api-reference/sdk/models/data-models#attachmentdataprovider) to handle storage
2. Your data provider implements two key methods:
* `save`: Stores the file and returns its URL
* `delete`: Removes the file from storage
**The process works as follows:**
When an attachment operation occurs:
1. The SDK first attempts to save/delete the file on your storage infrastructure
2. If successful:
* The SDK updates Velt's servers with minimal metadata
* The [`PartialComment`](/api-reference/sdk/models/data-models#partialcomment) object is updated to reference the attachment including the attachment url, name and metadata.
* When the comment is saved, this information is stored on your end.
* Velt servers only store necessary identifiers, not the actual files or URLs
3. If the operation fails, no changes are made to Velt's servers and the operation is retried if you have configured retries.
# Implementation Approaches
You can implement attachment self-hosting using either of these approaches:
1. **Endpoint based**: Provide endpoint URLs and let the SDK handle HTTP requests
2. **Function based**: Implement `save` and `delete` methods yourself
Both approaches are fully backward compatible and can be used together.
| Feature | Function based | Endpoint based |
| ------------------ | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Best For** | Complex setups requiring middleware logic, dynamic headers, or transformation before sending | Standard REST APIs where you just need to pass the request "as-is" to the backend |
| **Implementation** | You write the `fetch()` or `axios` code | You provide the `url` string and `headers` object |
| **Flexibility** | High | Medium |
| **Speed** | Medium | High |
## Endpoint based DataProvider
Instead of implementing custom methods, you can configure endpoints directly and let the SDK handle HTTP requests.
Unlike comments and reactions, attachment upload uses `multipart/form-data` format to avoid base64 conversion overhead. The browser automatically manages the Content-Type header with the proper boundary parameter.
### saveConfig
Config-based endpoint for saving attachments. The SDK automatically makes HTTP POST requests with multipart/form-data.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request format: `multipart/form-data` with two fields:
* `file`: File object (binary)
* `request`: JSON string containing [`SaveAttachmentResolverRequest`](/api-reference/sdk/models/data-models#saveattachmentresolverrequest)
* Response format: [`ResolverResponse`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const attachmentResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/attachments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
// Note: Content-Type is automatically set by browser
}
};
const attachmentDataProvider = {
config: attachmentResolverConfig
};
```
```js theme={null}
const attachmentResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/attachments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
// Note: Content-Type is automatically set by browser
}
};
const attachmentDataProvider = {
config: attachmentResolverConfig
};
Velt.setDataProviders({ attachment: attachmentDataProvider });
```
```javascript theme={null}
// Backend handler for /api/velt/attachments/save
const file = req.file;
const request = JSON.parse(req.body.request);
// Use metadata to organize files
const { metadata } = request;
const { organizationId, documentId } = metadata;
const key = `attachments/${organizationId}/${documentId}/${Date.now()}-${file.originalname}`;
await s3Client.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
Body: file.buffer,
ContentType: file.mimetype
}));
const url = `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${key}`;
// Return response in required format
res.json({ data: { url }, success: true, statusCode: 200 });
```
### deleteConfig
Config-based endpoint for deleting attachments. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`DeleteAttachmentResolverRequest`](/api-reference/sdk/models/data-models#deleteattachmentresolverrequest)
* Response format: [`ResolverResponse`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const attachmentResolverConfig = {
deleteConfig: {
url: 'https://your-backend.com/api/velt/attachments/delete',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
}
};
const attachmentDataProvider = {
config: attachmentResolverConfig
};
```
```js theme={null}
const attachmentResolverConfig = {
deleteConfig: {
url: 'https://your-backend.com/api/velt/attachments/delete',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
}
};
const attachmentDataProvider = {
config: attachmentResolverConfig
};
Velt.setDataProviders({ attachment: attachmentDataProvider });
```
```javascript theme={null}
// Backend handler for /api/velt/attachments/delete
const { url } = req.body;
// Delete file from S3
const key = url.split('.s3.amazonaws.com/')[1];
await s3Client.send(new DeleteObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key
}));
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
### Endpoint based Complete Example
```jsx theme={null}
const attachmentResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/attachments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
deleteConfig: {
url: 'https://your-backend.com/api/velt/attachments/delete',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
},
resolveTimeout: 5000,
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const attachmentDataProvider = {
config: attachmentResolverConfig
};
```
```js theme={null}
const attachmentResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/attachments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
deleteConfig: {
url: 'https://your-backend.com/api/velt/attachments/delete',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
}
},
resolveTimeout: 5000,
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const attachmentDataProvider = {
config: attachmentResolverConfig
};
Velt.setDataProviders({ attachment: attachmentDataProvider });
```
## Function based DataProvider
Implement custom methods to handle data operations yourself.
### save
Save attachments to your storage backend. Return the url with a success or error response. On error we will retry.
* Param: [`SaveAttachmentResolverRequest`](/api-reference/sdk/models/data-models#saveattachmentresolverrequest)
* Return: [`Promise>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const saveAttachmentsToDB = async (request) => {
const formData = new FormData();
formData.append('file', request.file);
formData.append('metadata', JSON.stringify(request.metadata));
const response = await fetch('/api/velt/attachments/save', {
method: 'POST',
body: formData
});
return await response.json();
};
const attachmentDataProvider = {
save: saveAttachmentsToDB,
};
```
```js Other Frameworks theme={null}
const saveAttachmentsToDB = async (request) => {
const formData = new FormData();
formData.append('file', request.file);
formData.append('metadata', JSON.stringify(request.metadata));
const response = await fetch('/api/velt/attachments/save', {
method: 'POST',
body: formData
});
return await response.json();
};
const attachmentDataProvider = {
save: saveAttachmentsToDB,
};
Velt.setDataProviders({ attachment: attachmentDataProvider });
```
```javascript theme={null}
const file = req.file;
const metadata = JSON.parse(req.body.metadata);
// Use metadata to organize files by organization and document
const { organizationId, documentId } = metadata;
const key = `attachments/${organizationId}/${documentId}/${Date.now()}-${file.originalname}`;
await s3Client.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
Body: file.buffer,
ContentType: file.mimetype
}));
const url = `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${key}`;
// Return response in required format
res.json({ data: { url }, success: true, statusCode: 200 });
```
### delete
Delete attachments from your storage backend. Return a success or error response. On error we will retry.
* Param: [`DeleteAttachmentResolverRequest`](/api-reference/sdk/models/data-models#deleteattachmentresolverrequest)
* Return: [`Promise>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const deleteAttachmentsFromDB = async (request) => {
const response = await fetch('/api/velt/attachments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const attachmentDataProvider = {
delete: deleteAttachmentsFromDB,
};
```
```js Other Frameworks theme={null}
const deleteAttachmentsFromDB = async (request) => {
const response = await fetch('/api/velt/attachments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const attachmentDataProvider = {
delete: deleteAttachmentsFromDB,
};
Velt.setDataProviders({ attachment: attachmentDataProvider });
```
```javascript theme={null}
const { url } = req.body;
// Delete file from S3
const key = url.split('.s3.amazonaws.com/')[1];
await s3Client.send(new DeleteObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key
}));
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
### config
Configuration for the attachment data provider.
* Type: [`ResolverConfig`](/api-reference/sdk/models/data-models#resolverconfig). Relevant properties:
* `resolveTimeout`: Timeout duration (in milliseconds) for resolver operations
* `saveRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for save operations.
* `deleteRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for delete operations.
```jsx theme={null}
const attachmentResolverConfig = {
resolveTimeout: 5000,
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
```
### Function based Complete Example
```jsx theme={null}
const saveAttachmentsToDB = async (request) => {
const formData = new FormData();
formData.append('file', request.file);
formData.append('metadata', JSON.stringify(request.metadata));
const response = await fetch('/api/velt/attachments/save', {
method: 'POST',
body: formData
});
return await response.json();
};
const deleteAttachmentsFromDB = async (request) => {
const response = await fetch('/api/velt/attachments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const attachmentResolverConfig = {
resolveTimeout: 5000,
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const attachmentDataProvider = {
save: saveAttachmentsToDB,
delete: deleteAttachmentsFromDB,
config: attachmentResolverConfig
};
```
```js theme={null}
const saveAttachmentsToDB = async (request) => {
const formData = new FormData();
formData.append('file', request.file);
formData.append('metadata', JSON.stringify(request.metadata));
const response = await fetch('/api/velt/attachments/save', {
method: 'POST',
body: formData
});
return await response.json();
};
const deleteAttachmentsFromDB = async (request) => {
const response = await fetch('/api/velt/attachments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const attachmentResolverConfig = {
resolveTimeout: 5000,
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const attachmentDataProvider = {
save: saveAttachmentsToDB,
delete: deleteAttachmentsFromDB,
config: attachmentResolverConfig
};
Velt.setDataProviders({ attachment: attachmentDataProvider });
```
# Sample Data
```json theme={null}
{
"annotationId": "ANNOTATION_ID",
"metadata": {
"apiKey": "API_KEY",
"documentId": "DOCUMENT_ID",
"organizationId": "ORGANIZATION_ID"
},
"comments": {
"184639": {
"commentId": 184639,
"commentHtml": "Hello
",
"commentText": "Hello",
"from": {
"userId": "USER_ID"
}
},
"743772": {
"commentId": 743772,
"attachments": {
"758336": {
"url": "https://your-bucket.s3.amazonaws.com/attachments/API_KEY/ATTACHMENT_ID.png",
"name": "image.png",
"attachmentId": 758336
}
},
"from": {
"userId": "USER_ID"
}
}
}
}
```
When an attachment is added, the comment object is updated with the attachment details including the URL and name from your storage.
```json theme={null}
{
"attachmentId": 758336,
"metadata": {
"organizationId": "ORGANIZATION_ID",
"documentId": "DOCUMENT_ID",
"apiKey": "API_KEY"
},
"file": "https://your-bucket.s3.amazonaws.com/attachments/API_KEY/ATTACHMENT_ID.png",
"mimeType": "image/png",
"name": "image.png"
}
```
This is a separate attachments collection on your database that stores the full attachment details.
```json theme={null}
{
"annotationId": "ANNOTATION_ID",
"comments": [
{
"commentId": 184639,
"from": {
"userId": "USER_ID"
},
"isCommentResolverUsed": true,
"isCommentTextAvailable": true,
"attachments": []
},
{
"commentId": 743772,
"from": {
"userId": "USER_ID"
},
"isCommentResolverUsed": true,
"attachments": [
{
"attachmentId": 758336,
"bucketPath": "API_KEY/organizations/ORG_ID/docs/DOC_ID/comments/ANNOTATION_ID/758336_image.png",
"isAttachmentResolverUsed": true,
"mimeType": "image/png",
"size": 289870,
"type": "png"
}
]
}
],
"metadata": {
"apiKey": "API_KEY",
"documentId": "DOCUMENT_ID",
"organizationId": "ORGANIZATION_ID"
}
}
```
Only attachment identifiers, bucket path, and file metadata (mimeType, size, type) are stored on Velt servers. The actual file URL and name remain on your storage infrastructure.
# Debugging
You can subscribe to `dataProvider` events to monitor and debug save and delete operations.
You can also use the [Velt Chrome DevTools extension](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl) to inspect and debug your Velt implementation.
```jsx theme={null}
import { useVeltClient } from '@veltdev/react';
const { client } = useVeltClient();
useEffect(() => {
if (!client) return;
const subscription = client.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
});
return () => subscription?.unsubscribe();
}, [client]);
```
```javascript theme={null}
const subscription = Velt.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
});
// Unsubscribe when done
subscription?.unsubscribe();
```
# Comments
Source: https://docs.velt.dev/self-host-data/comments
Self-host your comments data while using Velt's components. Keep comment storage on your infrastructure with minimal metadata stored on Velt servers.
* This is currently only compatible with `setDocuments` method.
* Ensure that the data providers are set prior to calling `identify` method.
* The data provider methods must return the correct status code (e.g. 200 for success, 500 for errors) and success boolean in the response object. This ensures proper error handling and retries.
* If you are using REST API to add or update comments, ensure that you set `isCommentResolverUsed` and `isCommentTextAvailable` fields in the request object. [Learn more](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations)
# Overview
Velt supports self-hosting your comments and related data:
* Comments can be stored on your own infrastructure, with only necessary identifiers on Velt servers.
* Velt Components automatically hydrate comment data in the frontend by fetching from your configured data provider.
* This gives you full control over comment data while maintaining all Velt collaboration features.
* This automatically also ensures that the in-app notifications content is not stored on Velt servers. The content is generated using the comments data in the frontend.
Email notifications via Velt's SendGrid integration are not available when you self-host comment content. Since the content lives on your infrastructure, Velt cannot construct and send emails via the sendgrid integration. Instead, use [Webhooks](/webhooks/basic) to receive events (e.g., mentions, replies), fetch the relevant comment/notification content from your database, and send emails from your own email provider.
# How does it work?
* When comments are created, updated, deleted or requested, the SDK uses your configured [`CommentAnnotationDataProvider`](/api-reference/sdk/models/data-models#commentannotationdataprovider) to handle storage and retrieval
* The data provider implements `get`, `save`, and `delete` methods to interact with your database
* Velt handles the data mapping and realtime synchronization while delegating persistence of actual content to your infrastructure
* The data provider works at the Comment Annotation (Thread) level not at the individual Comment (Message) level.
* For write requests (save, delete), the operation is first performed on your database and only if we get a success response, the SDK will perform the operation on the Velt server. If the operation fails on your database, the SDK will not perform the operation on the Velt server.
* You can configure retries, timeouts, etc. for the data provider.
# Implementation Approaches
You can implement comment self-hosting using either of these approaches:
1. **Endpoint based**: Provide endpoint URLs and let the SDK handle HTTP requests
2. **Function based**: Implement `get`, `save`, and `delete` methods yourself
Both approaches are fully backward compatible and can be used together.
| Feature | Function based | Endpoint based |
| ------------------ | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Best For** | Complex setups requiring middleware logic, dynamic headers, or transformation before sending | Standard REST APIs where you just need to pass the request "as-is" to the backend |
| **Implementation** | You write the `fetch()` or `axios` code | You provide the `url` string and `headers` object |
| **Flexibility** | High | Medium |
| **Speed** | Medium | High |
## Endpoint based DataProvider
Instead of implementing custom methods, you can configure endpoints directly and let the SDK handle HTTP requests.
### getConfig
Config-based endpoint for fetching comments. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`GetCommentResolverRequest`](/api-reference/sdk/models/data-models#getcommentresolverrequest)
* Response format: [`ResolverResponse>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const commentResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/comments/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const commentDataProvider = {
config: commentResolverConfig
};
```
```js theme={null}
const commentResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/comments/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const commentDataProvider = {
config: commentResolverConfig
};
Velt.setDataProviders({ comment: commentDataProvider });
```
### saveConfig
Config-based endpoint for saving comments. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`SaveCommentResolverRequest`](/api-reference/sdk/models/data-models#savecommentresolverrequest)
* Response format: [`ResolverResponse`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const commentResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/comments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const commentDataProvider = {
config: commentResolverConfig
};
```
```js theme={null}
const commentResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/comments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const commentDataProvider = {
config: commentResolverConfig
};
Velt.setDataProviders({ comment: commentDataProvider });
```
### deleteConfig
Config-based endpoint for deleting comments. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`DeleteCommentResolverRequest`](/api-reference/sdk/models/data-models#deletecommentresolverrequest)
* Response format: [`ResolverResponse`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const commentResolverConfig = {
deleteConfig: {
url: 'https://your-backend.com/api/velt/comments/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const commentDataProvider = {
config: commentResolverConfig
};
```
```js theme={null}
const commentResolverConfig = {
deleteConfig: {
url: 'https://your-backend.com/api/velt/comments/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const commentDataProvider = {
config: commentResolverConfig
};
Velt.setDataProviders({ comment: commentDataProvider });
```
### Endpoint based Complete Example
```jsx theme={null}
const commentResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/comments/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
saveConfig: {
url: 'https://your-backend.com/api/velt/comments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
deleteConfig: {
url: 'https://your-backend.com/api/velt/comments/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const commentDataProvider = {
config: commentResolverConfig
};
```
```js theme={null}
const commentResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/comments/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
saveConfig: {
url: 'https://your-backend.com/api/velt/comments/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
deleteConfig: {
url: 'https://your-backend.com/api/velt/comments/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const commentDataProvider = {
config: commentResolverConfig
};
Velt.setDataProviders({ comment: commentDataProvider });
```
## Function based DataProvider
Implement custom methods to handle data operations yourself.
### get
Method to fetch comments from your database. On error we will retry.
* Param: [`GetCommentResolverRequest`](/api-reference/sdk/models/data-models#getcommentresolverrequest)
* Return: [`Promise>>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const fetchCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentDataProvider = {
get: fetchCommentsFromDB,
};
```
```js Other Frameworks theme={null}
const fetchCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentDataProvider = {
get: fetchCommentsFromDB,
};
Velt.setDataProviders({ comment: commentDataProvider });
```
```javascript theme={null}
// Build query from request
const { commentAnnotationIds, documentIds, organizationId } = req.body;
const query = {};
if (commentAnnotationIds?.length) {
query.annotationId = { $in: commentAnnotationIds };
}
if (documentIds?.length) {
query.documentId = { $in: documentIds };
}
if (organizationId) {
query.organizationId = organizationId;
}
const annotations = await collection.find(query).toArray();
// Convert to Record
const result = {};
for (const annotation of annotations) {
result[annotation.annotationId] = annotation;
}
// Return response in required format
res.json({ data: result, success: true, statusCode: 200 });
```
```javascript theme={null}
// Build parameterized query
const { commentAnnotationIds, documentIds, organizationId } = req.body;
const conditions = [];
const values = [];
let paramIndex = 1;
if (commentAnnotationIds?.length) {
conditions.push(`annotation_id = ANY($${paramIndex++})`);
values.push(commentAnnotationIds);
}
if (documentIds?.length) {
conditions.push(`document_id = ANY($${paramIndex++})`);
values.push(documentIds);
}
if (organizationId) {
conditions.push(`organization_id = $${paramIndex++}`);
values.push(organizationId);
}
const whereClause = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
const { rows } = await client.query(
`SELECT annotation_id, data FROM comment_annotations ${whereClause}`,
values
);
// Convert to Record
const result = {};
for (const row of rows) {
result[row.annotation_id] = row.data;
}
// Return response in required format
res.json({ data: result, success: true, statusCode: 200 });
```
### save
Save comments to your database. Return a success or error response. On error we will retry.
* Param: [`SaveCommentResolverRequest`](/api-reference/sdk/models/data-models#savecommentresolverrequest)
* Note in the `SaveCommentResolverRequest` object, you will receive [the event name](/api-reference/sdk/models/data-models#resolveractions) that triggered the save.
* Return: [`Promise>`](/api-reference/sdk/models/data-models#resolverresponse)
If you are using REST API to add or update comments, ensure that you set `isCommentResolverUsed` and `isCommentTextAvailable` fields in the request object. [Learn more](/api-reference/rest-apis/v2/comments-feature/comment-annotations/add-comment-annotations)
```jsx React / Next.js theme={null}
const saveCommentsToDB = async (request) => {
const response = await fetch('/api/velt/comments/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentDataProvider = {
save: saveCommentsToDB,
};
```
```js Other Frameworks theme={null}
const saveCommentsToDB = async (request) => {
const response = await fetch('/api/velt/comments/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentDataProvider = {
save: saveCommentsToDB,
};
Velt.setDataProviders({ comment: commentDataProvider });
```
```javascript theme={null}
const { annotations, context } = req.body;
// Bulk upsert annotations
const operations = Object.entries(annotations).map(([id, annotation]) => ({
updateOne: {
filter: { annotationId: id },
update: {
$set: {
...annotation,
annotationId: id,
documentId: context?.documentId || annotation.documentId,
organizationId: context?.organizationId || annotation.organizationId,
}
},
upsert: true
}
}));
if (operations.length > 0) {
await collection.bulkWrite(operations);
}
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
```javascript theme={null}
const { annotations, context } = req.body;
// Transaction-based upsert
await client.query('BEGIN');
for (const [id, annotation] of Object.entries(annotations)) {
const data = { ...annotation, annotationId: id };
await client.query(
`INSERT INTO comment_annotations (annotation_id, document_id, organization_id, data, updated_at)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (annotation_id)
DO UPDATE SET data = EXCLUDED.data, updated_at = NOW()`,
[id, annotation.documentId, annotation.organizationId, JSON.stringify(data)]
);
}
await client.query('COMMIT');
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
### delete
Delete comments from your database. Return a success or error response. On error we will retry.
* Param: [`DeleteCommentResolverRequest`](/api-reference/sdk/models/data-models#deletecommentresolverrequest)
* Return: [`Promise>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const deleteCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentDataProvider = {
delete: deleteCommentsFromDB,
};
```
```js Other Frameworks theme={null}
const deleteCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentDataProvider = {
delete: deleteCommentsFromDB,
};
Velt.setDataProviders({ comment: commentDataProvider });
```
```javascript theme={null}
const { annotationId } = req.body;
await collection.deleteOne({ annotationId });
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
```javascript theme={null}
const { annotationId } = req.body;
await client.query(
'DELETE FROM comment_annotations WHERE annotation_id = $1',
[annotationId]
);
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
### config
Configuration for the comment data provider.
* Type: [`ResolverConfig`](/api-reference/sdk/models/data-models#resolverconfig). Relevant properties:
* `resolveTimeout`: Timeout duration (in milliseconds) for resolver operations
* `getRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for get operations.
* `saveRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for save operations.
* `deleteRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for delete operations.
```jsx theme={null}
const commentResolverConfig = {
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
```
### Function based Complete Example
```jsx theme={null}
const fetchCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const saveCommentsToDB = async (request) => {
const response = await fetch('/api/velt/comments/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const deleteCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentResolverConfig = {
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const commentDataProvider = {
get: fetchCommentsFromDB,
save: saveCommentsToDB,
delete: deleteCommentsFromDB,
config: commentResolverConfig
};
```
```js theme={null}
const fetchCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const saveCommentsToDB = async (request) => {
const response = await fetch('/api/velt/comments/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const deleteCommentsFromDB = async (request) => {
const response = await fetch('/api/velt/comments/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const commentResolverConfig = {
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const commentDataProvider = {
get: fetchCommentsFromDB,
save: saveCommentsToDB,
delete: deleteCommentsFromDB,
config: commentResolverConfig
};
Velt.setDataProviders({ comment: commentDataProvider });
```
# Triggering Email Notifications when Self-Hosting
When you self-host content, use Webhooks to trigger emails from your own system:
Subscribe to comment-related events (e.g., user mentions, replies). See [Webhooks](/webhooks/advanced).
Your server receives the webhook event. Use IDs from the payload (e.g., `annotationId`, `commentId`) to query your own comment and notification content from your database via your Data Provider.
Combine the webhook event context with the self-hosted content to build the subject, body, and list of recipients (e.g., mentioned users).
Use your own email service (SendGrid under your account, SES, Postmark, etc.) to send the email.
**Example:**
```json cURL theme={null}
POST /webhooks/velt HTTP/1.1
Content-Type: application/json
{
"webhookId": "webhook-123",
"actionType": "newlyAdded",
"commentAnnotation": {
"annotationId": "ANNOTATION_ID",
"metadata": {
"documentId": "DOC_ID",
"apiKey": "API_KEY"
}
},
"targetComment": {
"commentId": 123,
"from": {
"userId": "USER_1"
}
},
"actionUser": {
"userId": "USER_1",
"name": "John Doe",
"email": "john@example.com"
},
"metadata": {
"apiKey": "API_KEY",
"documentId": "DOC_ID",
"pageInfo": {
"url": "https://app.example.com/doc/123"
}
},
"notificationSource": "comment",
"platform": "sdk"
}
```
```javascript Node.js theme={null}
// Pseudocode: handle webhook, fetch content, send email
app.post('/webhooks/velt', async (req, res) => {
const evt = req.body;
const annotationId = evt?.commentAnnotation?.annotationId;
const targetCommentId = evt?.targetComment?.commentId;
// 1) Fetch self-hosted content
const annotation = await db.commentAnnotations.get(annotationId);
const targetComment = annotation?.comments?.[targetCommentId];
// 2) Resolve recipients (e.g., mentioned users)
const recipients = await resolveMentionedUsers(annotation, targetComment);
// 3) Compose email
const subject = `${evt.actionUser?.name} mentioned you`;
const body = targetComment?.commentText || '';
const pageUrl = evt?.metadata?.pageInfo?.url;
// 4) Send via your provider
await emailClient.send({ to: recipients, subject, html: renderTemplate({ body, pageUrl }) });
res.sendStatus(200);
});
```
# Sample Data
```json theme={null}
{
"annotationId": "ANNOTATION_ID",
"metadata": {
"apiKey": "API_KEY",
"documentId": "DOCUMENT_ID",
"organizationId": "ORGANIZATION_ID"
},
"comments": {
"184639": {
"commentId": 184639,
"commentHtml": "Hey @Jane, can you review this?
",
"commentText": "Hey @Jane, can you review this?",
"from": {
"userId": "USER_ID"
},
"to": [
{
"userId": "JANE_USER_ID"
}
],
"taggedUserContacts": [
{
"userId": "JANE_USER_ID",
"contact": {
"userId": "JANE_USER_ID"
},
"text": "@Jane"
}
]
},
"743772": {
"commentId": 743772,
"attachments": {
"758336": {
"url": "https://your-bucket.s3.amazonaws.com/attachments/API_KEY/ATTACHMENT_ID.png",
"name": "image.png",
"attachmentId": 758336
}
},
"from": {
"userId": "USER_ID"
}
}
}
}
```
```json theme={null}
{
"annotationId": "ANNOTATION_ID",
"annotationIndex": 3,
"annotationNumber": 4,
"color": "#A25F9E",
"comments": [
{
"attachments": [],
"commentId": 184639,
"createdAt": 1768544691938,
"from": {
"userId": "USER_ID"
},
"isCommentResolverUsed": true,
"isCommentTextAvailable": true,
"isDraft": false,
"lastUpdated": "2026-01-16T05:34:19.402Z",
"reactionAnnotationIds": ["REACTION_ANNOTATION_ID"],
"taggedUserContacts": [],
"to": [],
"type": "text"
},
{
"attachments": [
{
"attachmentId": 758336,
"bucketPath": "API_KEY/organizations/ORG_ID/docs/DOC_ID/comments/ANNOTATION_ID/758336_image.png",
"isAttachmentResolverUsed": true,
"mimeType": "image/png",
"size": 289870,
"type": "png"
}
],
"commentId": 743772,
"createdAt": 1768544717230,
"from": {
"userId": "USER_ID"
},
"isCommentResolverUsed": true,
"isDraft": false,
"lastUpdated": "2026-01-16T05:34:31.352Z",
"reactionAnnotationIds": [],
"taggedUserContacts": [],
"to": [],
"type": "text"
}
],
"createdAt": 1768544637011,
"from": {
"userId": "USER_ID"
},
"involvedUserIds": ["USER_ID"],
"lastUpdated": 1768542354784,
"metadata": {
"apiKey": "API_KEY",
"clientDocumentId": "DOCUMENT_ID",
"clientOrganizationId": "ORGANIZATION_ID",
"documentId": "INTERNAL_DOC_ID",
"organizationId": "INTERNAL_ORG_ID"
},
"pageInfo": {
"baseUrl": "https://your-app.com",
"commentUrl": "https://your-app.com/?commentId=ANNOTATION_ID",
"path": "/",
"url": "https://your-app.com/"
},
"status": {
"id": "OPEN",
"name": "Open",
"color": "var(--velt-accent, #625DF5)",
"lightColor": "var(--velt-accent-light, #E7E8FA)",
"type": "default"
}
}
```
Note: Comment text/HTML content is NOT stored on Velt servers when using the comment resolver. Only metadata like `isCommentResolverUsed`, `isCommentTextAvailable`, attachment references (without URL/name), and reaction annotation IDs are stored.
# Debugging
You can subscribe to `dataProvider` events to monitor and debug get, save, and delete operations. The event includes a `moduleName` field that identifies which module triggered the resolver call, helping you trace data provider requests.
You can also use the [Velt Chrome DevTools extension](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl) to inspect and debug your Velt implementation.
Type: [`CommentResolverModuleName`](/api-reference/sdk/models/data-models#commentresolvermodulename)
```jsx theme={null}
import { useVeltClient } from '@veltdev/react';
const { client } = useVeltClient();
useEffect(() => {
if (!client) return;
const subscription = client.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
console.log('Module Name:', event.moduleName);
});
return () => subscription?.unsubscribe();
}, [client]);
```
```javascript theme={null}
const subscription = Velt.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
console.log('Module Name:', event.moduleName);
});
// Unsubscribe when done
subscription?.unsubscribe();
```
# Overview
Source: https://docs.velt.dev/self-host-data/overview
Self-host the user generated content on your infrastructure while storing only minimal identifiers on Velt servers.
# Self-Hosting Data
Velt allows you to self-host sensitive content while still using most of the collaboration features and components:
* **Comments**: Store comment content on your infrastructure with only identifiers on Velt servers
* **Attachments**: Store comment file attachments on your infrastructure with only identifiers on Velt servers
* **In-app notifications**: In-app notification content is automatically handled when you use comments and reactions self-hosted solutions
* **Reactions**: Store reaction data on your systems with only identifiers on Velt servers
* **Users**: Store sensitive user PII on your servers, with only identifiers on Velt servers
For each data type, you configure a data provider that implements specific methods (get, save, delete) to interact with your database. Velt Components automatically hydrate data in the frontend by fetching from your configured providers.
This approach gives you complete control and ownership of your data while maintaining all Velt collaboration features and real-time functionality.
Email notifications via Velt's SendGrid integration are not available when you self-host comment content. Since the content lives on your infrastructure, Velt cannot construct and send emails via the sendgrid integration. Instead, use [Webhooks](/webhooks/basic) to receive events (e.g., mentions, replies), fetch the relevant comment/notification content from your database, and send emails from your own email provider.
# Supported Infrastructure
You can self-host your data on any infrastructure that you want as long as you can receive and return the data in the provided format. Here are some examples:
* AWS
* GCP
* Azure
* Any Custom Infrastructure
# Reactions
Source: https://docs.velt.dev/self-host-data/reactions
Self-host your reactions data while using Velt's components. Keep reaction storage on your infrastructure with minimal metadata stored on Velt servers.
* This is currently only compatible with `setDocuments` method.
* Ensure that the data providers are set prior to calling `identify` method.
* The data provider methods must return the correct status code (e.g. 200 for success, 500 for errors) and success boolean in the response object. This ensures proper error handling and retries.
# Overview
Velt supports self-hosting your reactions and related data:
* Reactions can be stored on your own infrastructure, with only necessary identifiers on Velt servers.
* Velt Components automatically hydrate reaction data in the frontend by fetching from your configured data provider.
* This gives you full control over reaction data while maintaining all Velt collaboration features.
* This automatically also ensures that the in-app notifications content related to reactions is not stored on Velt servers. The content is generated using the reactions data in the frontend.
# How does it work?
When users add or remove reactions:
1. The SDK uses your configured [`ReactionAnnotationDataProvider`](/api-reference/sdk/models/data-models#reactionannotationdataprovider) to handle storage
2. Your data provider implements three key methods:
* `get`: Fetches reactions from your database
* `save`: Stores reactions and returns success/error
* `delete`: Removes reactions from your database
**The process works as follows:**
When a reaction operation occurs:
1. The SDK first attempts to save/delete the reaction on your database
2. If successful:
* The SDK updates Velt's servers with minimal metadata
* The [`PartialReactionAnnotation`](/api-reference/sdk/models/data-models#partialreactionannotation) object is updated with the reaction details including emoji, user, and metadata
* When the reaction is saved, this information is stored on your end
* Velt servers only store necessary identifiers, not the actual reaction content
3. If the operation fails, no changes are made to Velt's servers and the operation is retried if you have configured retries.
You can configure retries, timeouts, etc. for the data provider.
# Implementation Approaches
You can implement reaction self-hosting using either of these approaches:
1. **Endpoint based**: Provide endpoint URLs and let the SDK handle HTTP requests
2. **Function based**: Implement `get`, `save`, and `delete` methods yourself
Both approaches are fully backward compatible and can be used together.
| Feature | Function based | Endpoint based |
| ------------------ | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Best For** | Complex setups requiring middleware logic, dynamic headers, or transformation before sending | Standard REST APIs where you just need to pass the request "as-is" to the backend |
| **Implementation** | You write the `fetch()` or `axios` code | You provide the `url` string and `headers` object |
| **Flexibility** | High | Medium |
| **Speed** | Medium | High |
## Endpoint based DataProvider
Instead of implementing custom methods, you can configure endpoints directly and let the SDK handle HTTP requests.
### getConfig
Config-based endpoint for fetching reactions. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`GetReactionResolverRequest`](/api-reference/sdk/models/data-models#getreactionresolverrequest)
* Response format: [`ResolverResponse>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const reactionResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/reactions/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const reactionDataProvider = {
config: reactionResolverConfig
};
```
```js theme={null}
const reactionResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/reactions/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const reactionDataProvider = {
config: reactionResolverConfig
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
### saveConfig
Config-based endpoint for saving reactions. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`SaveReactionResolverRequest`](/api-reference/sdk/models/data-models#savereactionresolverrequest)
* Response format: [`ResolverResponse`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const reactionResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/reactions/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const reactionDataProvider = {
config: reactionResolverConfig
};
```
```js theme={null}
const reactionResolverConfig = {
saveConfig: {
url: 'https://your-backend.com/api/velt/reactions/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const reactionDataProvider = {
config: reactionResolverConfig
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
### deleteConfig
Config-based endpoint for deleting reactions. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`DeleteReactionResolverRequest`](/api-reference/sdk/models/data-models#deletereactionresolverrequest)
* Response format: [`ResolverResponse`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx theme={null}
const reactionResolverConfig = {
deleteConfig: {
url: 'https://your-backend.com/api/velt/reactions/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const reactionDataProvider = {
config: reactionResolverConfig
};
```
```js theme={null}
const reactionResolverConfig = {
deleteConfig: {
url: 'https://your-backend.com/api/velt/reactions/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
}
};
const reactionDataProvider = {
config: reactionResolverConfig
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
### Endpoint based Complete Example
```jsx theme={null}
const reactionResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/reactions/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
saveConfig: {
url: 'https://your-backend.com/api/velt/reactions/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
deleteConfig: {
url: 'https://your-backend.com/api/velt/reactions/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const reactionDataProvider = {
config: reactionResolverConfig
};
```
```js theme={null}
const reactionResolverConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/reactions/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
saveConfig: {
url: 'https://your-backend.com/api/velt/reactions/save',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
deleteConfig: {
url: 'https://your-backend.com/api/velt/reactions/delete',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const reactionDataProvider = {
config: reactionResolverConfig
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
## Function based DataProvider
Implement custom methods to handle data operations yourself.
### get
Method to fetch reactions from your database. On error we will retry.
* Param: [`GetReactionResolverRequest`](/api-reference/sdk/models/data-models#getreactionresolverrequest)
* Return: [`Promise>>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const fetchReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionDataProvider = {
get: fetchReactionsFromDB,
};
```
```js Other Frameworks theme={null}
const fetchReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionDataProvider = {
get: fetchReactionsFromDB,
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
```javascript theme={null}
// Build query from request
const { reactionAnnotationIds, documentIds, organizationId } = req.body;
const query = {};
if (reactionAnnotationIds?.length) {
query.annotationId = { $in: reactionAnnotationIds };
}
if (documentIds?.length) {
query.documentId = { $in: documentIds };
}
if (organizationId) {
query.organizationId = organizationId;
}
const annotations = await collection.find(query).toArray();
// Convert to Record
const result = {};
for (const annotation of annotations) {
result[annotation.annotationId] = annotation;
}
// Return response in required format
res.json({ data: result, success: true, statusCode: 200 });
```
```javascript theme={null}
// Build parameterized query
const { reactionAnnotationIds, documentIds, organizationId } = req.body;
const conditions = [];
const values = [];
let paramIndex = 1;
if (reactionAnnotationIds?.length) {
conditions.push(`annotation_id = ANY($${paramIndex++})`);
values.push(reactionAnnotationIds);
}
if (documentIds?.length) {
conditions.push(`document_id = ANY($${paramIndex++})`);
values.push(documentIds);
}
if (organizationId) {
conditions.push(`organization_id = $${paramIndex++}`);
values.push(organizationId);
}
const whereClause = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
const { rows } = await client.query(
`SELECT annotation_id, data FROM reaction_annotations ${whereClause}`,
values
);
// Convert to Record
const result = {};
for (const row of rows) {
result[row.annotation_id] = row.data;
}
// Return response in required format
res.json({ data: result, success: true, statusCode: 200 });
```
### save
Save reactions to your database. Return a success or error response. On error we will retry.
* Param: [`SaveReactionResolverRequest`](/api-reference/sdk/models/data-models#savereactionresolverrequest)
* Return: [`Promise>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const saveReactionsToDB = async (request) => {
const response = await fetch('/api/velt/reactions/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionDataProvider = {
save: saveReactionsToDB,
};
```
```js Other Frameworks theme={null}
const saveReactionsToDB = async (request) => {
const response = await fetch('/api/velt/reactions/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionDataProvider = {
save: saveReactionsToDB,
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
```javascript theme={null}
const { annotations, context } = req.body;
// Bulk upsert annotations
const operations = Object.entries(annotations).map(([id, annotation]) => ({
updateOne: {
filter: { annotationId: id },
update: {
$set: {
...annotation,
annotationId: id,
documentId: context?.documentId || annotation.documentId,
organizationId: context?.organizationId || annotation.organizationId,
}
},
upsert: true
}
}));
if (operations.length > 0) {
await collection.bulkWrite(operations);
}
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
```javascript theme={null}
const { annotations, context } = req.body;
// Transaction-based upsert
await client.query('BEGIN');
for (const [id, annotation] of Object.entries(annotations)) {
const data = { ...annotation, annotationId: id };
await client.query(
`INSERT INTO reaction_annotations (annotation_id, document_id, organization_id, data, updated_at)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (annotation_id)
DO UPDATE SET data = EXCLUDED.data, updated_at = NOW()`,
[id, annotation.documentId, annotation.organizationId, JSON.stringify(data)]
);
}
await client.query('COMMIT');
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
### delete
Delete reactions from your database. Return a success or error response. On error we will retry.
* Param: [`DeleteReactionResolverRequest`](/api-reference/sdk/models/data-models#deletereactionresolverrequest)
* Return: [`Promise>`](/api-reference/sdk/models/data-models#resolverresponse)
```jsx React / Next.js theme={null}
const deleteReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionDataProvider = {
delete: deleteReactionsFromDB,
};
```
```js Other Frameworks theme={null}
const deleteReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionDataProvider = {
delete: deleteReactionsFromDB,
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
```javascript theme={null}
const { annotationId } = req.body;
await collection.deleteOne({ annotationId });
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
```javascript theme={null}
const { annotationId } = req.body;
await client.query(
'DELETE FROM reaction_annotations WHERE annotation_id = $1',
[annotationId]
);
// Return response in required format
res.json({ success: true, statusCode: 200 });
```
### config
Configuration for the reaction data provider.
* Type: [`ResolverConfig`](/api-reference/sdk/models/data-models#resolverconfig). Relevant properties:
* `resolveTimeout`: Timeout duration (in milliseconds) for resolver operations
* `getRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for get operations.
* `saveRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for save operations.
* `deleteRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig). Configure retry behavior for delete operations.
```jsx theme={null}
const reactionResolverConfig = {
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
```
### Function based Complete Example
```jsx theme={null}
const fetchReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const saveReactionsToDB = async (request) => {
const response = await fetch('/api/velt/reactions/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const deleteReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionResolverConfig = {
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const reactionDataProvider = {
get: fetchReactionsFromDB,
save: saveReactionsToDB,
delete: deleteReactionsFromDB,
config: reactionResolverConfig
};
```
```js theme={null}
const fetchReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const saveReactionsToDB = async (request) => {
const response = await fetch('/api/velt/reactions/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const deleteReactionsFromDB = async (request) => {
const response = await fetch('/api/velt/reactions/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return await response.json();
};
const reactionResolverConfig = {
resolveTimeout: 2000,
getRetryConfig: { retryCount: 3, retryDelay: 2000 },
saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};
const reactionDataProvider = {
get: fetchReactionsFromDB,
save: saveReactionsToDB,
delete: deleteReactionsFromDB,
config: reactionResolverConfig
};
Velt.setDataProviders({ reaction: reactionDataProvider });
```
# Sample Data
```json theme={null}
{
"annotationId": "REACTION_ANNOTATION_ID",
"metadata": {
"apiKey": "API_KEY",
"documentId": "DOCUMENT_ID",
"organizationId": "ORGANIZATION_ID"
},
"icon": "HEART_FACE"
}
```
The reaction annotation on your database stores the actual reaction icon/emoji content.
```json theme={null}
{
"annotationId": "REACTION_ANNOTATION_ID",
"commentAnnotationId": "COMMENT_ANNOTATION_ID",
"from": {
"userId": "USER_ID"
},
"involvedUserIds": ["USER_ID"],
"isReactionResolverUsed": true,
"lastUpdated": 1768542354775,
"metadata": {
"apiKey": "API_KEY",
"clientDocumentId": "DOCUMENT_ID",
"clientOrganizationId": "ORGANIZATION_ID",
"documentId": "INTERNAL_DOC_ID",
"organizationId": "INTERNAL_ORG_ID"
},
"pageInfo": {
"baseUrl": "https://your-app.com",
"path": "/",
"url": "https://your-app.com/"
},
"reactions": [
{
"from": {
"userId": "USER_ID"
},
"lastUpdated": 1768542354775
}
],
"type": "reaction"
}
```
Only reaction identifiers and metadata are stored on Velt servers. The actual reaction icon/emoji remains on your infrastructure.
```json theme={null}
{
"annotationId": "COMMENT_ANNOTATION_ID",
"comments": [
{
"commentId": 184639,
"from": {
"userId": "USER_ID"
},
"isCommentResolverUsed": true,
"isCommentTextAvailable": true,
"reactionAnnotationIds": ["REACTION_ANNOTATION_ID"]
}
],
"metadata": {
"apiKey": "API_KEY",
"documentId": "DOCUMENT_ID",
"organizationId": "ORGANIZATION_ID"
}
}
```
The comment annotation on Velt servers keeps a reference to the reaction annotation IDs (`reactionAnnotationIds`), but the actual reaction content is stored on your infrastructure.
# Debugging
You can subscribe to `dataProvider` events to monitor and debug get, save, and delete operations. The event includes a `moduleName` field that identifies which module triggered the resolver call, helping you trace data provider requests.
You can also use the [Velt Chrome DevTools extension](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl) to inspect and debug your Velt implementation.
Type: [`ReactionResolverModuleName`](/api-reference/sdk/models/data-models#reactionresolvermodulename)
```jsx theme={null}
import { useVeltClient } from '@veltdev/react';
const { client } = useVeltClient();
useEffect(() => {
if (!client) return;
const subscription = client.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
console.log('Module Name:', event.moduleName);
});
return () => subscription?.unsubscribe();
}, [client]);
```
```javascript theme={null}
const subscription = Velt.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
console.log('Module Name:', event.moduleName);
});
// Unsubscribe when done
subscription?.unsubscribe();
```
# Users
Source: https://docs.velt.dev/self-host-data/users
Self-host your users' PII while using Velt's collaboration features. Keep sensitive user data on your infrastructure with only user IDs stored on Velt servers.
Ensure that the data providers are set prior to calling `identify` method.
# Overview
Velt supports self-hosting your users' personally identifiable information (PII):
* Only the userId is stored on Velt servers, keeping sensitive user metadata on your infrastructure
* Velt Components automatically hydrate user details in the frontend by fetching from your configured data provider
* This gives you full control over user data while maintaining all Velt functionality
## How does it work?
* When the SDK is initialized, it will call the [`UserDataProvider`](/api-reference/sdk/models/data-models#userdataprovider) you configure with the list of userIds that it needs to fetch for the currently set user, organization, document, etc.
* The [`UserDataProvider`](/api-reference/sdk/models/data-models#userdataprovider) takes in a list of userIds and returns a Record object with the userIds as keys and the user data as values.
# Implementation Approaches
You can implement user self-hosting using either of these approaches:
1. **Endpoint based**: Provide an endpoint URL and let the SDK handle HTTP requests
2. **Function based**: Implement the `get` method yourself
Both approaches are fully backward compatible and can be used together.
| Feature | Function based | Endpoint based |
| ------------------ | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Best For** | Complex setups requiring middleware logic, dynamic headers, or transformation before sending | Standard REST APIs where you just need to pass the request "as-is" to the backend |
| **Implementation** | You write the `fetch()` or `axios` code | You provide the `url` string and `headers` object |
| **Flexibility** | High | Medium |
| **Speed** | Medium | High |
## Endpoint based DataProvider
Instead of implementing custom methods, you can configure an endpoint directly and let the SDK handle HTTP requests.
### getConfig
Config-based endpoint for fetching users. The SDK automatically makes HTTP POST requests with the request body.
* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`GetUserResolverRequest`](/api-reference/sdk/models/data-models#getuserresolverrequest) - `{ organizationId: string, userIds: string[] }`
* Response format: [`ResolverResponse>`](/api-reference/sdk/models/data-models#resolverresponse)
The config-based approach uses a different request format than the custom methods approach. It passes `{ organizationId, userIds }` instead of just `userIds`.
```jsx theme={null}
const userConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/users/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
};
const userDataProvider = {
config: userConfig
};
```
```js theme={null}
const userConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/users/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
};
const userDataProvider = {
config: userConfig
};
Velt.setDataProviders({ user: userDataProvider });
```
### Endpoint based Complete Example
```jsx theme={null}
const userConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/users/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
};
const userDataProvider = {
config: userConfig
};
```
```js theme={null}
const userConfig = {
getConfig: {
url: 'https://your-backend.com/api/velt/users/get',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
},
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
};
const userDataProvider = {
config: userConfig
};
Velt.setDataProviders({ user: userDataProvider });
```
## Function based DataProvider
Here are the methods that you need to implement on the data provider:
### get
Method to fetch users from your database.
* Param: `string[]`: Array of userIds to fetch
* Return: `Promise>` or [`Promise>>`](/api-reference/sdk/models/data-models#resolverresponse)
Both response formats are supported for backward compatibility:
* **Old format**: `Record` - Returns user data directly
* **New format**: [`ResolverResponse>`](/api-reference/sdk/models/data-models#resolverresponse) - Returns `{ data, success, statusCode, message?, timestamp? }`
```jsx React / Next.js theme={null}
const fetchUsersFromDB = async (userIds) => {
const response = await fetch('/api/velt/users/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds })
});
return await response.json();
};
const userDataProvider = {
get: fetchUsersFromDB,
};
```
```js Other Frameworks theme={null}
const fetchUsersFromDB = async (userIds) => {
const response = await fetch('/api/velt/users/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds })
});
return await response.json();
};
const userDataProvider = {
get: fetchUsersFromDB,
};
Velt.setDataProviders({ user: userDataProvider });
```
```javascript theme={null}
const { userIds } = req.body;
const users = await collection.find({
userId: { $in: userIds }
}).toArray();
// Convert to Record
const result = {};
for (const user of users) {
result[user.userId] = user;
}
// Return response in required format
res.json(result);
```
```javascript theme={null}
const { userIds } = req.body;
const { rows } = await client.query(
'SELECT user_id, name, email, photo_url FROM users WHERE user_id = ANY($1)',
[userIds]
);
// Convert to Record
const result = {};
for (const row of rows) {
result[row.user_id] = {
userId: row.user_id,
name: row.name,
email: row.email,
photoUrl: row.photo_url
};
}
// Return response in required format
res.json(result);
```
### config
Configuration for the user data provider.
* Type: [`ResolverConfig`](/api-reference/sdk/models/data-models#resolverconfig). Relevant properties:
* `resolveUsersConfig`: [`ResolveUsersConfig`](/api-reference/sdk/models/data-models#resolveusersconfig). Controls whether Velt issues user-resolver requests during initialization to fetch contact lists at the organization, folder, and document levels. Use this to optimize performance in environments with a large number of users by preventing unnecessary user-data fetches. You can selectively disable user-resolver requests for specific scopes and instead provide your own user data via the [custom autocomplete feature](/async-collaboration/comments/customize-behavior#customautocompletesearch) instead.
* `organization`: boolean - Enable/disable user requests for organization users (default: true)
* `document`: boolean - Enable/disable user requests for document users (default: true)
* `folder`: boolean - Enable/disable user requests for folder users (default: true)
```jsx theme={null}
const userConfig = {
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
};
```
### Function based Complete Example
```jsx theme={null}
const fetchUsersFromDB = async (userIds) => {
const response = await fetch('/api/velt/users/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds })
});
return await response.json();
};
const userDataProvider = {
get: fetchUsersFromDB,
config: {
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
}
};
```
```js theme={null}
const fetchUsersFromDB = async (userIds) => {
const response = await fetch('/api/velt/users/get', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userIds })
});
return await response.json();
};
const userDataProvider = {
get: fetchUsersFromDB,
config: {
resolveUsersConfig: {
organization: false,
folder: false,
document: true
}
}
};
Velt.setDataProviders({ user: userDataProvider });
```
# Anonymous User Resolution
When a user who is not part of the contact list is tagged by email in a comment, they won't have a `userId`. The [`AnonymousUserDataProvider`](/api-reference/sdk/models/data-models#anonymoususerdataprovider) resolves these email → `userId` mappings automatically. 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`, backfills it into the comment data before persisting, and optionally writes a stub user record to the organization database.
* Type: [`AnonymousUserDataProvider`](/api-reference/sdk/models/data-models#anonymoususerdataprovider)
* `resolveUserIdsByEmail`: Callback that maps email addresses to user IDs. Called automatically at comment save time.
* Param: [`ResolveUserIdsByEmailRequest`](/api-reference/sdk/models/data-models#resolveuseridsbyemailrequest) - `{ organizationId: string, documentId?: string, folderId?: string, emails: string[] }`
* Return: [`Promise>>`](/api-reference/sdk/models/data-models#resolverresponse) - `{ statusCode, success, data: { [email]: userId } }`
* `config`: Optional configuration for timeout and retry behavior.
* Type: [`AnonymousUserDataProviderConfig`](/api-reference/sdk/models/data-models#anonymoususerdataproviderconfig)
* `resolveTimeout`: `number` - Timeout in milliseconds for the resolve call
* `getRetryConfig`: [`RetryConfig`](/api-reference/sdk/models/data-models#retryconfig) - `{ retryCount, retryDelay }`
Key behaviors:
* 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.
```jsx theme={null}
// Option 1: via VeltProvider
const anonymousUserDataProvider = {
resolveUserIdsByEmail: async (request) => {
const map = await yourBackend.lookupUserIds(request.emails);
return { statusCode: 200, success: true, data: map };
},
config: {
resolveTimeout: 5000,
getRetryConfig: { retryCount: 2, retryDelay: 300 },
},
};
// Option 2: standalone method
client.setAnonymousUserDataProvider({
resolveUserIdsByEmail: async (request) => {
const map = await yourBackend.lookupUserIds(request.emails);
return { statusCode: 200, success: true, data: map };
},
config: {
resolveTimeout: 5000,
getRetryConfig: { retryCount: 2, retryDelay: 300 },
},
});
// Option 3: via setDataProviders
client.setDataProviders({
anonymousUser: {
resolveUserIdsByEmail: async (request) => { /* ... */ },
},
});
```
```html theme={null}
```
# Sample Data
```json theme={null}
{
"USER_ID_1": {
"userId": "USER_ID_1",
"name": "John Doe",
"email": "john.doe@example.com",
"photoUrl": "https://example.com/photos/john.jpg",
"organizationId": "ORG_ID",
"role": "admin"
},
"USER_ID_2": {
"userId": "USER_ID_2",
"name": "Jane Smith",
"email": "jane.smith@example.com",
"photoUrl": "https://example.com/photos/jane.jpg",
"organizationId": "ORG_ID",
"role": "member"
}
}
```
```json theme={null}
{
"userId": "USER_ID_1"
}
```
Only the userId is stored on Velt servers. All other user metadata (name, email, photoUrl, etc.) remains on your infrastructure.
# Debugging
You can subscribe to `dataProvider` events to monitor and debug get operations. The event includes a `moduleName` field that identifies which module triggered the resolver call, helping you trace data provider requests.
You can also use the [Velt Chrome DevTools extension](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl) to inspect and debug your Velt implementation.
Type: [`UserResolverModuleName`](/api-reference/sdk/models/data-models#userresolvermodulename)
```jsx theme={null}
import { useVeltClient } from '@veltdev/react';
const { client } = useVeltClient();
useEffect(() => {
if (!client) return;
const subscription = client.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
console.log('Module Name:', event.moduleName);
});
return () => subscription?.unsubscribe();
}, [client]);
```
```javascript theme={null}
const subscription = Velt.on('dataProvider').subscribe((event) => {
console.log('Data Provider Event:', event);
console.log('Module Name:', event.moduleName);
});
// Unsubscribe when done
subscription?.unsubscribe();
```
# Temp release notes
Source: https://docs.velt.dev/temp-release-notes
### New Features
* \[**Comments**]: Added `markAsRead()` and `markAsUnread()` methods to mark comment annotations as read or unread for the current user.
```jsx theme={null}
// Using Hooks
const commentElement = useCommentUtils();
commentElement.markAsUnread("eUgq6G6zXxJmOT9eBXtT");
commentElement.markAsRead("eUgq6G6zXxJmOT9eBXtT");
// Using API methods
const commentElement = client.getCommentElement();
commentElement.markAsUnread("eUgq6G6zXxJmOT9eBXtT");
commentElement.markAsRead("eUgq6G6zXxJmOT9eBXtT");
```
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.markAsUnread("eUgq6G6zXxJmOT9eBXtT");
commentElement.markAsRead("eUgq6G6zXxJmOT9eBXtT");
```
### Improvements
* \[**Recorder**]: Added system sound capture when recording a browser tab.
* \[**Comments**]: You can now tag users by copy-pasting their email address. Previously, only manually typed emails worked for tagging users not on the contact list.
* \[**Comments**]: Added `viewedBy` and `reactionAnnotations` fields to comment annotation objects returned via REST APIs including [Get Comment Annotations](/api-reference/rest-apis/v2/comments-feature/comment-annotations/get-comment-annotations-v2) and [Get Comments](/api-reference/rest-apis/v2/comments-feature/comments/get-comments). These fields provide enhanced visibility into user engagement and reactions.
**`viewedBy`**: An array of User objects representing who has seen and read the comment annotation. Use this to track engagement, identify which stakeholders have reviewed feedback, or build custom read receipt indicators.
**`reactionAnnotations`**: An array of complete ReactionAnnotation objects containing the full reaction data for each comment. Each object includes the reaction ID, icon, user information, and metadata. Use this to display reaction counts, show who reacted with what emoji, or build custom reaction analytics.
```typescript theme={null}
// Comment Annotation Object
{
...
comments: [
{
...
reactionAnnotations?: ReactionAnnotation[]; // Complete reaction objects with full details
}
],
viewedBy?: User[]; // Users who have seen and read this annotation
}
```
### Bug Fixes
* \[**Recorder**]: Fixed an issue in the video editor where the playhead position was ignored after playback ended. When seeking or dragging the playhead after video completion, playback now correctly starts from the new position instead of always starting from the beginning.
### Improvements
* \[**Comments**]: Released v2 improved version of comment anchoring that makes comment positioning more dynamic and robust on more dynamic websites. This includes enhanced element detection for pin, text, and area comments, improved cursor display based on DOM element visibility, and better handling of image tag positioning with conditional relative positioning for container elements.
### Bug Fixes
* \[**Comments**]: Fixed an issue where draft comments with resolvers were incorrectly submitted before being finalized, causing data inconsistencies. Draft comments with resolvers now work properly and are only saved when explicitly published.
* \[**Access Control**]: Fixed an issue where organization access permissions were not being set correctly when using the permission provider. When users logged in, their organization-level permissions were failing to initialize properly. Organization access now gets assigned correctly during the identification process.
# Temp release notes input
Source: https://docs.velt.dev/temp-release-notes-input
### New Features
* \[**Core**]: Added `getHeartbeat()` method to subscribe to user-specific heartbeat data. You can retrieve heartbeat data for the current user or specify a `userId` to get heartbeats for any user, providing better visibility into active sessions per user.
```jsx theme={null}
// Using Hooks
import { useHeartbeat } from "@veltdev/react";
import { useEffect } from "react";
const { data: heartbeatData } = useHeartbeat();
useEffect(() => {
console.log("Heartbeat data: ", heartbeatData);
}, [heartbeatData]);
// Using API methods
client.getHeartbeat().subscribe((heartbeatData) => {
console.log("Heartbeat data: ", heartbeatData);
});
```
```html theme={null}
```
**Heartbeat Interfaces:**
```typescript theme={null}
export interface HeartbeatConfig {
userId?: string;
}
export interface GetHeartbeatResponse {
data: Heartbeat[] | null;
}
```
### Improvements
* \[**Comments**]: Added location removal logic when video plays in Timeline Player. Comment pins are now properly removed as the video plays, ensuring accurate timeline visualization.
* \[**Comments**]: Added change detection in Timeline Player. The timeline bar now moves correctly when hovering over the play button or timeline, ensuring responsive playback controls.
### Bug Fixes
* \[**Core**]: Fixed disconnect errors in the heartbeat feature. The SDK now handles network issues at the client side without triggering server disconnect errors.
* \[**Core**]: Fixed user color and text color persistence during auto-login. Colors passed during `identify()` now persist correctly in auto-login scenarios, ensuring consistent user identification.
* \[**Presence**]: Fixed `multipleUsersOnline` event firing when only a single user is present. The presence feature now correctly identifies single-user scenarios, preventing unwanted analytics events.
### Bug Fixes
* \[**Comments**]: Fixed race condition errors in Player Timeline by adding proper lifecycle management. The player timeline now properly handles component destruction, preventing "undefined" errors that could occur when the component was destroyed during asynchronous operations.
* \[**Notifications**]: Fixed error handling for document notifications by adding metadata validation. The SDK now checks if notification metadata exists before processing document notifications, preventing unnecessary errors in analytics tracking when notifications are missing required data.
### Bug Fixes
* \[**Core**]: Fixed user resolution for tagged and assigned users in contact lists. Users added via `updateContactList()` are now properly cached, ensuring that tagged and assigned users resolve correctly from cached data instead of failing to load.
### Bug Fixes
* \[**Core**]: Improved `updateContactList()` method behavior by removing unnecessary validation checks. The method now directly applies user-provided contact list data without additional validation, giving you more control over contact list management.
### Bug Fixes
* \[**Recorder**]: Fixed an issue where zoom was not applied unless the zoom section in the timeline was modified directly.
### Bug Fixes
* \[**Recorder**]: Fixed an issue where the zoom animation in the editor preview had a curve in the transition. The editor preview now displays smooth transitions to the correct coordinates instead of curved transitions.
* \[**Recorder**]: Fixed an issue where VeltIf was throwing an error when evaluating MediaStream object.
* \[**Comments**]: Fixed sanitization of `target` attributes in comment HTML content. The DomPurifier now preserves `target` attributes in comment HTML fields, allowing users to include links with target specifications (such as `target="_blank"`) in their comments.
### Improvements
* \[**Core**]: Optimized the initialization module and significantly reduced the SDK initialization time.
### New Features
* \[**Core**]: Added `fetchDebugInfo()` and `getDebugInfo()` methods to retrieve debugging information about your Velt implementation. Use `fetchDebugInfo()` to get a one-time snapshot or `getDebugInfo()` to subscribe to real-time updates of key setup info like sdk version, apikey, user, organizationId, documentId, folderID version, locations etc. You can also get this info from [Velt's Chrome devtools](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl)
```jsx theme={null}
// Using API methods - One-time fetch
await client.fetchDebugInfo();
// Using API methods - Subscribe to updates
client.getDebugInfo().subscribe((debugInfo) => {
console.log("Debug info: ", debugInfo);
});
```
```html theme={null}
```
**Debug Info Interface:**
```typescript theme={null}
export interface VeltDebugInfo {
veltVersion?: string;
apiKey?: string;
serverMap?: {
organization?: OrganizationMetadata;
documents?: DocumentMetadata[];
locations?: Location[];
folder?: FolderMetadata;
user?: User;
};
clientMap?: {
organization?: OrganizationMetadata;
documents?: DocumentMetadata[];
locations?: Location[];
folder?: FolderMetadata;
user?: User;
};
}
```
### Improvements
* \[**Access Control**]: Prevent users from adding comments, reactions, recorders, and area comments through the SDK when `access context` is used and the user doesn't have access to the specific context.
### New Features
* \[**Comments**]: Added preliminary infra required to capture screenshots in comments.
### Improvements
* \[**Access Control**]: Now you can set feature level permissions using Access Context. Access Context allows you to set granular, feature-level permissions using custom metadata. When configured, new feature data is added and existing feature data is fetched only for the access context values the current user has access to. [Learn more →](/key-concepts/overview#set-feature-level-permissions-using-access-context-custom-metadata)
### Improvements
* \[**Recorder**]: Updated video editor timeline picture mode design for improved visual clarity. The unselected portion of timeline pictures now displays with a cleaner design that better distinguishes selected from unselected frames.
* \[**Recorder**]: Fixed audio merging in screen recording to combine microphone and tab audio. When recording with both microphone and tab audio enabled, both audio sources are now properly merged into the final recording.
### Bug Fixes
* \[**Comments**]: Fixed type definition for `selectCommentByAnnotationId()` and made `annotationId` parameter optional.
* \[**Comments**]: Fixed embed mode logic in comments sidebar to support multiple embedded sidebars.
### New Features
* \[**Access Control**]: Added the new Permission Provider feature. With this approach, Velt pings your defined endpoint to verify whether a user should be granted access to a resource (organization, folder, or document). This ensures that your backend is still the source of truth and you don't have to sync the permissions into Velt directly. [Learn more →](/key-concepts/overview#c-real-time-permission-provider)
* \[**Access Control**]: Added a config to automatically revoke permissions, including revoking access to documents, folders, and optionally organizations when users log out or when documents are unset. This ensures immediate permission removal without requiring manual cleanup. [Learn more →](/key-concepts/overview#c-real-time-permission-provider)
* \[**Access Control**]: Added various Permission Provider events to monitor the sequence of permission check events for debugging and tracking purposes. [Learn more →](/api-reference/sdk/models/data-models#permissionproviderevent)
### Improvements
* \[**Access Control**]: Simplified Permission Provider implementation by removing `onResourceAccessRequired` call and signature handling from client SDK. Permission handling is now fully managed internally by the SDK. You no longer need to handle signatures or make `onResourceAccessRequired` calls. The SDK automatically handles permission caching, validation, and synchronization with the backend. [Learn more →](/api-reference/sdk/api/api-methods#setpermissionprovider)
### New Features
* \[**Recorder**]: Added video editor timeline image preview to display frame snapshots in the timeline. This helps you quickly navigate to specific scenes without scrubbing through the entire video.
```jsx theme={null}
```
```html theme={null}
```
The timeline preview only works when both `videoEditorTimelinePreview` and `videoEditor` are set to `true`.
### Improvements
* \[**Comments**]: Enhanced `selectCommentByAnnotationId()` to close the selected comment annotation when called with no arguments or an invalid ID.
```jsx theme={null}
// Using Hooks
const commentElement = useCommentUtils();
// Close the currently selected annotation
commentElement.selectCommentByAnnotationId();
commentElement.selectCommentByAnnotationId('invalid-id');
// Using API methods
const commentElement = client.getCommentElement();
// Close the currently selected annotation
commentElement.selectCommentByAnnotationId();
commentElement.selectCommentByAnnotationId('invalid-id');
```
```html theme={null}
```
### New Features
* \[**Access Control**]: Added early version of feature level permissions using Access Context. Access Context allows you to set granular, feature-level permissions using custom metadata. When configured, new feature data is added and existing feature data is fetched only for the access context values the current user has access to. [Learn more →](/key-concepts/overview#set-feature-level-permissions-using-access-context-custom-metadata)
### Improvements
* \[**Access Control**]: Added `source` field to Permission Provider requests to identify which method triggered the request. The `source` field helps you debug and trace which SDK method initiated the permission check. [Learn more →](/key-concepts/overview#c-real-time-permission-provider)
* \[**Access Control**]: Added various Permission Provider events to monitor the sequence of permission check events for debugging and tracking purposes. [Learn more →](/api-reference/sdk/models/data-models#permissionproviderevent)
### Bug Fixes
* \[**Notifications**]: Fixed notification fetching with Permission Provider when document IDs needed mapping to client document IDs.
# Conditional Templates
Source: https://docs.velt.dev/ui-customization/conditional-templates
* Conditional Templates let you conditionally show or hide parts of the Velt Component Wireframes.
* You can add conditions based on the same data models available in [Template Variables](/ui-customization/template-variables).
There are two ways to use Conditional Templates:
## 1. Using `Velt If` component
* Wrap wireframe components and html elements in `Velt If` component.
* If the condition is not met, the component will not be rendered.
* Good for targeting groups of components at once.
```jsx theme={null}
{/* Content to render if condition is true */}
```
```html theme={null}
```
## 2. Using `Velt If` attribute
* Add `Velt If` attribute to existing wireframe components.
* If the condition is not met, the component will not be rendered.
* Good for targeting a single component.
```jsx theme={null}
```
```html theme={null}
```
## Syntax
The condition is specified as a string that will be evaluated as a JavaScript expression. Here is a sample syntax:
Syntax: `{} `
Example: `{annotation.status.id} === 'OPEN'`
* Template variables need to be enclosed in curly braces: `{variable.property}`
* Operators:
* Comparison operators: `===`, `==`, `!==`, `>`, `<`, `>=`, `<=`
* Logical operators: `&&`, `||`, `!`
* Value can be a string, number or boolean.
## Strict CSP Policy
Some environments enforce strict Content Security Policies (CSP) that disallow `unsafe-eval`. In such cases, the default method could be blocked and the conditions may not be evaluated.
Here is how you can use an alternative approach to evaluate `Velt If` conditions. This method uses a safer, CSP-compliant parser to evaluate your `Velt If` conditions.
```javascript theme={null}
client.enableSafeEval();
client.disableSafeEval();
```
```javascript theme={null}
Velt.enableSafeEval();
Velt.disableSafeEval();
```
## Examples
```jsx theme={null}
{/* 1. Show content only for open annotations: */}
This annotation is currently open.
{/* 2. Display a message for annotations with more than 5 comments: */}
This is a popular annotation!
{/* 3. Combine multiple conditions: */}
This is a new open annotation without any comments yet.
```
```html theme={null}
This annotation is currently open.
This is a popular annotation!
This is a new open annotation without any comments yet.
```
# Action Components
Source: https://docs.velt.dev/ui-customization/custom-action-component
* A customizable button component that can be used to add custom actions and extend the functionality of any Velt component. Some examples include:
* Add custom filtering, sorting and grouping to the Comment Sidebar
* Add custom actions to each item in the Notifications panel.
* Add custom actions to the Comment Dialog.
* In the [callback event](#callback-event), in addition to returning the button context, it also returns the key component data that it sits within Eg: `CommentAnnotation`, `Comment`, `Notification`, `CommentSidebarData` etc.
## Types
* `button`: Basic clickable button
* `button-toggle`: Toggleable button that maintains state
* `single-select`: Single select (radio button) group
* `multi-select`: Multi select (checkbox) group
## Component Props
| Property | Type | Required | Description |
| ---------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Yes | Unique identifier for the button. This can be a static id or a dynamic id using a template variable. |
| `type` | string | No | Button type: 'button' (default), 'button-toggle', 'multi-select', or 'single-select' |
| `disabled` | boolean | No | Whether the button is disabled |
| `active` | boolean | No | Whether the button is in active/selected state. Use this if you want to add a default state to the button. Applies to 'button-toggle', 'multi-select', or 'single-select' buttons. |
| `group` | string | No | Group identifier for single-select/multi-select buttons |
## Usage
### Button
```jsx {6-8} theme={null}
```
**Handle the button click event:**
```jsx theme={null}
// Hook
const veltButtonClickEventData = useVeltEventCallback('veltButtonClick');
useEffect(() => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext.clickedButtonId === 'close-sidebar') {
// Close sidebar
}
}
}, [veltButtonClickEventData]);
// API Method
client.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext.clickedButtonId === 'close-sidebar') {
// Close sidebar
}
}
});
```
```html {6-8} theme={null}
```
**Handle the button click event:**
```js theme={null}
Velt.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext.clickedButtonId === 'close-sidebar') {
// Close sidebar
}
}
});
```
### Button Toggle
```jsx {6-8} theme={null}
{/* Optional: Set the active prop to true if you want to add a default state to the button */}
```
**Handle the button click event:**
```jsx theme={null}
// Hook
const veltButtonClickEventData = useVeltEventCallback('veltButtonClick');
useEffect(() => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.clickedButtonId === 'toggleCommentPins') {
if (veltButtonClickEventData.buttonContext?.selections?.ungrouped['toggleCommentPins']) {
commentElement.showCommentsOnDom();
} else {
commentElement.hideCommentsOnDom();
}
}
}
}, [veltButtonClickEventData]);
// API Method
client.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.clickedButtonId === 'toggleCommentPins') {
if (veltButtonClickEventData.buttonContext?.selections?.ungrouped['toggleCommentPins']) {
commentElement.showCommentsOnDom();
} else {
commentElement.hideCommentsOnDom();
}
}
}
});
```
```html {6-8} theme={null}
```
**Handle the button click event:**
```js theme={null}
Velt.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.clickedButtonId === 'toggleCommentPins') {
if (veltButtonClickEventData.buttonContext?.selections?.ungrouped['toggleCommentPins']) {
commentElement.showCommentsOnDom();
} else {
commentElement.hideCommentsOnDom();
}
}
}
});
```
### Single Select Button Group
```jsx {6-12} theme={null}
```
**Handle the button click event:**
```jsx theme={null}
// Hook
const veltButtonClickEventData = useVeltEventCallback('veltButtonClick');
useEffect(() => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.groupId === 'custom-filter') {
const selectedFilter = veltButtonClickEventData.buttonContext?.selections?.['custom-filter'];
if (selectedFilter?.unread) {
// show unread comments
} else if (selectedFilter?.mentions) {
// show comments with mentions
}
}
}
}, [veltButtonClickEventData]);
// API Method
client.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.groupId === 'custom-filter') {
const selectedFilter = veltButtonClickEventData.buttonContext?.selections?.['custom-filter'];
if (selectedFilter?.unread) {
// show unread comments
} else if (selectedFilter?.mentions) {
// show comments with mentions
}
}
}
});
```
```html {6-12} theme={null}
```
**Handle the button click event:**
```js theme={null}
Velt.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.groupId === 'custom-filter') {
const selectedFilter = veltButtonClickEventData.buttonContext?.selections?.['custom-filter'];
if (selectedFilter?.unread) {
// show unread comments
} else if (selectedFilter?.mentions) {
// show comments with mentions
}
}
}
});
```
### Multi Select Button Group
```jsx {6-12} theme={null}
```
**Handle the button click event:**
```jsx theme={null}
// Hook
const veltButtonClickEventData = useVeltEventCallback('veltButtonClick');
useEffect(() => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.groupId === 'custom-filter') {
const selections = veltButtonClickEventData.buttonContext?.selections?.['custom-filter'];
if (selections?.unread) {
// show unread comments
}
if (selections?.mentions) {
// show comments with mentions
}
}
}
}, [veltButtonClickEventData]);
// API Method
client.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.groupId === 'custom-filter') {
const selections = veltButtonClickEventData.buttonContext?.selections?.['custom-filter'];
if (selections?.unread) {
// show unread comments
}
if (selections?.mentions) {
// show comments with mentions
}
}
}
});
```
```html {6-12} theme={null}
```
**Handle the button click event:**
```js theme={null}
Velt.on('veltButtonClick').subscribe(veltButtonClickEventData => {
if (veltButtonClickEventData) {
if (veltButtonClickEventData.buttonContext?.groupId === 'custom-filter') {
const selections = veltButtonClickEventData.buttonContext?.selections?.['custom-filter'];
if (selections?.unread) {
// show unread comments
}
if (selections?.mentions) {
// show comments with mentions
}
}
}
});
```
### Callback Event
The Velt Button Wireframe emits events when users interact with it. You can listen to these events to implement custom behaviors.
* Returns: [VeltButtonClickEvent](/api-reference/sdk/models/data-models#veltbuttonclickevent)
```jsx theme={null}
// Hook
const veltButtonClickEventData = useVeltEventCallback('veltButtonClick');
useEffect(() => {
if (veltButtonClickEventData) {
// Handle button click event response
}
}, [veltButtonClickEventData]);
// API Method
client.on('veltButtonClick').subscribe((veltButtonClickEventData) => {
// Handle button click event response
});
```
```js theme={null}
Velt.on('veltButtonClick').subscribe((veltButtonClickEventData) => {
// Handle button click event response
});
```
### Set Dynamic ID to Velt Button
* You can use template variables to set the id.
```jsx theme={null}
```
```html theme={null}
```
### Reset Velt Button State
* Reset the state of Velt Button components programmatically.
* Params: (optional) [VeltResetButtonStateConfig](/api-reference/sdk/models/data-models#veltresetbuttonstateconfig)
```jsx theme={null}
// Reset state for a specific button in a given group
client.resetVeltButtonState({id: 'openSidebar', group: 'multi'});
// Reset state for all buttons in a given group
client.resetVeltButtonState({group: 'multi'});
// Reset state for a specific button in all groups
client.resetVeltButtonState({id: 'openSidebar'});
// Reset state for all buttons
client.resetVeltButtonState();
```
```js theme={null}
// Reset state for a specific button in a given group
Velt.resetVeltButtonState({id: 'openSidebar', group: 'multi'});
// Reset state for all buttons in a given group
Velt.resetVeltButtonState({group: 'multi'});
// Reset state for a specific button in all groups
Velt.resetVeltButtonState({id: 'openSidebar'});
// Reset state for all buttons
Velt.resetVeltButtonState();
```
# Custom Button
Source: https://docs.velt.dev/ui-customization/features/async/arrows/custom-button
## Custom Arrow Button
If you want to replace the default arrow button with your own custom button, you can pass it in as a child component.
```js React / Next.js theme={null}
import { VeltArrowTool } from '@veltdev/react';
function YourComponent() {
return (
//custom arrow button goes here
)
}
```
```html HTML theme={null}
```
# Parts
Source: https://docs.velt.dev/ui-customization/features/async/arrows/parts
We offer several parts which can be used like classes. Full list below.
The component is encapsulated in Shadow DOM, which is isolated from the normal DOM.
Set whatever CSS rules you want.
The part lets you target a specific element within a Shadow DOM.
Reference the table below to see what parts we expose.
Alternatively, you can directly inspect the component HTML to see what parts are available.
| property | description |
| ------------------ | ----------------------------------------- |
| `container` | Targets the comment tool container |
| `button-container` | Targets the comment tool button container |
| `button-icon` | Targets the comment tool button SVG icon |
```css Tool theme={null}
velt-arrow-tool::part(button-icon) {
width: 1.5rem;
height: 1.5rem;
}
```
# Slots
Source: https://docs.velt.dev/ui-customization/features/async/arrows/slots
Provide a template for the Arrow Tool.
Target the `button` slot with your own custom template.
```js React / Next.js theme={null}
import {
VeltArrowTool
} from '@veltdev/react';
export default function App() {
return (
<>
Arrow
>
);
}
```
```html HTML theme={null}
Arrow documentation
Arrow
```
# Variables
Source: https://docs.velt.dev/ui-customization/features/async/arrows/variables
To update CSS variables for the Arrow Tool, please refer to [Global Styles](/global-styles/global-styles)
# Comment Bubble
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-bubble
This button shows the comment count and the author's avatar. This is used in Popover comments feature.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltCommentBubbleWireframe
```jsx theme={null}
```
```html theme={null}
```
## CommentsCount
```jsx theme={null}
```
```html theme={null}
```
## Avatar
```jsx theme={null}
```
```html theme={null}
```
## UnreadIcon
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
```jsx theme={null}
```
```jsx theme={null}
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
Default: `false`
**Using Props:**
```js theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
**Using Props:**
```HTML theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
### Open Dialog on Click
Control whether the comment dialog opens when the bubble is clicked.
Default: `true`
```jsx theme={null}
```
```html theme={null}
```
# Comment Dialog
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-components
Comments Dialog Component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## Ghost Banner
```jsx theme={null}
```
```html theme={null}
```
## Private Banner
```jsx theme={null}
```
```html theme={null}
```
## Assignee Banner
```jsx theme={null}
```
```html theme={null}
```
### Resolve Button (Assignee Banner)
```jsx theme={null}
```
```html theme={null}
```
### Unresolve Button (Assignee Banner)
```jsx theme={null}
```
```html theme={null}
```
### User Avatar (Assignee Banner)
```jsx theme={null}
```
```html theme={null}
```
### User Name (Assignee Banner)
```jsx theme={null}
```
```html theme={null}
```
## Header
```jsx theme={null}
// Pin Dialog Header
// Sidebar Dialog Header
```
```html theme={null}
```
### Status (Header)
```jsx theme={null}
```
```html theme={null}
```
#### Trigger (Header Status)
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Header Status Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Header Status Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow (Header Status Trigger)**
```jsx theme={null}
```
```html theme={null}
```
#### Content (Header Status)
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Header Status Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Header Status Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Header Status Content Item)**
```jsx theme={null}
```
```html theme={null}
```
### Priority (Header)
```jsx theme={null}
```
```html theme={null}
```
#### Trigger (Header Priority)
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Header Priority Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Header Priority Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow (Header Priority Trigger)**
```jsx theme={null}
```
```html theme={null}
```
#### Content (Header Priority)
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Header Priority Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Header Priority Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Header Priority Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tick (Header Priority Content Item)**
```jsx theme={null}
```
```html theme={null}
```
### Options (Header)
```jsx theme={null}
```
```html theme={null}
```
#### Trigger (Header Options)
```jsx theme={null}
```
```html theme={null}
```
#### Content (Header Options)
```jsx theme={null}
```
```html theme={null}
```
##### **Mark As Read (Header Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Make Private (Header Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Enable (Header Options Content Make Private)**
```jsx theme={null}
```
```html theme={null}
```
##### **Disable (Header Options Content Make Private)**
```jsx theme={null}
```
```html theme={null}
```
##### **Assign (Header Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Edit (Header Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Delete (Header Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Thread (Header Options Content Delete)**
```jsx theme={null}
```
```html theme={null}
```
##### **Comment (Header Options Content Delete)**
```jsx theme={null}
```
```html theme={null}
```
##### **Notification (Header Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Subscribe (Header Options Content Notification)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unsubscribe (Header Options Content Notification)**
```jsx theme={null}
```
```html theme={null}
```
### Copy Link (Header)
```jsx theme={null}
```
```html theme={null}
```
### Resolve Button (Header)
```jsx theme={null}
```
```html theme={null}
```
### Custom Annotation Dropdown (Header)
```jsx theme={null}
```
```html theme={null}
```
#### Trigger (Header Custom Annotation Dropdown)
```jsx theme={null}
```
```html theme={null}
```
##### **List (Header Custom Annotation Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Header Custom Annotation Dropdown Trigger List)**
```jsx theme={null}
```
```html theme={null}
```
##### **Remaining Count (Header Custom Annotation Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Placeholder (Header Custom Annotation Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow (Header Custom Annotation Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
#### Content (Header Custom Annotation Dropdown)
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Header Custom Annotation Dropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Label (Header Custom Annotation Dropdown Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Header Custom Annotation Dropdown Content Item)**
```jsx theme={null}
```
```html theme={null}
```
### Comment Index (Header)
```jsx theme={null}
```
```html theme={null}
```
### Comment Number (Header)
The annotation number displays in the comment dialog header, providing a persistent identifier for each comment that remains constant across sessions. This number makes it easy to reference specific comments in team discussions or documentation.
```jsx theme={null}
```
```html theme={null}
```
### Comment Category (Header)
```jsx theme={null}
```
```html theme={null}
```
### Comment Suggestion Status (Header)
```jsx theme={null}
```
```html theme={null}
```
### Navigation Button (Header)
```jsx theme={null}
```
```html theme={null}
```
## Private Badge
```jsx theme={null}
```
```html theme={null}
```
## Body
```jsx theme={null}
```
```html theme={null}
```
### Threads (Body)
```jsx theme={null}
```
```html theme={null}
```
#### Thread Card (Body Threads)
```jsx theme={null}
```
```html theme={null}
```
##### **Avatar (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unread (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Seen Dropdown (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Body Threads Thread Card Seen Dropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Body Threads Thread Card Seen Dropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Title (Body Threads Thread Card Seen Dropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Items (Body Threads Thread Card Seen Dropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Body Threads Thread Card Seen Dropdown Content Items)**
```jsx theme={null}
```
```html theme={null}
```
##### **Time (Body Threads Thread Card Seen Dropdown Content Items Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Body Threads Thread Card Seen Dropdown Content Items Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Avatar (Body Threads Thread Card Seen Dropdown Content Items Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Time (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Device Type (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Edited (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Options (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Body Threads Thread Card Options)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Body Threads Thread Card Options)**
```jsx theme={null}
```
```html theme={null}
```
##### **Mark As Read (Body Threads Thread Card Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Edit (Body Threads Thread Card Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Delete (Body Threads Thread Card Options Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Thread (Body Threads Thread Card Options Content Delete)**
```jsx theme={null}
```
```html theme={null}
```
##### **Comment (Body Threads Thread Card Options Content Delete)**
```jsx theme={null}
```
```html theme={null}
```
##### **Message (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **EditComposer (Body Threads Thread Card)**
Conditionally renders when a comment is being edited.
```jsx theme={null}
```
```html theme={null}
```
##### **Reaction Tool (Body Threads Thread Card)**
Use `excludeReactionIds` to hide specific reactions from the reaction picker.
```jsx theme={null}
```
```html theme={null}
```
##### **Reactions (Body Threads Thread Card)**
Use `excludeReactionIds` to hide specific reactions from display.
```jsx theme={null}
```
```html theme={null}
```
##### **ReactionPin (Body Threads Thread Card)**
Display a specific reaction as a pinned standalone element.
```jsx theme={null}
```
```html theme={null}
```
**Props**:
* `reactionId` ([`IVeltCommentDialogThreadCardReactionPinProps`](/api-reference/sdk/models/data-models#iveltcommentdialogthreadcardreactionpinprops)): Unique identifier of the reaction to pin and display
##### **Attachments (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Image (Body Threads Thread Card Attachments)**
```jsx theme={null}
```
```html theme={null}
```
##### **Preview (Body Threads Thread Card Attachments Image)**
```jsx theme={null}
```
```html theme={null}
```
##### **Delete (Body Threads Thread Card Attachments Image)**
```jsx theme={null}
```
```html theme={null}
```
##### **Download (Body Threads Thread Card Attachments Image)**
```jsx theme={null}
```
```html theme={null}
```
##### **Other (Body Threads Thread Card Attachments)**
```jsx theme={null}
```
```html theme={null}
```
##### **Delete (Body Threads Thread Card Attachments Other)**
```jsx theme={null}
```
```html theme={null}
```
##### **Download (Body Threads Thread Card Attachments Other)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Body Threads Thread Card Attachments Other)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Body Threads Thread Card Attachments Other)**
```jsx theme={null}
```
```html theme={null}
```
##### **Size (Body Threads Thread Card Attachments Other)**
```jsx theme={null}
```
```html theme={null}
```
##### **Recordings (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Autocomplete Chip Tooltip (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Body Threads Thread Card Autocomplete Chip Tooltip)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Body Threads Thread Card Autocomplete Chip Tooltip)**
```jsx theme={null}
```
```html theme={null}
```
##### **Description (Body Threads Thread Card Autocomplete Chip Tooltip)**
```jsx theme={null}
```
```html theme={null}
```
##### **Reactions Panel (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Items (Body Threads Thread Card Reactions Panel)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Body Threads Thread Card Reactions Panel Items)**
```jsx theme={null}
```
```html theme={null}
```
##### **Emoji (Body Threads Thread Card Reactions Panel Items Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Reaction Pin (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Emoji (Body Threads Thread Card Reaction Pin)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Body Threads Thread Card Reaction Pin)**
```jsx theme={null}
```
```html theme={null}
```
##### **Reaction Pin Tooltip (Body Threads Thread Card)**
```jsx theme={null}
```
```html theme={null}
```
##### **Users (Body Threads Thread Card Reaction Pin Tooltip)**
```jsx theme={null}
```
```html theme={null}
```
##### **User (Body Threads Thread Card Reaction Pin Tooltip Users)**
```jsx theme={null}
```
```html theme={null}
```
##### **Avatar (Body Threads Thread Card Reaction Pin Tooltip Users User)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Body Threads Thread Card Reaction Pin Tooltip Users User)**
```jsx theme={null}
```
```html theme={null}
```
##### **Reaction Tool (Body Threads Thread Card)**
```jsx theme={null}
{/* Your custom element */}
```
```html theme={null}
```
##### **Reply (Body Threads Thread Card)**
The Reply component provides a quick reply button on each comment thread card, enabling users to respond directly without expanding the full comment dialog. This streamlines the commenting workflow in collaborative environments.
This button is hidden by default and can be enabled through wireframe customization.
```jsx theme={null}
```
```html theme={null}
```
##### **Assign Button (Body Threads Thread Card)**
The Assign Button component allows you to customize the assign button that appears on individual comment thread cards.
```jsx theme={null}
```
```html theme={null}
```
#### MoreReply (Body Threads)
```jsx theme={null}
```
```html theme={null}
```
### Reply Avatars (Body)
```jsx theme={null}
```
```html theme={null}
```
#### List (Body Reply Avatars)
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Body Reply Avatars List)**
```jsx theme={null}
```
```html theme={null}
```
#### Remaining Count (Body Reply Avatars)
```jsx theme={null}
```
```html theme={null}
```
### Toggle Reply (Body)
```jsx theme={null}
```
```html theme={null}
```
#### Icon (Body Toggle Reply)
```jsx theme={null}
```
```html theme={null}
```
#### Count (Body Toggle Reply)
```jsx theme={null}
```
```html theme={null}
```
#### Text (Body Toggle Reply)
```jsx theme={null}
```
```html theme={null}
```
### Hide Reply (Body)
The Hide Reply component adds a button to hide replies in the comment dialog, letting you control when replies are visible to match your workflow.
```jsx theme={null}
```
```html theme={null}
```
## Composer
```jsx theme={null}
```
```html theme={null}
```
### Avatar (Composer)
```jsx theme={null}
```
```html theme={null}
```
### Attachments (Composer)
```jsx theme={null}
```
```html theme={null}
```
#### Selected (Composer Attachments)
```jsx theme={null}
```
```html theme={null}
```
##### **Image (Composer Attachments Selected)**
```jsx theme={null}
```
```html theme={null}
```
##### **Other (Composer Attachments Selected)**
```jsx theme={null}
```
```html theme={null}
```
#### Invalid (Composer Attachments)
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Composer Attachments Invalid)**
```jsx theme={null}
```
```html theme={null}
```
### Recordings (Composer)
```jsx theme={null}
```
```html theme={null}
```
### Input (Composer)
```jsx theme={null}
```
```html theme={null}
```
### Format Toolbar (Composer)
The format toolbar contains text formatting buttons (bold, italic, underline, strikethrough).
```jsx theme={null}
```
```html theme={null}
```
#### Button (Composer Format Toolbar)
Individual format button with type: `bold`, `italic`, `underline`, or `strikethrough`.
```jsx theme={null}
```
```html theme={null}
```
### Action Button (Composer)
Supports types: `autocomplete`, `file`, `attachments`, `audio`, `video`, `screen`, `format`, `submit`.
```jsx theme={null}
```
```html theme={null}
```
### Assign User (Composer)
```jsx theme={null}
```
```html theme={null}
```
### Visibility Dropdown (Composer)
```jsx theme={null}
```
```html theme={null}
```
#### Trigger (Composer Visibility Dropdown)
```jsx theme={null}
```
```html theme={null}
```
##### **Label (Composer Visibility Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Composer Visibility Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
#### Content (Composer Visibility Dropdown)
```jsx theme={null}
```
```html theme={null}
```
##### **Public (Composer Visibility Dropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Private (Composer Visibility Dropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
### Autocomplete Option (Composer)
For standalone autocomplete primitive components (e.g., `VeltAutocompleteOption`, `VeltAutocompleteChip`), see the [Autocomplete Primitives](/ui-customization/features/async/comments/comment-dialog-primitives/overview#veltautocomplete) section.
```jsx theme={null}
```
```html theme={null}
```
#### Icon (Composer Autocomplete Option)
```jsx theme={null}
```
```html theme={null}
```
#### Name (Composer Autocomplete Option)
```jsx theme={null}
```
```html theme={null}
```
#### Description (Composer Autocomplete Option)
```jsx theme={null}
```
```html theme={null}
```
#### Error Icon (Composer Autocomplete Option)
```jsx theme={null}
```
```html theme={null}
```
### Autocomplete Group Option (Composer)
```jsx theme={null}
```
```html theme={null}
```
## Visibility Banner
Displays below the composer to let users set per-comment visibility. The `selectedVisibility` data variable exposes the current [`CommentVisibilityOptionType`](/api-reference/sdk/models/data-models#commentvisibilityoptiontype).
```jsx theme={null}
```
```html theme={null}
```
### Icon (Visibility Banner)
```jsx theme={null}
```
```html theme={null}
```
### Text (Visibility Banner)
```jsx theme={null}
```
```html theme={null}
```
### Dropdown (Visibility Banner)
```jsx theme={null}
```
```html theme={null}
```
#### Trigger (Visibility Banner Dropdown)
```jsx theme={null}
```
```html theme={null}
```
##### **Label (Visibility Banner Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **AvatarList (Visibility Banner Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
**Item (Visibility Banner Dropdown Trigger AvatarList)**
```jsx theme={null}
```
```html theme={null}
```
**RemainingCount (Visibility Banner Dropdown Trigger AvatarList)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Visibility Banner Dropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
#### Content (Visibility Banner Dropdown)
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Visibility Banner Dropdown Content)**
Accepts `type`: `'public' | 'org-users' | 'personal' | 'selected-people'`.
```jsx theme={null}
```
```html theme={null}
```
**Icon (Visibility Banner Dropdown Content Item)**
```jsx theme={null}
```
```html theme={null}
```
**Label (Visibility Banner Dropdown Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **UserPicker (Visibility Banner Dropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
**Search (Visibility Banner Dropdown Content UserPicker)**
```jsx theme={null}
```
```html theme={null}
```
**Icon (Visibility Banner Dropdown Content UserPicker Search)**
```jsx theme={null}
```
```html theme={null}
```
**Input (Visibility Banner Dropdown Content UserPicker Search)**
```jsx theme={null}
```
```html theme={null}
```
**Header (Visibility Banner Dropdown Content UserPicker)**
```jsx theme={null}
```
```html theme={null}
```
**Count (Visibility Banner Dropdown Content UserPicker Header)**
```jsx theme={null}
```
```html theme={null}
```
**UnselectAll (Visibility Banner Dropdown Content UserPicker Header)**
```jsx theme={null}
```
```html theme={null}
```
**Item (Visibility Banner Dropdown Content UserPicker)**
```jsx theme={null}
```
```html theme={null}
```
**Avatar (Visibility Banner Dropdown Content UserPicker Item)**
```jsx theme={null}
```
```html theme={null}
```
**Info (Visibility Banner Dropdown Content UserPicker Item)**
```jsx theme={null}
```
```html theme={null}
```
**Check (Visibility Banner Dropdown Content UserPicker Item)**
```jsx theme={null}
```
```html theme={null}
```
## All Comment
```jsx theme={null}
```
```html theme={null}
```
## Approve
```jsx theme={null}
```
```html theme={null}
```
## Sign In
```jsx theme={null}
```
```html theme={null}
```
## Upgrade
```jsx theme={null}
```
```html theme={null}
```
## Suggestion Action
```jsx theme={null}
```
```html theme={null}
```
### Accept (Suggestion Action)
```jsx theme={null}
```
```html theme={null}
```
### Reject (Suggestion Action)
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
##### **Example**
```jsx theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDialogShadowDOM();
commentElement.disableDialogShadowDOM();
```
##### **Example**
```
```
##### **API methods**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDialogShadowDOM();
commentElement.disableDialogShadowDOM();
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
Below are the examples to enable Dark Mode for comments dialog:
##### **Example**
```js theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
##### **Example**
```js theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
## Pre-defined Variants
The Comment Dialog has 2 pre-defined variants:
* `dialog`: this will customize the Comment Dialog only within Pin, Area, and Text Comments
* `sidebar`: this will customize the Comment Dialog only within Sidebar comments
To use them, set the `variant` name in the wireframe template equal to one of the pre-defined variants. You do not need to add it to the actual Velt component.
```jsx React / Next.js theme={null}
{/* This pre-defined variant will change the appearance of the Comment Dialog within Pin, Area, and Text comments only */}
...
{/* This pre-defined variant will change the appearance of the Comment Dialog within the Sidebar only */}
...
{/* If you dont use any variants, then customization will be applied to the Comment Dialog globally */}
...
```
```jsx Other Frameworks theme={null}
```
# Comment Dialog Primitives
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-primitives/overview
92+ primitive components for building custom Comment Dialog interfaces with maximum flexibility.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
The Comment Dialog Primitives API provides 92+ granular components that can be used independently to build completely custom comment interfaces. Each primitive can be used standalone or composed together for maximum customization flexibility.
## Usage Patterns
### Pattern 1: Context Wrapper (Recommended)
Components are wrapped in a context wrapper that provides shared context to children.
```jsx theme={null}
```
```html theme={null}
```
### Pattern 2: ID-Based (Standalone)
Each component receives `annotationId` directly and works independently.
```jsx theme={null}
```
```html theme={null}
```
## Common Inputs
All components inherit these base inputs. See [`CommentDialogCommonProps`](/api-reference/sdk/models/data-models#commentdialogcommonprops) for the type definition.
| React Prop | HTML Attribute | Type | Default | Description |
| -------------------------- | ----------------------------- | --------- | ------- | --------------------------- |
| `annotationId` | `annotation-id` | `string` | - | The annotation ID |
| `defaultCondition` | `default-condition` | `boolean` | `true` | When false, always shows |
| `inlineCommentSectionMode` | `inline-comment-section-mode` | `boolean` | `false` | Inline comment section mode |
| `commentPinSelected` | `comment-pin-selected` | `boolean` | `false` | Comment pin selected state |
| `fullExpanded` | `full-expanded` | `boolean` | `false` | Full expansion state |
***
## Components
### VeltCommentDialogContextWrapper
Context wrapper that provides shared annotation context to child primitives.
Props: [`CommentDialogContextWrapperProps`](/api-reference/sdk/models/data-models#commentdialogcontextwrapperprops)
```jsx theme={null}
{children}
```
**Props:**
| Prop | Type | Required | Description |
| -------------------- | --------- | ---------- | --------------------------------------- |
| `annotationId` | `string` | Yes (root) | Annotation ID for children |
| `commentId` | `string` | No | Comment ID for children |
| `attachmentId` | `string` | No | Attachment ID for children |
| `commentPinSelected` | `boolean` | No | Selection state for children |
| `[key: string]` | `any` | No | Any custom attribute passed via context |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ---------------------- | -------- | ---------- | -------------------------------- |
| `annotation-id` | `string` | Yes (root) | Annotation ID for children |
| `comment-id` | `string` | No | Comment ID for children |
| `attachment-id` | `string` | No | Attachment ID for children |
| `comment-pin-selected` | `string` | No | Selection state ("true"/"false") |
***
### VeltCommentDialogHeader
Header component for the comment dialog.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogBody
Body container for the comment dialog thread content.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogThreadCard
Complete thread card with all comment metadata and content. See [`ThreadCardProps`](/api-reference/sdk/models/data-models#threadcardprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------------------ |
| `commentObj` | `object` | No | Direct comment object (Priority 1) |
| `commentId` | `number` | No | Comment ID for lookup (Priority 2) |
| `commentIndex` | `number` | No | Index in comments array (Priority 3) |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | --------------- | -------- | ------------------------------------ |
| `comment-obj` | `string (JSON)` | No | Direct comment object (Priority 1) |
| `comment-id` | `string` | No | Comment ID for lookup (Priority 2) |
| `comment-index` | `string` | No | Index in comments array (Priority 3) |
***
### VeltCommentDialogThreadCardAvatar
User avatar for the comment author. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardName
Display name of the comment author. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardTime
Timestamp of when the comment was created. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardMessage
The comment message content. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardReactions
Reactions display for the comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardReactionTool
Tool for adding reactions to a comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardRecordings
Recordings attached to the comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardReply
Reply indicator for the thread card.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogThreadCardUnread
Unread indicator for the comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardEdited
Edited indicator for the comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardDraft
Draft indicator for the comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardDeviceType
Device type indicator for the comment. See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogThreadCardOptions
Options menu for the comment (edit, delete, etc.). See [`CommentIndexProps`](/api-reference/sdk/models/data-models#commentindexprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------- |
| `commentIndex` | `number` | No | Index of comment in array |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------------- | -------- | -------- | ------------------------- |
| `comment-index` | `string` | No | Index of comment in array |
***
### VeltCommentDialogComposer
Complete composer with input, attachments, and action buttons. See [`ComposerProps`](/api-reference/sdk/models/data-models#composerprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------------------------- | --------- | -------- | --------------------------------------------- |
| `placeholder` | `string` | No | Primary placeholder (Priority 1) |
| `commentPlaceholder` | `string` | No | Placeholder for new comment |
| `replyPlaceholder` | `string` | No | Placeholder for reply |
| `editPlaceholder` | `string` | No | Placeholder for edit mode |
| `editMode` | `boolean` | No | Enable edit mode |
| `commentObj` | `object` | No | Comment object for edit mode |
| `commentIndex` | `number` | No | Index of comment being edited |
| `targetComposerElementId` | `string` | No | Unique identifier for programmatic submission |
| `context` | `object` | No | Custom context data to attach to comment |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ---------------------------- | --------------- | -------- | --------------------------------------------- |
| `placeholder` | `string` | No | Primary placeholder (Priority 1) |
| `commentplaceholder` | `string` | No | Placeholder for new comment |
| `replyplaceholder` | `string` | No | Placeholder for reply |
| `editplaceholder` | `string` | No | Placeholder for edit mode |
| `edit-mode` | `string` | No | Enable edit mode ("true"/"false") |
| `comment-obj` | `string (JSON)` | No | Comment object for edit mode |
| `comment-index` | `string` | No | Index of comment being edited |
| `target-composer-element-id` | `string` | No | Unique identifier for programmatic submission |
| `context` | `string (JSON)` | No | Custom context data to attach to comment |
***
### VeltCommentDialogComposerInput
Text input field for composing comments. See [`ComposerInputProps`](/api-reference/sdk/models/data-models#composerinputprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------------- | -------- | -------- | ---------------------- |
| `placeholder` | `string` | No | Input placeholder text |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ------------- | -------- | -------- | ---------------------- |
| `placeholder` | `string` | No | Input placeholder text |
***
### VeltCommentDialogComposerActionButton
Submit/send button for the composer.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogComposerAttachmentButton
Button to add file attachments to comments.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogComposerRecorderButton
Button to add audio/video recordings.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogComposerRecorderPlayer
Player for audio/video recordings in the composer.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogComposerFiles
Files display in the composer.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdown
Complete status dropdown with trigger and content.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownTrigger
Button that opens the status dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownTriggerIcon
Icon for the status dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownTriggerName
Name display for the status dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownTriggerArrow
Arrow indicator for the status dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownContent
Dropdown content container for status options.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownContentItem
Individual status item in the dropdown. See [`StatusDropdownItemProps`](/api-reference/sdk/models/data-models#statusdropdownitemprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------------ |
| `statusObj` | `object` | No | Direct status object (Priority 1) |
| `statusId` | `string` | No | Status ID for lookup (Priority 2) |
| `statusIndex` | `number` | No | Index in statuses array (Priority 3) |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| -------------- | --------------- | -------- | ------------------------------------ |
| `status-obj` | `string (JSON)` | No | Direct status object (Priority 1) |
| `status-id` | `string` | No | Status ID for lookup (Priority 2) |
| `status-index` | `string` | No | Index in statuses array (Priority 3) |
***
### VeltCommentDialogStatusDropdownContentItemIcon
Icon for a status dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatusDropdownContentItemName
Name display for a status dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdown
Complete priority dropdown with trigger and content.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownTrigger
Button that opens the priority dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownTriggerIcon
Icon for the priority dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownTriggerName
Name display for the priority dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownTriggerArrow
Arrow indicator for the priority dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownContent
Dropdown content container for priority options.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownContentItem
Individual priority item in the dropdown. See [`PriorityDropdownItemProps`](/api-reference/sdk/models/data-models#prioritydropdownitemprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| --------------- | -------- | -------- | -------------------------------------- |
| `priorityObj` | `object` | No | Direct priority object (Priority 1) |
| `priorityId` | `string` | No | Priority ID for lookup (Priority 2) |
| `priorityIndex` | `number` | No | Index in priorities array (Priority 3) |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ---------------- | --------------- | -------- | -------------------------------------- |
| `priority-obj` | `string (JSON)` | No | Direct priority object (Priority 1) |
| `priority-id` | `string` | No | Priority ID for lookup (Priority 2) |
| `priority-index` | `string` | No | Index in priorities array (Priority 3) |
***
### VeltCommentDialogPriorityDropdownContentItemIcon
Icon for a priority dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownContentItemName
Name display for a priority dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriorityDropdownContentItemTick
Tick/checkmark for a priority dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdown
Options menu for actions like assignment, editing, and notifications. See [`OptionsDropdownProps`](/api-reference/sdk/models/data-models#optionsdropdownprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------------------------------ | --------- | -------- | -------------------------------- |
| `commentIndex` | `number` | No | Index of comment for options |
| `enableAssignment` | `boolean` | No | Enable assignment option |
| `allowAssignment` | `boolean` | No | V4 alias for enableAssignment |
| `enableEdit` | `boolean` | No | Enable edit option |
| `allowEdit` | `boolean` | No | V4 alias for enableEdit |
| `enableNotifications` | `boolean` | No | Enable notifications option |
| `allowNotifications` | `boolean` | No | V4 alias for enableNotifications |
| `allowToggleNotification` | `boolean` | No | V4 alias for enableNotifications |
| `enablePrivateMode` | `boolean` | No | Enable private mode option |
| `allowPrivateMode` | `boolean` | No | V4 alias for enablePrivateMode |
| `allowChangeCommentAccessMode` | `boolean` | No | V4 alias for enablePrivateMode |
| `enableMarkAsRead` | `boolean` | No | Enable mark as read option |
| `allowMarkAsRead` | `boolean` | No | V4 alias for enableMarkAsRead |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ---------------------------------- | -------- | -------- | --------------------------------- |
| `comment-index` | `string` | No | Index of comment for options |
| `enable-assignment` | `string` | No | Enable assignment option |
| `allow-assignment` | `string` | No | V4 alias for enable-assignment |
| `enable-edit` | `string` | No | Enable edit option |
| `allow-edit` | `string` | No | V4 alias for enable-edit |
| `enable-notifications` | `string` | No | Enable notifications option |
| `allow-notifications` | `string` | No | V4 alias for enable-notifications |
| `allow-toggle-notification` | `string` | No | V4 alias for enable-notifications |
| `enable-private-mode` | `string` | No | Enable private mode option |
| `allow-private-mode` | `string` | No | V4 alias for enable-private-mode |
| `allow-change-comment-access-mode` | `string` | No | V4 alias for enable-private-mode |
| `enable-mark-as-read` | `string` | No | Enable mark as read option |
| `allow-mark-as-read` | `string` | No | V4 alias for enable-mark-as-read |
***
### VeltCommentDialogOptionsDropdownTrigger
Trigger button for the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContent
Content container for the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentAssign
Assign option in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentEdit
Edit option in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentDelete
Delete option in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentDeleteComment
Delete comment option in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentDeleteThread
Delete thread option in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentMakePrivate
Make private option container in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentMakePrivateEnable
Enable private mode option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentMakePrivateDisable
Disable private mode option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentNotification
Notification option container in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentNotificationSubscribe
Subscribe to notifications option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentNotificationUnsubscribe
Unsubscribe from notifications option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentMarkAsRead
Mark as read option container in the options dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentMarkAsReadMarkRead
Mark as read action option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptionsDropdownContentMarkAsReadMarkUnread
Mark as unread action option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdown
Custom annotation dropdown component.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownTrigger
Trigger button for the custom annotation dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownTriggerArrow
Arrow indicator for the custom annotation dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownTriggerList
List container for the custom annotation dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownTriggerListItem
Individual item in the custom annotation dropdown trigger list. See [`CustomAnnotationItemProps`](/api-reference/sdk/models/data-models#customannotationitemprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------- | -------- | -------- | ---------------- |
| `item` | `object` | No | Item data object |
| `index` | `number` | No | Item index |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------- | --------------- | -------- | ---------------- |
| `item` | `string (JSON)` | No | Item data object |
| `index` | `string` | No | Item index |
***
### VeltCommentDialogCustomAnnotationDropdownTriggerPlaceholder
Placeholder for the custom annotation dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownTriggerRemainingCount
Remaining count indicator for the custom annotation dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownContent
Content container for the custom annotation dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownContentItem
Individual item in the custom annotation dropdown content. See [`CustomAnnotationItemProps`](/api-reference/sdk/models/data-models#customannotationitemprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------- | -------- | -------- | ---------------- |
| `item` | `object` | No | Item data object |
| `index` | `number` | No | Item index |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------- | --------------- | -------- | ---------------- |
| `item` | `string (JSON)` | No | Item data object |
| `index` | `string` | No | Item index |
***
### VeltCommentDialogCustomAnnotationDropdownContentItemIcon
Icon for a custom annotation dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdownContentItemLabel
Label for a custom annotation dropdown item.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogReplyAvatars
Container for reply avatars.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogReplyAvatarsList
List container for reply avatars.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogReplyAvatarsListItem
Individual avatar item in the reply avatars list. See [`ReplyAvatarsListItemProps`](/api-reference/sdk/models/data-models#replyavatarslistitemprops).
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------- | -------- | -------- | --------------------- |
| `user` | `User` | Yes | User data object |
| `index` | `number` | Yes | Index of user in list |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------- | --------------- | -------- | --------------------- |
| `user` | `string (JSON)` | Yes | User data object |
| `index` | `string` | Yes | Index of user in list |
***
### VeltCommentDialogReplyCount
Reply count display.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAssigneeBanner
Assignee banner container.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAssigneeBannerResolved
Resolved state of the assignee banner.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAssigneeBannerUnresolved
Unresolved state of the assignee banner.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAssigneeBannerUnresolveButton
Button to unresolve the assignee banner.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocomplete
To customize the **wireframe appearance** of autocomplete options, chips, and tooltips, see the [Autocomplete Option](/ui-customization/features/async/comments/comment-dialog-components#autocomplete-option-composer) and [Autocomplete Chip Tooltip](/ui-customization/features/async/comments/comment-dialog-components#autocomplete-chip-tooltip-body-threads-thread-card) sections in the Comment Dialog Components page.
The autocomplete panel component accepts the following props for controlling selection mode, ordering, and data source.
```jsx theme={null}
```
**Props:**
| Prop | Type | Default | Description |
| ----------------------- | -------------------------------------------------------------------- | ------- | ------------------------------------------------------ |
| `multiSelect` | `boolean` | `false` | Allows selecting multiple contacts |
| `selectedFirstOrdering` | `boolean` | `false` | Shows selected items first in the list |
| `readOnly` | `boolean` | `false` | Disables user interaction |
| `inline` | `boolean` | `false` | Renders the autocomplete inline rather than as a panel |
| `contacts` | [`UserContact[]`](/api-reference/sdk/models/data-models#usercontact) | — | Overrides the default contact list |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Default | Description |
| ------------------------- | -------- | --------- | ------------------------------------------------------ |
| `multi-select` | `string` | `"false"` | Allows selecting multiple contacts |
| `selected-first-ordering` | `string` | `"false"` | Shows selected items first in the list |
| `read-only` | `string` | `"false"` | Disables user interaction |
| `inline` | `string` | `"false"` | Renders the autocomplete inline rather than as a panel |
***
### VeltAutocompleteEmptyWireframe
Customize the empty state shown when the autocomplete panel has no results. This is a wireframe component — for more wireframe customization patterns, see the [Comment Dialog Components](/ui-customization/features/async/comments/comment-dialog-components) page.
```jsx theme={null}
import { VeltWireframe, VeltAutocompleteEmptyWireframe } from '@veltdev/react';
No results found
```
```html theme={null}
No results found
```
***
### VeltAutocompleteChip
Standalone autocomplete chip component for rendering selected contact chips.
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------------ | ----------------------------------------------- | -------- | -------------------------------------------------------- |
| `type` | `'contact' \| 'custom' \| 'group' \| 'loading'` | No | Chip type |
| `email` | `string` | No | Contact email |
| `userObject` | `any` | No | User data object (JSON object or serialized JSON string) |
| `userId` | `string` | No | User ID |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ------------- | ----------------------------------------------- | -------- | ----------------------------------- |
| `type` | `'contact' \| 'custom' \| 'group' \| 'loading'` | No | Chip type |
| `email` | `string` | No | Contact email |
| `user-object` | `string` (JSON) | No | User data object as serialized JSON |
| `user-id` | `string` | No | User ID |
***
### VeltAutocompleteOption
Standalone autocomplete option component for rendering contact/custom options.
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------------ | -------- | -------- | -------------------------------------------------------- |
| `userObject` | `any` | No | User data object (JSON object or serialized JSON string) |
| `userId` | `string` | No | User ID |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| ------------- | --------------- | -------- | ----------------------------------- |
| `user-object` | `string` (JSON) | No | User data object as serialized JSON |
| `user-id` | `string` | No | User ID |
***
### VeltAutocompleteOptionDescription
Standalone autocomplete option description component.
```jsx theme={null}
```
**Props:**
| Prop | Type | Required | Description |
| ------- | -------- | -------- | ------------------------------------------ |
| `field` | `string` | No | Field name to display from the user object |
```html theme={null}
```
**Attributes:**
| Attribute | Type | Required | Description |
| --------- | -------- | -------- | ------------------------------------------ |
| `field` | `string` | No | Field name to display from the user object |
***
### VeltAutocompleteOptionIcon
Icon for an autocomplete option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteOptionName
Name display for an autocomplete option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteOptionErrorIcon
Error icon for an autocomplete option.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteGroupOption
Group option in the autocomplete panel.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteTool
Tool component for the autocomplete panel.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteEmpty
Empty state for the autocomplete panel.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteChipTooltip
Tooltip for an autocomplete chip.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteChipTooltipIcon
Icon within the autocomplete chip tooltip.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteChipTooltipName
Name display within the autocomplete chip tooltip.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltAutocompleteChipTooltipDescription
Description within the autocomplete chip tooltip.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogResolveButton
Button to resolve a comment thread.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogUnresolveButton
Button to unresolve a comment thread.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCopyLink
Button to copy the comment link to clipboard.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCloseButton
Button to close the comment dialog.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogDeleteButton
Button to delete a comment.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPrivateBanner
Banner indicating a comment's private status.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPrivateButton
Button to toggle private mode on a comment.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogGhostBanner
Banner displayed for ghost or anonymous comments.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBanner
Banner displaying the current visibility setting for a comment.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerIcon
Icon within the visibility banner.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerText
Text label within the visibility banner.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdown
Dropdown for selecting visibility options within the banner.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownTrigger
Trigger element for the visibility dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownTriggerLabel
Label text within the visibility dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownTriggerAvatarList
Avatar list within the visibility dropdown trigger showing selected users.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownTriggerAvatarListItem
Individual avatar within the trigger avatar list.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownTriggerAvatarListRemainingCount
Count of remaining avatars not displayed in the trigger list.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownTriggerIcon
Icon within the visibility dropdown trigger.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContent
Content container for the visibility dropdown options.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentItemIcon
Icon for a visibility option item in the dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentItemLabel
Label for a visibility option item in the dropdown.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPicker
User picker for selecting specific users when visibility is set to `selected-people`.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerSearch
Search container within the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerSearchIcon
Search icon within the user picker search.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerSearchInput
Search input field within the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerHeader
Header section of the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerHeaderCount
Count of selected users displayed in the user picker header.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerHeaderUnselectAll
Button to unselect all users in the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerItem
Individual user item within the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerItemAvatar
Avatar for a user item in the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerItemInfo
Info text for a user item in the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogVisibilityBannerDropdownContentUserPickerItemCheck
Check indicator for a selected user in the user picker.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogSignIn
Sign-in prompt displayed to unauthenticated users.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogNavigationButton
Button for navigating between comments.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAttachmentButton
Button for adding file attachments to a comment.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogDeviceTypeIcons
Icons indicating the device type used when the comment was created.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCommentIndex
Display for the comment's index position.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCommentNumber
Display for the comment's number.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCommentCategory
Display for the comment's category.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogMetadata
Display for comment metadata.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogThreads
Container for the comment threads.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAllComment
View for displaying all comments.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogApprove
Approve button for comment approval workflows.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogUpgrade
Upgrade prompt component.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogMoreReply
Indicator showing additional replies are available.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogHideReply
Button to hide replies in a thread.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogToggleReply
Toggle control for showing or hiding replies.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogToggleReplyShow
Show-replies variant of the reply toggle.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogToggleReplyHide
Hide-replies variant of the reply toggle.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogToggleReplyCount
Reply count displayed within the reply toggle.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogSuggestionAction
Container for suggestion accept/reject actions.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogSuggestionActionAccept
Button to accept a suggestion.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogSuggestionActionReject
Button to reject a suggestion.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCommentSuggestionStatus
Status indicator for a comment suggestion.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAssignDropdown
Dropdown for assigning a comment to a user.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogAssignMenu
Menu for assignment options.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogOptions
Options menu for the comment thread.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogPriority
Priority selector for the comment.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogStatus
Status selector for the comment.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
### VeltCommentDialogCustomAnnotationDropdown
Dropdown for custom annotation categories.
```jsx theme={null}
```
**Props:** Common inputs only (see [Common Inputs](#common-inputs) section).
```html theme={null}
```
**Attributes:** Common attributes only (see [Common Inputs](#common-inputs) section).
***
## Notes
* **Attribute Naming:** HTML uses kebab-case, React uses camelCase
* **Boolean Values:** HTML uses "true"/"false" strings, React uses actual booleans
* **Object Values:** HTML uses JSON strings, React uses actual objects
* **Required vs Optional:** `annotationId` is required when using standalone mode, not required inside context wrapper
## API Reference
* [Comment Dialog Primitives API](/api-reference/sdk/api/api-methods#comment-dialog-primitives)
* [Data Models](/api-reference/sdk/models/data-models#comment-dialog-primitives)
# Comment Dialog — Structure
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-structure
Canonical structure for the Velt Comment Dialog Wireframe (React & HTML mirrors). Order follows your provided JSX/HTML. Parent/child is defined only by the extension path. Direct children are marked with (Leaf) when they have no descendants.
> **Conventions**
>
> * **React names** use `PascalCase` under the `VeltCommentDialogWireframe` namespace.
> * **Parent/child** is determined **only** by the extension path (`A.B` → `B` is a child of `A`). Visual placement (e.g., inside `Header`) does not imply hierarchy.
> * **HTML names** mirror React 1:1 in `kebab-case`.
> * **(Leaf)** = component has no children in this wireframe.
***
## React
`VeltCommentDialogWireframe`
### `VeltCommentDialogWireframe.GhostBanner`
*(no direct children)*
***
### `VeltCommentDialogWireframe.PrivateBanner`
*(no direct children)*
***
### `VeltCommentDialogWireframe.AssigneeBanner`
* `VeltCommentDialogWireframe.AssigneeBanner.ResolveButton` **(Leaf)**
* `VeltCommentDialogWireframe.AssigneeBanner.UnresolveButton` **(Leaf)**
* `VeltCommentDialogWireframe.AssigneeBanner.UserAvatar` **(Leaf)**
* `VeltCommentDialogWireframe.AssigneeBanner.UserName` **(Leaf)**
***
### `VeltCommentDialogWireframe.Header`
***
### `VeltCommentDialogWireframe.CommentNumber` **(Leaf)**
***
### `VeltCommentDialogWireframe.Status`
***
### `VeltCommentDialogWireframe.Priority`
***
### `VeltCommentDialogWireframe.Options`
* `VeltCommentDialogWireframe.Options.Trigger` **(Leaf)**
* `VeltCommentDialogWireframe.Options.Content`
#### `VeltCommentDialogWireframe.Options.Content`
* `VeltCommentDialogWireframe.Options.Content.MarkAsRead` **(Leaf)**
* `VeltCommentDialogWireframe.Options.Content.MakePrivate`
* `VeltCommentDialogWireframe.Options.Content.Assign` **(Leaf)**
* `VeltCommentDialogWireframe.Options.Content.Edit` **(Leaf)**
* `VeltCommentDialogWireframe.Options.Content.Delete`
* `VeltCommentDialogWireframe.Options.Content.Notification`
***
### `VeltCommentDialogWireframe.CopyLink`
***
### `VeltCommentDialogWireframe.ResolveButton`
***
### `VeltCommentDialogWireframe.CustomAnnotationDropdown`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Content`
#### `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger.List`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger.RemainingCount` **(Leaf)**
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger.Placeholder` **(Leaf)**
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger.Arrow` **(Leaf)**
#### `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger.List`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Trigger.List.Item` **(Leaf)**
#### `VeltCommentDialogWireframe.CustomAnnotationDropdown.Content`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Content.Item`
#### `VeltCommentDialogWireframe.CustomAnnotationDropdown.Content.Item`
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Content.Item.Label` **(Leaf)**
* `VeltCommentDialogWireframe.CustomAnnotationDropdown.Content.Item.Icon` **(Leaf)**
***
### `VeltCommentDialogWireframe.CommentIndex`
***
### `VeltCommentDialogWireframe.CommentCategory`
***
### `VeltCommentDialogWireframe.CommentSuggestionStatus`
***
### `VeltCommentDialogWireframe.NavigationButton`
***
### `VeltCommentDialogWireframe.PrivateBadge`
***
### `VeltCommentDialogWireframe.Body`
### `VeltCommentDialogWireframe.Threads`
### `VeltCommentDialogWireframe.ThreadCard`
#### `VeltCommentDialogWireframe.ThreadCard`
* `VeltCommentDialogWireframe.ThreadCard.Avatar` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Name` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Unread` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown`
* `VeltCommentDialogWireframe.ThreadCard.Time` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.DeviceType` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Options`
* `VeltCommentDialogWireframe.ThreadCard.Message` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.EditComposer`
* `VeltCommentDialogWireframe.ThreadCard.ReactionTool` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Reactions` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments`
* `VeltCommentDialogWireframe.ThreadCard.Recordings` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Reply` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.AssignButton` **(Leaf)**
##### `VeltCommentDialogWireframe.ThreadCard.Options`
* `VeltCommentDialogWireframe.ThreadCard.Options.Trigger` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Options.Content`
##### `VeltCommentDialogWireframe.ThreadCard.Options.Content`
* `VeltCommentDialogWireframe.ThreadCard.Options.Content.MarkAsRead` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Options.Content.MakePrivate`
* `VeltCommentDialogWireframe.ThreadCard.Options.Content.Assign` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Options.Content.Edit` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Options.Content.Delete`
* `VeltCommentDialogWireframe.ThreadCard.Options.Content.Notification`
##### `VeltCommentDialogWireframe.ThreadCard.EditComposer`
*(no direct children)*
##### `VeltCommentDialogWireframe.ThreadCard.SeenDropdown`
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Trigger` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content`
##### `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content`
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Title` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items`
##### `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items`
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items.Item`
##### `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items.Item`
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items.Item.Time` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items.Item.Name` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.SeenDropdown.Content.Items.Item.Avatar` **(Leaf)**
##### `VeltCommentDialogWireframe.ThreadCard.Attachments`
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Image`
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Other`
##### `VeltCommentDialogWireframe.ThreadCard.Attachments.Image`
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Image.Preview` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Image.Delete` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Image.Download` **(Leaf)**
##### `VeltCommentDialogWireframe.ThreadCard.Attachments.Other`
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Other.Delete` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Other.Download` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Other.Icon` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Other.Name` **(Leaf)**
* `VeltCommentDialogWireframe.ThreadCard.Attachments.Other.Size` **(Leaf)**
### `VeltCommentDialogWireframe.ReplyAvatars`
#### `VeltCommentDialogWireframe.ReplyAvatars`
* `VeltCommentDialogWireframe.ReplyAvatars.List`
* `VeltCommentDialogWireframe.ReplyAvatars.RemainingCount` **(Leaf)**
##### `VeltCommentDialogWireframe.ReplyAvatars.List`
* `VeltCommentDialogWireframe.ReplyAvatars.List.Item` **(Leaf)**
### `VeltCommentDialogWireframe.ToggleReply`
#### `VeltCommentDialogWireframe.ToggleReply`
* `VeltCommentDialogWireframe.ToggleReply.Icon` **(Leaf)**
* `VeltCommentDialogWireframe.ToggleReply.Count` **(Leaf)**
* `VeltCommentDialogWireframe.ToggleReply.Text` **(Leaf)**
***
### `VeltCommentDialogWireframe.HideReply`
#### `VeltCommentDialogWireframe.HideReply`
*(no direct children)*
***
### `VeltCommentDialogWireframe.Composer`
* `VeltCommentDialogWireframe.Composer.Avatar` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments`
* `VeltCommentDialogWireframe.Composer.Recordings` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Input` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.ActionButton` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.AssignUser` **(Leaf)**
#### `VeltCommentDialogWireframe.Composer.Attachments`
* `VeltCommentDialogWireframe.Composer.Attachments.Selected`
* `VeltCommentDialogWireframe.Composer.Attachments.Invalid`
##### `VeltCommentDialogWireframe.Composer.Attachments.Selected`
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Image`
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other`
##### `VeltCommentDialogWireframe.Composer.Attachments.Selected.Image`
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Image.Preview` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Image.Delete` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Image.Loading` **(Leaf)**
##### `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other`
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other.Delete` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other.Loading` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other.Icon` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other.Name` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Selected.Other.Size` **(Leaf)**
##### `VeltCommentDialogWireframe.Composer.Attachments.Invalid`
* `VeltCommentDialogWireframe.Composer.Attachments.Invalid.Item`
##### `VeltCommentDialogWireframe.Composer.Attachments.Invalid.Item`
* `VeltCommentDialogWireframe.Composer.Attachments.Invalid.Item.Preview` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Invalid.Item.Message` **(Leaf)**
* `VeltCommentDialogWireframe.Composer.Attachments.Invalid.Item.Delete` **(Leaf)**
***
### `VeltCommentDialogWireframe.VisibilityBanner`
* `VeltCommentDialogWireframe.VisibilityBanner.Icon` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Text` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown`
#### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content`
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger.Label` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger.AvatarList`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger.Icon` **(Leaf)**
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger.AvatarList`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger.AvatarList.Item` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Trigger.AvatarList.RemainingCount` **(Leaf)**
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.Item` (accepts `type`: `'public'` | `'org-users'` | `'personal'` | `'selected-people'`)
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker`
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.Item`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.Item.Icon` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.Item.Label` **(Leaf)**
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Search`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Header`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Item`
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Search`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Search.Icon` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Search.Input` **(Leaf)**
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Header`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Header.Count` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Header.UnselectAll` **(Leaf)**
##### `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Item`
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Item.Avatar` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Item.Info` **(Leaf)**
* `VeltCommentDialogWireframe.VisibilityBanner.Dropdown.Content.UserPicker.Item.Check` **(Leaf)**
***
### `VeltCommentDialogWireframe.AllComment`
***
### `VeltCommentDialogWireframe.Approve`
***
### `VeltCommentDialogWireframe.SignIn`
***
### `VeltCommentDialogWireframe.Upgrade`
***
### `VeltCommentDialogWireframe.SuggestionAction`
* `VeltCommentDialogWireframe.SuggestionAction.Accept` **(Leaf)**
* `VeltCommentDialogWireframe.SuggestionAction.Reject` **(Leaf)**
***
## HTML (1:1 mirror)
`velt-comment-dialog-wireframe`
### `velt-comment-dialog-ghost-banner-wireframe`
*(no direct children)*
***
### `velt-comment-dialog-private-banner-wireframe`
*(no direct children)*
***
### `velt-comment-dialog-assignee-banner-wireframe`
* `velt-comment-dialog-assignee-banner-resolve-button-wireframe` **(Leaf)**
* `velt-comment-dialog-assignee-banner-unresolve-button-wireframe` **(Leaf)**
* `velt-comment-dialog-assignee-banner-user-avatar-wireframe` **(Leaf)**
* `velt-comment-dialog-assignee-banner-user-name-wireframe` **(Leaf)**
***
### `velt-comment-dialog-header-wireframe`
*(no direct children)*
***
### `velt-comment-dialog-comment-number-wireframe` **(Leaf)**
***
### `velt-comment-dialog-status-wireframe`
***
### `velt-comment-dialog-priority-wireframe`
***
### `velt-comment-dialog-options-wireframe`
* `velt-comment-dialog-options-trigger-wireframe` **(Leaf)**
* `velt-comment-dialog-options-content-wireframe`
#### `velt-comment-dialog-options-content-wireframe`
* `velt-comment-dialog-options-content-mark-as-read-wireframe` **(Leaf)**
* `velt-comment-dialog-options-content-make-private-wireframe`
* `velt-comment-dialog-options-content-assign-wireframe` **(Leaf)**
* `velt-comment-dialog-options-content-edit-wireframe` **(Leaf)**
* `velt-comment-dialog-options-content-delete-wireframe`
* `velt-comment-dialog-options-content-notification-wireframe`
***
### `velt-comment-dialog-copy-link-wireframe`
***
### `velt-comment-dialog-resolve-button-wireframe`
***
### `velt-comment-dialog-custom-annotation-dropdown-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-trigger-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-content-wireframe`
#### `velt-comment-dialog-custom-annotation-dropdown-trigger-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-trigger-list-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-trigger-remaining-count-wireframe` **(Leaf)**
* `velt-comment-dialog-custom-annotation-dropdown-trigger-placeholder-wireframe` **(Leaf)**
* `velt-comment-dialog-custom-annotation-dropdown-trigger-arrow-wireframe` **(Leaf)**
#### `velt-comment-dialog-custom-annotation-dropdown-trigger-list-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-trigger-list-item-wireframe` **(Leaf)**
#### `velt-comment-dialog-custom-annotation-dropdown-content-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-content-item-wireframe`
#### `velt-comment-dialog-custom-annotation-dropdown-content-item-wireframe`
* `velt-comment-dialog-custom-annotation-dropdown-content-item-label-wireframe` **(Leaf)**
* `velt-comment-dialog-custom-annotation-dropdown-content-item-icon-wireframe` **(Leaf)**
***
### `velt-comment-dialog-comment-index-wireframe`
***
### `velt-comment-dialog-comment-category-wireframe`
***
### `velt-comment-dialog-comment-suggestion-status-wireframe`
***
### `velt-comment-dialog-navigation-button-wireframe`
***
### `velt-comment-dialog-private-badge-wireframe`
***
### `velt-comment-dialog-body-wireframe`
*(no direct children here — see its children below)*
***
### `velt-comment-dialog-threads-wireframe`
* `velt-comment-dialog-thread-card-wireframe`
#### `velt-comment-dialog-thread-card-wireframe`
* `velt-comment-dialog-thread-card-avatar-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-name-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-unread-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-seen-dropdown-wireframe`
* `velt-comment-dialog-thread-card-time-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-device-type-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-options-wireframe`
* `velt-comment-dialog-thread-card-message-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-edit-composer-wireframe`
* `velt-comment-dialog-thread-card-reaction-tool-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-reactions-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-wireframe`
* `velt-comment-dialog-thread-card-recordings-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-reply-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-assign-button-wireframe` **(Leaf)**
##### `velt-comment-dialog-thread-card-options-wireframe`
* `velt-comment-dialog-thread-card-options-trigger-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-options-content-wireframe`
##### `velt-comment-dialog-thread-card-options-content-wireframe`
* `velt-comment-dialog-thread-card-options-content-mark-as-read-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-options-content-make-private-wireframe`
* `velt-comment-dialog-thread-card-options-content-assign-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-options-content-edit-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-options-content-delete-wireframe`
* `velt-comment-dialog-thread-card-options-content-notification-wireframe`
##### `velt-comment-dialog-thread-card-edit-composer-wireframe`
*(no direct children)*
##### `velt-comment-dialog-thread-card-seen-dropdown-wireframe`
* `velt-comment-dialog-thread-card-seen-dropdown-trigger-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-seen-dropdown-content-wireframe`
##### `velt-comment-dialog-thread-card-seen-dropdown-content-wireframe`
* `velt-comment-dialog-thread-card-seen-dropdown-content-title-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-seen-dropdown-content-items-wireframe`
##### `velt-comment-dialog-thread-card-seen-dropdown-content-items-wireframe`
* `velt-comment-dialog-thread-card-seen-dropdown-content-items-item-wireframe`
##### `velt-comment-dialog-thread-card-seen-dropdown-content-items-item-wireframe`
* `velt-comment-dialog-thread-card-seen-dropdown-content-items-item-time-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-seen-dropdown-content-items-item-name-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-seen-dropdown-content-items-item-avatar-wireframe` **(Leaf)**
##### `velt-comment-dialog-thread-card-attachments-wireframe`
* `velt-comment-dialog-thread-card-attachments-image-wireframe`
* `velt-comment-dialog-thread-card-attachments-other-wireframe`
##### `velt-comment-dialog-thread-card-attachments-image-wireframe`
* `velt-comment-dialog-thread-card-attachments-image-preview-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-image-delete-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-image-download-wireframe` **(Leaf)**
##### `velt-comment-dialog-thread-card-attachments-other-wireframe`
* `velt-comment-dialog-thread-card-attachments-other-delete-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-other-download-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-other-icon-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-other-name-wireframe` **(Leaf)**
* `velt-comment-dialog-thread-card-attachments-other-size-wireframe` **(Leaf)**
***
### `velt-comment-dialog-reply-avatars-wireframe`
* `velt-comment-dialog-reply-avatars-list-wireframe`
* `velt-comment-dialog-reply-avatars-remaining-count-wireframe` **(Leaf)**
#### `velt-comment-dialog-reply-avatars-list-wireframe`
* `velt-comment-dialog-reply-avatars-list-item-wireframe` **(Leaf)**
***
### `velt-comment-dialog-toggle-reply-wireframe`
* `velt-comment-dialog-toggle-reply-icon-wireframe` **(Leaf)**
* `velt-comment-dialog-toggle-reply-count-wireframe` **(Leaf)**
* `velt-comment-dialog-toggle-reply-text-wireframe` **(Leaf)**
***
### `velt-comment-dialog-hide-reply-wireframe`
*(no direct children)*
***
### `velt-comment-dialog-composer-wireframe`
* `velt-comment-dialog-composer-avatar-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-wireframe`
* `velt-comment-dialog-composer-recordings-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-input-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-action-button-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-assign-user-wireframe` **(Leaf)**
#### `velt-comment-dialog-composer-attachments-wireframe`
* `velt-comment-dialog-composer-attachments-selected-wireframe`
* `velt-comment-dialog-composer-attachments-invalid-wireframe`
##### `velt-comment-dialog-composer-attachments-selected-wireframe`
* `velt-comment-dialog-composer-attachments-selected-image-wireframe`
* `velt-comment-dialog-composer-attachments-selected-other-wireframe`
##### `velt-comment-dialog-composer-attachments-selected-image-wireframe`
* `velt-comment-dialog-composer-attachments-selected-image-preview-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-selected-image-delete-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-selected-image-loading-wireframe` **(Leaf)**
##### `velt-comment-dialog-composer-attachments-selected-other-wireframe`
* `velt-comment-dialog-composer-attachments-selected-other-delete-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-selected-other-loading-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-selected-other-icon-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-selected-other-name-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-selected-other-size-wireframe` **(Leaf)**
##### `velt-comment-dialog-composer-attachments-invalid-wireframe`
* `velt-comment-dialog-composer-attachments-invalid-item-wireframe`
##### `velt-comment-dialog-composer-attachments-invalid-item-wireframe`
* `velt-comment-dialog-composer-attachments-invalid-item-preview-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-invalid-item-message-wireframe` **(Leaf)**
* `velt-comment-dialog-composer-attachments-invalid-item-delete-wireframe` **(Leaf)**
***
### `velt-comment-dialog-visibility-banner-wireframe`
* `velt-comment-dialog-visibility-banner-icon-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-text-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-wireframe`
#### `velt-comment-dialog-visibility-banner-dropdown-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-trigger-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-wireframe`
##### `velt-comment-dialog-visibility-banner-dropdown-trigger-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-trigger-label-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-trigger-avatar-list-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-trigger-icon-wireframe` **(Leaf)**
##### `velt-comment-dialog-visibility-banner-dropdown-trigger-avatar-list-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-trigger-avatar-list-item-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-trigger-avatar-list-remaining-count-wireframe` **(Leaf)**
##### `velt-comment-dialog-visibility-banner-dropdown-content-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-item-wireframe` (accepts `type`: `public` | `org-users` | `personal` | `selected-people`)
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-wireframe`
##### `velt-comment-dialog-visibility-banner-dropdown-content-item-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-item-icon-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-content-item-label-wireframe` **(Leaf)**
##### `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-search-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-header-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-item-wireframe`
##### `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-search-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-search-icon-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-search-input-wireframe` **(Leaf)**
##### `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-header-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-header-count-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-header-unselect-all-wireframe` **(Leaf)**
##### `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-item-wireframe`
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-item-avatar-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-item-info-wireframe` **(Leaf)**
* `velt-comment-dialog-visibility-banner-dropdown-content-user-picker-item-check-wireframe` **(Leaf)**
***
### `velt-comment-dialog-all-comment-wireframe`
***
### `velt-comment-dialog-approve-wireframe`
***
### `velt-comment-dialog-sign-in-wireframe`
***
### `velt-comment-dialog-upgrade-wireframe`
***
### `velt-comment-dialog-suggestion-action-wireframe`
* `velt-comment-dialog-suggestion-action-accept-wireframe` **(Leaf)**
* `velt-comment-dialog-suggestion-action-reject-wireframe` **(Leaf)**
# Variants
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/pre-defined-variants
## Pre-defined Variants
The Comment Dialog has 2 pre-defined variants:
* `dialog`: this will customize the Comment Dialog only within Pin, Area, and Text Comments
* `sidebar`: this will customize the Comment Dialog only within Sidebar comments
To use them, set the `variant` name in the wireframe template equal to one of the pre-defined variants. You do not need to add it to the actual Velt component.
```jsx React / Next.js theme={null}
{/* This pre-defined variant will change the appearance of the Comment Dialog within Pin, Area, and Text comments only */}
...
{/* This pre-defined variant will change the appearance of the Comment Dialog within the Sidebar only */}
...
{/* If you dont use any variants, then customization will be applied to the Comment Dialog globally */}
...
```
```jsx HTML theme={null}
```
# Styling
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/styling
## Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
### Example
```jsx theme={null}
```
### API methods
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDialogShadowDOM();
commentElement.disableDialogShadowDOM();
```
### Example
```
```
### API methods
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDialogShadowDOM();
commentElement.disableDialogShadowDOM();
```
## Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
Below are the examples to enable Dark Mode for comments dialog:
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
# Comment Pin
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-pin
The Pin that appears on the DOM when you place a Comment.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltCommentPinWireframe
```jsx theme={null}
```
```html theme={null}
```
## GhostCommentIndicator
```jsx theme={null}
```
```html theme={null}
```
## Index
```jsx theme={null}
```
```html theme={null}
```
## Number
The annotation number displays on comment pins, providing a persistent identifier for each comment that remains constant across sessions. This number makes it easy to reference specific comments in team discussions or documentation.
```jsx theme={null}
```
```html theme={null}
```
## PrivateCommentIndicator
```jsx theme={null}
```
```html theme={null}
```
## Triangle
```jsx theme={null}
```
```html theme={null}
```
## UnreadCommentIndicator
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enablePinShadowDOM();
commentElement.disablePinShadowDOM();
```
**Using Props:**
```jsx theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enablePinShadowDOM();
commentElement.disablePinShadowDOM();
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
**Using Props:**
```js theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
### Variants
* Define variants for the `Velt Comment Pin` component. This is useful for customizing how the pin looks on different elements like charts, tables, etc.
* Learn more about how to define and use variants [here](/ui-customization/layout#variants).
```jsx theme={null}
```
```html theme={null}
```
# Comment Player Timeline
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-player-timeline
## Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```jsx theme={null}
```
# Comments Sidebar Button
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-button
The button used to open the Comments Sidebar panel.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltSidebarButtonWireframe
```jsx theme={null}
```
```html theme={null}
```
### Icon
```jsx theme={null}
```
```html theme={null}
```
### CommentsCount
```jsx theme={null}
```
```html theme={null}
```
### UnreadIcon
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
### Example
```jsx theme={null}
{/* If you use floating mode, you can turn off the shadow dom for the sidebar using this: */}
```
### Example
```html theme={null}
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
To enable Dark Mode for sidebar button:
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
# Comment Sidebar
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-components
Comment Sidebar Component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## Skeleton
```jsx theme={null}
```
```html theme={null}
```
## Panel
```jsx theme={null}
```
```html theme={null}
```
### Header (Panel)
```jsx theme={null}
```
```html theme={null}
```
#### FullscreenButton (Panel Header)
Button to toggle full-screen mode for the Comments Sidebar. This component only appears when full-screen mode is enabled via the `fullScreen` prop or API method. Clicking it allows users to expand the sidebar to fill the entire viewport or return to normal mode.
```jsx theme={null}
```
```html theme={null}
```
#### CloseButton (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
#### Search (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
#### ResetFilterButton (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
#### Status (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Panel Header Status)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Header Status Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow (Panel Header Status Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Indicator (Panel Header Status Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Panel Header Status)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Header Status Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon (Panel Header Status Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Header Status Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Header Status Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Header Status Content Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Header Status Content Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Header Status Content Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
#### LocationFilterDropdown (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Panel Header LocationFilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Label (Panel Header LocationFilterDropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Panel Header LocationFilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
#### MinimalFilterDropdown (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Panel Header MinimalFilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Panel Header MinimalFilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **SortDate (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Sort date
```
```html theme={null}
Sort by date
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content SortDate)**
```jsx theme={null}
Sort date
```
```html theme={null}
Sort by date
```
##### **SortUnread (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Sort unread
```
```html theme={null}
Sort by unread
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content SortUnread)**
```jsx theme={null}
Sort unread
```
```html theme={null}
Sort by unread
```
##### **FilterAll (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Filter All
```
```html theme={null}
Unread or Read
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content FilterAll)**
```jsx theme={null}
Filter All
```
```html theme={null}
Unread or Read
```
##### **FilterUnread (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Filter unread
```
```html theme={null}
Unread only
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content FilterUnread)**
```jsx theme={null}
Filter unread
```
```html theme={null}
Unread only
```
##### **FilterRead (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Filter read
```
```html theme={null}
Read only
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content FilterRead)**
```jsx theme={null}
Filter read
```
```html theme={null}
Read only
```
##### **FilterResolved (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Filter resolved
```
```html theme={null}
Resolved
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content FilterResolved)**
```jsx theme={null}
Filter resolved
```
```html theme={null}
Resolved
```
##### **FilterOpen (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Filter open
```
```html theme={null}
Open only
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content FilterOpen)**
```jsx theme={null}
Filter open
```
```html theme={null}
Open only
```
##### **FilterReset (Panel Header MinimalFilterDropdown Content)**
```jsx theme={null}
Reset filters
```
```html theme={null}
Reset filters
```
##### **SelectedIcon (Panel Header MinimalFilterDropdown Content FilterReset)**
```jsx theme={null}
Reset filters
```
```html theme={null}
Reset filters
```
#### MinimalActionsDropdown (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Panel Header MinimalActionsDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Panel Header MinimalActionsDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **MarkAllRead (Panel Header MinimalActionsDropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **MarkAllResolved (Panel Header MinimalActionsDropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
#### DocumentFilterDropdown (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Panel Header DocumentFilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Panel Header DocumentFilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Document (Panel Header DocumentFilterDropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
#### FilterButton (Panel Header)
```jsx theme={null}
```
```html theme={null}
```
### Filter (Panel)
```jsx theme={null}
```
```html theme={null}
```
#### Title (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
#### CloseButton (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
#### Location (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Location)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Location)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Location Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Location Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Location Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Location Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Location Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Location)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Location Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Location Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Location Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Location Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Location Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **ViewAll (Panel Filter Location)**
```jsx theme={null}
```
```html theme={null}
```
#### Document (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Document)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Document)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Document Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Document Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Document Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Document Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Document Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Document)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Document Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Document Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Document Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Document Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Document Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **ViewAll (Panel Filter Document)**
```jsx theme={null}
```
```html theme={null}
```
#### Involved (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Involved)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Involved)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Involved Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Involved Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Involved Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Involved Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Involved Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Involved)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Involved Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Involved Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Involved Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Involved Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Involved Item)**
```jsx theme={null}
```
```html theme={null}
```
#### People (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter People)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter People)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter People Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter People Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter People Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter People Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter People Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter People Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter People Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter People)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter People Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter People Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter People Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter People Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter People Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Tagged (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Tagged)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Tagged)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Tagged Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Tagged Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Tagged Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter Tagged Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Tagged Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Tagged Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Tagged Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Tagged)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Tagged Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Tagged Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Tagged Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Tagged Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Tagged Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Assigned (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Assigned)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Assigned)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Assigned Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Assigned Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Assigned Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter Assigned Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Assigned Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Assigned Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Assigned Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Assigned)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Assigned Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Assigned Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Assigned Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Assigned Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Assigned Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Custom (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Custom)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Custom)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Custom Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Custom Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Custom Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter Custom Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Custom Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Custom Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Custom Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Custom)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Custom Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Custom Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Custom Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Custom Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Custom Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Category (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Category)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Category)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Category Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Category Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Category Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter Category Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Category Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Category Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Category Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Category)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Category Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Category Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Category Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Category Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Category Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Priority (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Priority)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Priority)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Priority Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Priority Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Priority Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter Priority Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Priority Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Priority Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Priority Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Priority)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Priority Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Priority Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Priority Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Priority Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Priority Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Status (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Status)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Status)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Status Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Status Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Status Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Close (Panel Filter Status Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Status Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Status Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Status Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Status)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Status Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Status Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Status Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Status Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Status Item)**
```jsx theme={null}
```
```html theme={null}
```
#### CommentType (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter CommentType)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter CommentType)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter CommentType Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter CommentType Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter CommentType Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter CommentType Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter CommentType Item)**
```jsx theme={null}
```
```html theme={null}
```
#### Versions (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Versions)**
```jsx theme={null}
```
```html theme={null}
```
##### **Search (Panel Filter Versions)**
```jsx theme={null}
```
```html theme={null}
```
##### **Tags (Panel Filter Versions Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Versions Search Tags)**
```jsx theme={null}
```
```html theme={null}
```
###### **Name (Panel Filter Versions Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
###### **Close (Panel Filter Versions Search Tags Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **HiddenCount (Panel Filter Versions Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Input (Panel Filter Versions Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **DropdownIcon (Panel Filter Versions Search)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter Versions)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter Versions Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter Versions Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter Versions Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter Versions Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter Versions Item)**
```jsx theme={null}
```
```html theme={null}
```
#### GroupBy (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter GroupBy)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel Filter GroupBy)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel Filter GroupBy Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Filter GroupBy Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Filter GroupBy Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel Filter GroupBy Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel Filter GroupBy Item)**
```jsx theme={null}
```
```html theme={null}
```
#### ResetButton (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
#### DoneButton (Panel Filter)
```jsx theme={null}
```
```html theme={null}
```
If you want to change the filters globally across all filters in the sidebar, customize the shared filter option item (shown below) once and reuse it. Use `VeltCommentsSidebarWireframe.Filter.Item` (and its `Checkbox`, `Name`, and `Count` subcomponents) to apply changes across all filters.
### Item (Panel)
```jsx theme={null}
```
```html theme={null}
```
#### Checkbox (Panel Item)
```jsx theme={null}
```
```html theme={null}
```
##### **Checked (Panel Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
##### **Unchecked (Panel Item Checkbox)**
```jsx theme={null}
```
```html theme={null}
```
#### Name (Panel Item)
```jsx theme={null}
```
```html theme={null}
```
#### Count (Panel Item)
```jsx theme={null}
```
```html theme={null}
```
### List (Panel)
```jsx theme={null}
```
```html theme={null}
```
#### Item (Panel List)
```jsx theme={null}
```
```html theme={null}
```
##### **Group (Panel List Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel List Item Group)**
```jsx theme={null}
```
```html theme={null}
```
##### **Count (Panel List Item Group)**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow (Panel List Item Group)**
```jsx theme={null}
```
```html theme={null}
```
##### **Annotation (Panel List Item)**
```jsx theme={null}
```
```html theme={null}
```
### EmptyPlaceholder (Panel)
```jsx theme={null}
```
```html theme={null}
```
### PageModeComposer (Panel)
```jsx theme={null}
```
```html theme={null}
```
## FocusedThread
```jsx theme={null}
```
```html theme={null}
```
### BackButton (FocusedThread)
```jsx theme={null}
```
```html theme={null}
```
### DialogContainer (FocusedThread)
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
##### **Example**
```jsx theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableSidebarShadowDOM();
commentElement.disableSidebarShadowDOM();
```
##### **Example**
```jsx theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableSidebarShadowDOM();
commentElement.disableSidebarShadowDOM();
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
To enable Dark Mode for comments sidebar:
##### **Example**
```js theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
##### **Example**
```js theme={null}
```
##### **API methods**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
## Variants
Here are the variants that you can use in Comments Sidebar:
* `variant`: This is the variant for the entire Comments Sidebar.
* `dialogVariant`: This is the variant for the Comment Dialog that appears in the Comments Sidebar.
* `pageModeComposerVariant`: This is the variant for the Comment Composer that appears in the Comments Sidebar in page mode.
* `focusedThreadDialogVariant`: This is the variant for the Comment Dialog that appears when a focused thread mode is enabled.
```jsx React / Next.js theme={null}
```
```jsx Other Frameworks theme={null}
```
# Comments Sidebar — Structure
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-structure
Canonical structure for the Velt Comments Sidebar Wireframe (React only). Order follows your provided JSX. Parent/child is defined ONLY by dot-path extensions. Direct children with no descendants are marked (Leaf).
> **Conventions**
>
> * **React names** use `PascalCase` under the `VeltCommentsSidebarWireframe` namespace.
> * **Parent/child** is determined **only** by the extension path (`A.B` → `B` is a child of `A`). Visual placement (e.g., inside `Header`) does not imply hierarchy.
> * **HTML names** mirror React 1:1 in `kebab-case`.
> * **(Leaf)** = component has no children in this wireframe.
> * **The highest parents will start with '##' as their header tag, their children will use one more '#' at each level.**
***
## React
`VeltCommentsSidebarWireframe`
### `VeltCommentsSidebarWireframe.Skeleton` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.Panel` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.Header` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.FullscreenButton` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.CloseButton` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.Search` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.ResetFilterButton` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.Status`
#### `VeltCommentsSidebarWireframe.Status.Trigger`
* `VeltCommentsSidebarWireframe.Status.Trigger.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Status.Trigger.Arrow` **(Leaf)**
* `VeltCommentsSidebarWireframe.Status.Trigger.Indicator` **(Leaf)**
#### `VeltCommentsSidebarWireframe.Status.Content`
* `VeltCommentsSidebarWireframe.Status.Content.Item`
##### `VeltCommentsSidebarWireframe.Status.Content.Item`
* `VeltCommentsSidebarWireframe.Status.Content.Item.Icon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Status.Content.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Status.Content.Item.Count` **(Leaf)**
* `VeltCommentsSidebarWireframe.Status.Content.Item.Checkbox`
###### `VeltCommentsSidebarWireframe.Status.Content.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Status.Content.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Status.Content.Item.Checkbox.Unchecked` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.LocationFilterDropdown`
#### `VeltCommentsSidebarWireframe.LocationFilterDropdown.Trigger`
* `VeltCommentsSidebarWireframe.LocationFilterDropdown.Trigger.Label` **(Leaf)**
#### `VeltCommentsSidebarWireframe.LocationFilterDropdown.Content` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.MinimalFilterDropdown`
#### `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Trigger` **(Leaf)**
#### `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content`
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.SortDate` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.SortUnread` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.FilterAll` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.FilterUnread` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.FilterRead` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.FilterResolved` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalFilterDropdown.Content.SelectedIcon` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.MinimalActionsDropdown`
#### `VeltCommentsSidebarWireframe.MinimalActionsDropdown.Trigger` **(Leaf)**
#### `VeltCommentsSidebarWireframe.MinimalActionsDropdown.Content`
* `VeltCommentsSidebarWireframe.MinimalActionsDropdown.Content.MarkAllRead` **(Leaf)**
* `VeltCommentsSidebarWireframe.MinimalActionsDropdown.Content.MarkAllResolved` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.DocumentFilterDropdown`
#### `VeltCommentsSidebarWireframe.DocumentFilterDropdown.Trigger` **(Leaf)**
#### `VeltCommentsSidebarWireframe.DocumentFilterDropdown.Content` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.FilterButton` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.Filter`
#### `VeltCommentsSidebarWireframe.Filter.Title` **(Leaf)**
#### `VeltCommentsSidebarWireframe.Filter.CloseButton` **(Leaf)**
#### `VeltCommentsSidebarWireframe.Filter.Location`
* `VeltCommentsSidebarWireframe.Filter.Location.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Search`
* `VeltCommentsSidebarWireframe.Filter.Location.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Location.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Location.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Item`
* `VeltCommentsSidebarWireframe.Filter.Location.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Location.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.Item.Count` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Location.ViewAll` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Document`
* `VeltCommentsSidebarWireframe.Filter.Document.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Search`
* `VeltCommentsSidebarWireframe.Filter.Document.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Document.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Document.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Item`
* `VeltCommentsSidebarWireframe.Filter.Document.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Document.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.Item.Count` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Document.ViewAll` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Involved`
* `VeltCommentsSidebarWireframe.Filter.Involved.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Search`
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Item`
* `VeltCommentsSidebarWireframe.Filter.Involved.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Involved.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Involved.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.People`
* `VeltCommentsSidebarWireframe.Filter.People.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Search`
* `VeltCommentsSidebarWireframe.Filter.People.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.People.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.People.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Item`
* `VeltCommentsSidebarWireframe.Filter.People.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.People.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.People.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Tagged`
* `VeltCommentsSidebarWireframe.Filter.Tagged.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search`
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Item`
* `VeltCommentsSidebarWireframe.Filter.Tagged.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Tagged.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Tagged.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Assigned`
* `VeltCommentsSidebarWireframe.Filter.Assigned.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search`
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Item`
* `VeltCommentsSidebarWireframe.Filter.Assigned.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Assigned.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Assigned.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Custom`
* `VeltCommentsSidebarWireframe.Filter.Custom.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Search`
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Item`
* `VeltCommentsSidebarWireframe.Filter.Custom.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Custom.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Custom.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Category`
* `VeltCommentsSidebarWireframe.Filter.Category.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Search`
* `VeltCommentsSidebarWireframe.Filter.Category.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Category.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Category.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Item`
* `VeltCommentsSidebarWireframe.Filter.Category.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Category.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Category.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Priority`
* `VeltCommentsSidebarWireframe.Filter.Priority.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Search`
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Item`
* `VeltCommentsSidebarWireframe.Filter.Priority.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Priority.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Priority.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Status`
* `VeltCommentsSidebarWireframe.Filter.Status.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Search`
* `VeltCommentsSidebarWireframe.Filter.Status.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Status.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Status.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Item`
* `VeltCommentsSidebarWireframe.Filter.Status.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Status.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Status.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.CommentType`
* `VeltCommentsSidebarWireframe.Filter.CommentType.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.CommentType.Item`
* `VeltCommentsSidebarWireframe.Filter.CommentType.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.CommentType.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.CommentType.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.CommentType.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.CommentType.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.Versions`
* `VeltCommentsSidebarWireframe.Filter.Versions.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Search`
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.Tags`
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.Tags.Item`
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.Tags.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.Tags.Item.Close` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.HiddenCount` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.Input` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Search.DropdownIcon` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Item`
* `VeltCommentsSidebarWireframe.Filter.Versions.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Versions.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Versions.Item.Count` **(Leaf)**
***
#### `VeltCommentsSidebarWireframe.Filter.GroupBy`
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Item`
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.GroupBy.Item.Count` **(Leaf)**
#### `VeltCommentsSidebarWireframe.Filter.ResetButton` **(Leaf)**
#### `VeltCommentsSidebarWireframe.Filter.DoneButton` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.Filter.Item`
* `VeltCommentsSidebarWireframe.Filter.Item.Checkbox`
* `VeltCommentsSidebarWireframe.Filter.Item.Checkbox.Checked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Item.Checkbox.Unchecked` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Item.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.Filter.Item.Count` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.List`
#### `VeltCommentsSidebarWireframe.List.Item`
* `VeltCommentsSidebarWireframe.List.Item.Group`
* `VeltCommentsSidebarWireframe.List.Item.Group.Name` **(Leaf)**
* `VeltCommentsSidebarWireframe.List.Item.Group.Count` **(Leaf)**
* `VeltCommentsSidebarWireframe.List.Item.Group.Arrow` **(Leaf)**
* `VeltCommentsSidebarWireframe.List.Item.Annotation` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.EmptyPlaceholder` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.PageModeComposer` **(Leaf)**
***
### `VeltCommentsSidebarWireframe.FocusedThread`
#### `VeltCommentsSidebarWireframe.FocusedThread.BackButton` **(Leaf)**
#### `VeltCommentsSidebarWireframe.FocusedThread.DialogContainer` **(Leaf)**
***
## Related (Global status dropdown alternative)
`VeltCommentsSidebarStatusDropdownWireframe`
### `VeltCommentsSidebarStatusDropdownWireframe.Trigger` **(Leaf)**
### `VeltCommentsSidebarStatusDropdownWireframe.Content` **(Leaf)**
## HTML (1:1 mirror)
`velt-comments-sidebar-wireframe`
### `velt-comment-sidebar-skeleton-wireframe` **(Leaf)**
***
### `velt-comment-sidebar-panel-wireframe`
#### `velt-comments-sidebar-header-wireframe`
* `velt-comments-sidebar-fullscreen-button-wireframe` **(Leaf)**
* `velt-comments-sidebar-close-button-wireframe` **(Leaf)**
* `velt-comments-sidebar-reset-filter-button-wireframe` **(Leaf)**
* `velt-comments-sidebar-search-wireframe` **(Leaf)**
##### `velt-comments-sidebar-status-wireframe`
* `velt-comments-sidebar-status-dropdown-trigger-wireframe`
* `velt-comments-sidebar-status-dropdown-trigger-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-trigger-arrow-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-trigger-indicator-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-content-wireframe`
* `velt-comments-sidebar-status-dropdown-content-item-wireframe`
* `velt-comments-sidebar-status-dropdown-content-item-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-content-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-content-item-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-content-item-checkbox-wireframe`
* `velt-comments-sidebar-status-dropdown-content-item-checkbox-unchecked-wireframe` **(Leaf)**
* `velt-comments-sidebar-status-dropdown-content-item-checkbox-checked-wireframe` **(Leaf)**
##### `velt-comments-sidebar-location-filter-dropdown-wireframe`
* `velt-comments-sidebar-location-filter-dropdown-trigger-wireframe`
* `velt-comments-sidebar-location-filter-dropdown-trigger-label-wireframe` **(Leaf)**
* `velt-comments-sidebar-location-filter-dropdown-content-wireframe` **(Leaf)**
##### `velt-comments-sidebar-minimal-filter-dropdown-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-trigger-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-filter-dropdown-content-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-sort-date-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-selected-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-filter-dropdown-content-sort-unread-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-selected-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-filter-dropdown-content-filter-all-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-selected-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-filter-dropdown-content-filter-unread-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-selected-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-filter-dropdown-content-filter-read-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-selected-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-filter-dropdown-content-filter-resolved-wireframe`
* `velt-comments-sidebar-minimal-filter-dropdown-content-selected-icon-wireframe` **(Leaf)**
##### `velt-comments-sidebar-minimal-actions-dropdown-wireframe`
* `velt-comments-sidebar-minimal-actions-dropdown-trigger-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-actions-dropdown-content-wireframe`
* `velt-comments-sidebar-minimal-actions-dropdown-content-mark-all-read-wireframe` **(Leaf)**
* `velt-comments-sidebar-minimal-actions-dropdown-content-mark-all-resolved-wireframe` **(Leaf)**
##### `velt-comments-sidebar-document-filter-dropdown-wireframe`
* `velt-comments-sidebar-document-filter-dropdown-trigger-wireframe` **(Leaf)**
* `velt-comments-sidebar-document-filter-dropdown-content-wireframe`
* `velt-comments-sidebar-filter-document-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-button-wireframe` **(Leaf)**
***
#### `velt-comments-sidebar-filter-wireframe`
* `velt-comments-sidebar-filter-title-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-close-button-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-location-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-view-all-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-document-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-view-all-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-involved-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-people-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-custom-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-tagged-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-assigned-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-category-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-comment-type-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-priority-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-status-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-versions-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-wireframe`
* `velt-comments-sidebar-filter-search-tags-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-wireframe`
* `velt-comments-sidebar-filter-search-tags-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-tags-item-close-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-hidden-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-input-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-search-dropdown-icon-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
##### `velt-comments-sidebar-filter-group-by-wireframe`
* `velt-comments-sidebar-filter-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-reset-button-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-done-button-wireframe` **(Leaf)**
***
#### `velt-comments-sidebar-list-wireframe`
* `velt-comments-sidebar-list-item-wireframe`
* `velt-comments-sidebar-list-item-group-wireframe`
* `velt-comments-sidebar-list-item-group-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-list-item-group-count-wireframe` **(Leaf)**
* `velt-comments-sidebar-list-item-group-arrow-wireframe` **(Leaf)**
* `velt-comments-sidebar-list-item-annotation-wireframe` **(Leaf)**
#### `velt-comments-sidebar-empty-placeholder-wireframe` **(Leaf)**
#### `velt-comments-sidebar-page-mode-composer-wireframe` **(Leaf)**
***
### `velt-comments-sidebar-focused-thread-wireframe`
* `velt-comments-sidebar-focused-thread-back-button-wireframe` **(Leaf)**
* `velt-comments-sidebar-focused-thread-dialog-container-wireframe` **(Leaf)**
***
## Common Filter Item (Reusable)
`velt-comments-sidebar-filter-item-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-wireframe`
* `velt-comments-sidebar-filter-item-checkbox-checked-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-checkbox-unchecked-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-name-wireframe` **(Leaf)**
* `velt-comments-sidebar-filter-item-count-wireframe` **(Leaf)**
# Comment Tool
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-tool
The button to add new comments.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltCommentToolWireframe
```jsx theme={null}
{/* Your custom element */}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```jsx theme={null}
```
### Dark Mode
Default: `false`
```js theme={null}
```
```js theme={null}
```
# Velt Video Player
Source: https://docs.velt.dev/ui-customization/features/async/comments/comment-video-player
## Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```jsx theme={null}
```
# Styling
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/styling
## Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
### Example
```jsx theme={null}
```
### API methods
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableSidebarShadowDOM();
commentElement.disableSidebarShadowDOM();
```
### Example
```jsx theme={null}
```
### API methods
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableSidebarShadowDOM();
commentElement.disableSidebarShadowDOM();
```
## Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
To enable Dark Mode for comments sidebar:
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
# Empty placeholder
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/empty-placeholder
The subcomponent of the Comments Sidebar that represents the placeholder when there are no Comments
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/overview
The subcomponent of the Comments Sidebar that represents the filter that is used to filter what Comments appear in the Sidebar
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Title */}
{/* Close Button */}
{/* Location */}
{/* Document */}
{/* Author */}
{/* Tagged */}
{/* Assigned */}
{/* Category */}
{/* Priority */}
{/* CommentType */}
{/* Versions */}
{/* Custom */}
{/* Status */}
{/* GroupBy */}
{/* Reset Button */}
{/* Done Button */}
```
```HTML HTML theme={null}
```
# Filter Type - Assigned
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/assigned
The subcomponent of the Comments Sidebar Filter that represents the Assigned filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Filter Type - Category
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/category
The subcomponent of the Comments Sidebar Filter that represents the Category
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Close button
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/close-button
The subcomponent of the Comments Sidebar Filter that represents the Close button
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Filter Type - Comment Type
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/comment-type
The subcomponent of the Comments Sidebar Filter that represents the Comment Type filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Filter Type - Custom
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/custom
The subcomponent of the Comments Sidebar Filter that represents the Custom filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You need to add one for each custom filter you want to add.
```jsx React / Next.js theme={null}
{/* If you are using the "dropdown" filterOptionLayout */}
{/* If you are using the default filterOptionLayout */}
```
```HTML HTML theme={null}
```
# Filter Type - Document
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/document
The subcomponent of the Comments Sidebar Filter that represents the Document filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Done button
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/done-button
The subcomponent of the Comments Sidebar Filter that represents the Done Button
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Filter item
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item
Used to customize all the filter option items of the Comments Sidebar Filter at once
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Groupby
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/groupby
The subcomponent of the Comments Sidebar Filter that represents the Groupby filter option
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx eact / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Filter Type - Involved
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/involved
The subcomponent of the Comments Sidebar Filter that represents the Involved filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Filter Type - Location
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/location
The subcomponent of the Comments Sidebar Filter that represents the Location filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Filter Type - Author
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/people
The subcomponent of the Comments Sidebar Filter that represents the Author filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Filter Type - Priority
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/priority
The subcomponent of the Comments Sidebar Filter that represents the Priority filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Reset button
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/reset-button
The subcomponent of the Comments Sidebar Filter that represents the Reset Filter Button
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Filter Type - Status
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/status
The subcomponent of the Comments Sidebar Filter that represents the Status filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Filter Type - Tagged
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/tagged
The subcomponent of the Comments Sidebar Filter that represents the Tagged filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Title
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/title
The subcomponent of the Comments Sidebar Filter that represents the Title
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Filter Type - Versions
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/versions
The subcomponent of the Comments Sidebar Filter that represents the Version filter
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can either use this or use the global filter item wireframe to customize all the filter types of filter options at once. [Learn More](/ui-customization/features/async/comments/comments-sidebar/subcomponents/filter/subcomponents/filter-item)
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Searchable Dropdown: It's not visible by default. You need to add the wireframe to the sidebar. */}
{/* Checklist: It's visible by default. */}
```
```HTML HTML theme={null}
```
# Focused thread
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/focused-thread
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
```jsx React / Next.js theme={null}
{/* Back Button to back to default view with all threads */}
{/* Container that contains the comment dialog */}
```
```HTML HTML theme={null}
```
# Header
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header
The subcomponent of the Comments Sidebar that represents the Header of the Sidebar
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/overview
The subcomponent of the Comments Sidebar that represents the Header of the Sidebar
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Not shown for embedded comments sidebar */}
{/* Not included in the default component */}
{/* Not included in the default component */}
{/* Not included in the default component */}
```
```HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/document-filter-dropdown/overview
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
This component is not included in the default Comments Sidebar component. You need to explicitly add it to the sidebar wireframe.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/location-filter-dropdown/overview
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
This component is not included in the default Comments Sidebar component. You need to explicitly add it to the sidebar wireframe.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/minimal-action-dropdown/overview
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
* This enables actions like `Mark all read` and `Mark all resolved` in the sidebar. By default it's not enabled. You need to explicitly add the wireframe to the sidebar.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/minimal-filter-dropdown/overview
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
* This enables minimal filtering and sorting dropdown in the sidebar. By default it's not enabled. You need to explicitly add the wireframe to the sidebar. It includes options like:
* Filter by `All`
* Filter by `Unread`
* Filter by `Read`
* Filter by `Resolved`
* Filter by `Assigned to Me`
* Sort by `Unread`
* Sort by `Last Updated Timestamp`
```jsx React / Next.js theme={null}
Sort date
Sort unread
Filter all
Filter unread
Filter read
Filter resolved
Filter open
Filter assigned to me
Filter reset
```
```HTML HTML theme={null}
Sort by date
Sort by unread
Unread or Read
Unread only
Read only
Resolved
Open
Assigned to me
Reset filters
```
### FilterAssignedToMe (MinimalFilterDropdown Content)
```jsx theme={null}
Filter assigned to me
```
```html theme={null}
Assigned to me
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/status/overview
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Content
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/status/subcomponents/content
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Trigger
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/header/subcomponents/status/subcomponents/trigger
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/list/overview
The subcomponent of the Comments Sidebar that represents the List of Comments in the Sidebar
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Dialog container
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/list/subcomponents/dialog-container
You can customize the Comment Dialog that appears inside the Sidebar with this subcomponent
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
You can customize the Comment Dialog that appears in the Sidebar by adding a Comment Dialog Wireframe inside this subcomponent and modifying it to your liking.
```jsx React / Next.js theme={null}
...
```
```HTML HTML theme={null}
...
```
# Group
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/list/subcomponents/group
The subcomponent of the Comments Sidebar List that represents the Group
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Page mode composer
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/page-mode-composer
The subcomponent of the Comments Sidebar that represents the Composer that appears in Page Mode.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Panel
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/panel
The subcomponent of the Comments Sidebar that represents the List of Comments in the Sidebar
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
{/* Header */}
{/* Filter */}
{/* List */}
{/* Empty Placeholder */}
{/* Page Mode Composer */}
```
```HTML HTML theme={null}
```
# Reset filter button
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/reset-filter-button
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Overview
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/sidebar-button/overview
The button that is used to open the Comments Sidebar panel.
We recommend that you familiarize yourselves with [Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Component
Here's how the default sidebar button looks like:
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Styling
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/sidebar-button/styling
## Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
### Example
```jsx theme={null}
```
### Example
```jsx theme={null}
```
## Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
`Default: false`
To enable Dark Mode for sidebar button:
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
### Example
```js theme={null}
```
### API methods
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
# Comments count
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/sidebar-button/subcomponents/comments-count
The subcomponent on the Sidebar Button that shows the total
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Icon
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/sidebar-button/subcomponents/icon
The subcomponent on the Sidebar Button that shows the total
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Skeleton
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/subcomponents/skeleton
The subcomponent of the Comments Sidebar that represents the Skeleton loader that appears when the Sidebar is first loading.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Default Subcomponent
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Variants
Source: https://docs.velt.dev/ui-customization/features/async/comments/comments-sidebar/variants
## Variants
Here are the variants that you can use in Comments Sidebar:
* `variant`: This is the variant for the entire Comments Sidebar.
* `dialogVariant`: This is the variant for the Comment Dialog that appears in the Comments Sidebar.
* `pageModeComposerVariant`: This is the variant for the Comment Composer that appears in the Comments Sidebar in page mode.
* `focusedThreadDialogVariant`: This is the variant for the Comment Dialog that appears when a focused thread mode is enabled.
```jsx React / Next.js theme={null}
```
```HTML HTML theme={null}
```
# Confirmation Dialog
Source: https://docs.velt.dev/ui-customization/features/async/comments/confirm-dialog
The Confirmation Dialog that appears when you delete a comment annotation.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltConfirmDialogWireframe
```jsx theme={null}
```
```html theme={null}
```
## Title
```jsx theme={null}
```
```html theme={null}
```
## Message
```jsx theme={null}
```
```html theme={null}
```
## RejectButton
```jsx theme={null}
```
```html theme={null}
```
## ApproveButton
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
ShadowDOM is not used in this component. You can apply your styling directly to the component.
### Dark Mode
This component takes the dark mode property from the parent feature (eg: comments) where this used.
If the parent feature component is in dark mode, this component will also be in dark mode.
# Inline Comments Section
Source: https://docs.velt.dev/ui-customization/features/async/comments/inline-comments-section
Components that appear when using Inline Comments
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltInlineCommentsSectionWireframe
```jsx theme={null}
```
```html theme={null}
```
### Skeleton
```jsx theme={null}
```
```html theme={null}
```
### Panel
```jsx theme={null}
```
```html theme={null}
```
#### ComposerContainer
```jsx theme={null}
```
```html theme={null}
```
##### **VeltCommentDialogWireframe.Composer**
You can find the wireframe for the `Comment Dialog Composer` [here](/ui-customization/features/async/comments/comment-dialog-components#composer).
#### SortingDropdown
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger**
```jsx theme={null}
```
```html theme={null}
```
##### **Name**
```jsx theme={null}
```
```html theme={null}
```
##### **Icon**
```jsx theme={null}
```
```html theme={null}
```
##### **Content**
```jsx theme={null}
```
```html theme={null}
```
##### **Item**
```jsx theme={null}
```
```html theme={null}
```
#### FilterDropdown (Panel)
```jsx theme={null}
```
```html theme={null}
```
##### **Trigger (Panel FilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow (Panel FilterDropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Name (Panel FilterDropdown Trigger)**
```jsx theme={null}
```
```html theme={null}
```
##### **Content (Panel FilterDropdown)**
```jsx theme={null}
```
```html theme={null}
```
##### **List (Panel FilterDropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
##### **Item (Panel FilterDropdown Content List)**
```jsx theme={null}
```
```html theme={null}
```
##### **Label (Panel FilterDropdown Content List Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **Checkbox (Panel FilterDropdown Content List Item)**
```jsx theme={null}
```
```html theme={null}
```
##### **ApplyButton (Panel FilterDropdown Content)**
```jsx theme={null}
```
```html theme={null}
```
#### CommentCount
```jsx theme={null}
```
```html theme={null}
```
#### List
```jsx theme={null}
```
```html theme={null}
```
##### **VeltCommentDialogWireframe**
You can find the wireframe for the `Comment Dialog` [here](/ui-customization/features/async/comments/comment-dialog-components).
## Pre-defined Variants
Here are the pre-deinfed variants:
1. `dialog-variant`: Use this to customize the `Comment Dialog` that appears within the `Inline Comments Section` component.
2. `variant`: Use this to customize the entire `Inline Comments Section` component itself.
3. `composer-variant`: Use this to customize the main Composer that appears within the `Inline Comments Section` component.
Learn more about Variants [here](/ui-customization/layout#variants)
```jsx React / Next.js theme={null}
```
```HTML theme={null}
```
# MultiThread Comment Dialog
Source: https://docs.velt.dev/ui-customization/features/async/comments/multithread-comment-dialog
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltMultiThreadCommentDialogWireframe
```jsx theme={null}
```
```html theme={null}
```
## CommentCount
```jsx theme={null}
```
```html theme={null}
```
## MinimalFilterDropdown
```jsx theme={null}
```
```html theme={null}
```
## MinimalActionsDropdown
```jsx theme={null}
```
```html theme={null}
```
## NewThreadButton
```jsx theme={null}
```
```html theme={null}
```
## CloseButton
```jsx theme={null}
```
```html theme={null}
```
## List
```jsx theme={null}
```
```html theme={null}
```
## ResetFilterButton
```jsx theme={null}
```
```html theme={null}
```
## ComposerContainer
```jsx theme={null}
```
```html theme={null}
```
# Persistent Comment Mode Banner
Source: https://docs.velt.dev/ui-customization/features/async/comments/persistent-comment-mode-banner
The persistent comment mode banner that appears when persistent mode is enabled and user is adding a comment.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltPersistentCommentModeWireframe
```jsx theme={null}
```
```html theme={null}
```
## CloseButton
```jsx theme={null}
```
```html theme={null}
```
## Label
```jsx theme={null}
```
```html theme={null}
```
### Label.Public
```jsx theme={null}
```
```html theme={null}
```
### Label.Private
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```html theme={null}
```
# Comment Composer
Source: https://docs.velt.dev/ui-customization/features/async/comments/standalone-components/comment-composer
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
This component is a thin wrapper around the [Comment Dialog Composer](/ui-customization/features/async/comments/comment-dialog-components#composer) component.
## VeltCommentComposerWireframe
```jsx theme={null}
```
```html theme={null}
```
### VeltCommentDialogWireframe.Composer
You can find the wireframe for the `Comment Composer` [here](/ui-customization/features/async/comments/comment-dialog-components#composer).
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
```jsx theme={null}
```
```html theme={null}
```
### Dark Mode
`Default: false`
```jsx theme={null}
```
```html theme={null}
```
### Variants
* Define variants for the entire Comment Composer component. This will enable you to show different Composer UI in different parts of your app.
* Alternatively, define a variant for the Comment Dialog component and use it here. This will enable you to show different Comment Dialog UI on the DOM vs here.
* Learn more about how to define and use variants [here](/ui-customization/layout#variants).
```jsx theme={null}
```
```html theme={null}
```
# Comment Thread
Source: https://docs.velt.dev/ui-customization/features/async/comments/standalone-components/comment-thread
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
This component is a thin wrapper around the [Comment Dialog](/ui-customization/features/async/comments/comment-dialog-components) component.
## VeltCommentThreadWireframe
```jsx theme={null}
```
```html theme={null}
```
### VeltCommentDialogWireframe
You can find the wireframe for the `Comment Dialog` [here](/ui-customization/features/async/comments/comment-dialog-components).
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
```jsx theme={null}
```
```html theme={null}
```
### Dark Mode
`Default: false`
```js theme={null}
```
```html theme={null}
```
### Variants
* Define variants for the entire Comment Thread component. This will enable you to show different Thread UI in different parts of your app.
* Alternatively, define a variant for the Comment Dialog component and use it here. This will enable you to show different Comment Dialog UI on the DOM vs here.
* Learn more about how to define and use variants [here](/ui-customization/layout#variants).
```jsx theme={null}
```
```html theme={null}
```
# Text Comment Tool
Source: https://docs.velt.dev/ui-customization/features/async/comments/text-comment-tool
The Comment Tool that appears when you highlight some text.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltTextCommentToolWireframe
```jsx theme={null}
{/* Your custom element */}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```jsx theme={null}
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
Default: `false`
Below are the examples to enable Dark Mode for text comment tool.
**Using Props:**
```js theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
**Using Props:**
```js theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
# Text Comment Toolbar
Source: https://docs.velt.dev/ui-customization/features/async/comments/text-comment-toolbar
The Toolbar that appears when you highlight some text
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltTextCommentToolbarWireframe
```jsx theme={null}
```
```html theme={null}
```
## CommentAnnotation
```jsx theme={null}
```
```html theme={null}
```
## Divider
```jsx theme={null}
```
```html theme={null}
```
## Copywriter
```jsx theme={null}
```
```html theme={null}
```
## Generic
```jsx theme={null}
```
````html theme={null}
```
## Styling
### Disable ShadowDOM
- By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
- Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx
````
```jsx theme={null}
```
### Dark Mode
By default, all components are in Light Mode, but there are several properties and methods to enable Dark Mode.
Default: `false`
**Using Props:**
```js theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = client.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
**Using Props:**
```js theme={null}
```
**Using API:**
```jsx theme={null}
const commentElement = Velt.getCommentElement();
commentElement.enableDarkMode();
commentElement.disableDarkMode();
```
# Inline Reactions Section
Source: https://docs.velt.dev/ui-customization/features/async/inline-reactions
This component is used to render the reaction tool and all the reactions.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltInlineReactionsSectionWireframe
```jsx theme={null}
```
```html theme={null}
```
### Panel
```jsx theme={null}
```
```html theme={null}
```
#### ToolContainer
```jsx theme={null}
```
```html theme={null}
```
##### VeltReactionToolWireframe
```jsx theme={null}
```
```html theme={null}
```
#### List
```jsx theme={null}
```
```html theme={null}
```
##### VeltReactionPinWireframe
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```jsx theme={null}
```
### Dark Mode
Default: `false`
**Using Props:**
```js theme={null}
```
**Using API:**
```js theme={null}
const reactionElement = client.getReactionElement();
reactionElement.enableDarkMode();
reactionElement.disableDarkMode();
```
**Using Props:**
```html theme={null}
```
**Using API:**
```js theme={null}
const reactionElement = Velt.getReactionElement();
reactionElement.enableDarkMode();
reactionElement.disableDarkMode();
```
## Variants
### Pre-defined Variants
The Inline Reactions has 1 pre-defined variant:
* `inline`: This will customize the default components inside the Inline Reactions Component.
You can define your own variants and use them in different places of your app.
```jsx theme={null}
```
```html theme={null}
```
# Notifications Panel
Source: https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel
The Notification Panel contains all notifications within the current organization. It appears when you click the notification tool or embed it directly on a page.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## Title
```jsx theme={null}
```
```html theme={null}
```
## ReadAllButton
```jsx theme={null}
```
```html theme={null}
```
## Skeleton
```jsx theme={null}
```
```html theme={null}
```
## Header
```jsx theme={null}
```
```html theme={null}
```
#### TabAll
```jsx theme={null}
```
```html theme={null}
```
#### TabDocuments
```jsx theme={null}
```
```html theme={null}
```
#### TabForYou
```jsx theme={null}
```
```html theme={null}
```
#### TabPeople
```jsx theme={null}
```
```html theme={null}
```
## Content
```jsx theme={null}
```
```html theme={null}
```
### ForYou
```jsx theme={null}
```
```html theme={null}
```
### All
```jsx theme={null}
```
```html theme={null}
```
#### All List
```jsx theme={null}
```
```html theme={null}
```
#### All List Item
```jsx theme={null}
```
```html theme={null}
```
#### All List Item Label
```jsx theme={null}
```
```html theme={null}
```
#### All List Item Content
```jsx theme={null}
```
```html theme={null}
```
### Documents
```jsx theme={null}
```
```html theme={null}
```
#### Documents List
```jsx theme={null}
```
```html theme={null}
```
#### Documents List Item
```jsx theme={null}
```
```html theme={null}
```
#### Documents List Item Unread
```jsx theme={null}
```
```html theme={null}
```
#### Documents List Item Name
```jsx theme={null}
```
```html theme={null}
```
#### Documents List Item Count
```jsx theme={null}
```
```html theme={null}
```
#### Documents List Item Content
```jsx theme={null}
```
```html theme={null}
```
### People
```jsx theme={null}
```
```html theme={null}
```
#### People List
```jsx theme={null}
```
```html theme={null}
```
#### People List Item
```jsx theme={null}
```
```html theme={null}
```
#### People List Item Avatar
```jsx theme={null}
```
```html theme={null}
```
#### People List Item Name
```jsx theme={null}
```
```html theme={null}
```
#### People List Item Count
```jsx theme={null}
```
```html theme={null}
```
#### People List Item Content
```jsx theme={null}
```
```html theme={null}
```
## Settings
```jsx theme={null}
```
```html theme={null}
```
### Settings Header
```jsx theme={null}
```
```html theme={null}
```
#### Header BackButton
```jsx theme={null}
```
```html theme={null}
```
#### Header Title
```jsx theme={null}
```
```html theme={null}
```
### Settings Title
```jsx theme={null}
```
```html theme={null}
```
### Settings Description
```jsx theme={null}
```
```html theme={null}
```
### Settings List
```jsx theme={null}
```
```html theme={null}
```
#### Settings List Accordion
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Trigger
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Trigger Label
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Trigger SelectedValue
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Trigger Icon
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Content
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Content Item
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Content Item Icon
```jsx theme={null}
```
```html theme={null}
```
#### Accordion Content Item Label
```jsx theme={null}
```
```html theme={null}
```
### Settings Footer
```jsx theme={null}
```
```html theme={null}
```
#### MuteAllTitle
```jsx theme={null}
```
```html theme={null}
```
#### MuteAllDescription
```jsx theme={null}
```
```html theme={null}
```
#### MuteAllToggle
```jsx theme={null}
```
```html theme={null}
```
## Shared Components
### Content List
```jsx theme={null}
```
```html theme={null}
```
### Content List Item
```jsx theme={null}
```
```html theme={null}
```
#### Content List Item Avatar
```jsx theme={null}
```
```html theme={null}
```
#### Content List Item Unread
```jsx theme={null}
```
```html theme={null}
```
#### Content List Item Headline
```jsx theme={null}
```
```html theme={null}
```
#### Content List Item Body
```jsx theme={null}
```
```html theme={null}
```
#### Content List Item FileName
```jsx theme={null}
```
```html theme={null}
```
#### Content List Item Time
```jsx theme={null}
```
```html theme={null}
```
### LoadMore
```jsx theme={null}
```
```html theme={null}
```
### AllReadContainer
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
```jsx theme={null}
```
```jsx theme={null}
```
### Dark Mode
`Default: false`
```js theme={null}
```
```html theme={null}
```
# Notifications Tool
Source: https://docs.velt.dev/ui-customization/features/async/notifications/notifications-tool
The button that opens or closes the notification panel.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltNotificationsToolWireframe
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
**Example**
```jsx theme={null}
```
```jsx theme={null}
```
### Dark Mode
`Default: false`
```js theme={null}
```
```js theme={null}
```
## Variant
You can define and use [variants](/ui-customization/layout#variants) for the Notification Tool or the Notification Panel.
1. `variant`: For the Notification Tool.
2. `panelVariant`: For the Notification Panel.
```jsx theme={null}
```
```html theme={null}
```
# Control Panel
Source: https://docs.velt.dev/ui-customization/features/async/recorder/control-panel
Recorder control panel component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## Floating Mode
```jsx theme={null}
```
```html theme={null}
```
### Container (Floating Mode)
```jsx theme={null}
```
```html theme={null}
```
#### Video (Floating Mode Container)
```jsx theme={null}
```
```html theme={null}
```
#### Waveform (Floating Mode Container)
```jsx theme={null}
```
```html theme={null}
```
#### CollapsedButton (Floating Mode Container)
```jsx theme={null}
```
```html theme={null}
```
##### On (CollapsedButton)
```jsx theme={null}
```
```html theme={null}
```
##### Off (CollapsedButton)
```jsx theme={null}
```
```html theme={null}
```
#### Paused (Floating Mode Container)
```jsx theme={null}
```
```html theme={null}
```
### ScreenMiniContainer (Floating Mode)
```jsx theme={null}
```
```html theme={null}
```
#### Video (Floating Mode ScreenMiniContainer)
This is the same Video component as used in the Floating Mode Container.
```jsx theme={null}
```
```html theme={null}
```
#### Screen (Floating Mode ScreenMiniContainer)
```jsx theme={null}
```
```html theme={null}
```
### Loading (Floating Mode)
```jsx theme={null}
```
```html theme={null}
```
### ActionBar (Floating Mode)
```jsx theme={null}
```
```html theme={null}
```
#### TypeIcon (ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Time (ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Waveform (ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Toggle (ActionBar)
```jsx theme={null}
```
```html theme={null}
```
##### Pause (Toggle)
```jsx theme={null}
```
```html theme={null}
```
##### Play (Toggle)
```jsx theme={null}
```
```html theme={null}
```
#### Pip (ActionBar)
Picture-in-Picture button for screen recordings with camera. This allows users to view the recording in a floating window during screen capture.
Picture-in-Picture is only supported in Chrome browsers and only works for screen recordings when the camera is active. This feature is disabled by default.
```jsx theme={null}
```
```html theme={null}
```
#### Stop (ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Clear (ActionBar)
```jsx theme={null}
```
```html theme={null}
```
## Thread Mode
```jsx theme={null}
```
```html theme={null}
```
### Video (Thread Mode)
```jsx theme={null}
```
```html theme={null}
```
### Loading (Thread Mode)
```jsx theme={null}
```
```html theme={null}
```
### ActionBar (Thread Mode)
This ActionBar is similar in structure to the Floating Mode ActionBar but specific to ThreadMode.
```jsx theme={null}
```
```html theme={null}
```
#### TypeIcon (Thread Mode ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Time (Thread Mode ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Waveform (Thread Mode ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Toggle (Thread Mode ActionBar)
```jsx theme={null}
```
```html theme={null}
```
##### Pause (Thread Mode Toggle)
```jsx theme={null}
```
```html theme={null}
```
##### Play (Thread Mode Toggle)
```jsx theme={null}
```
```html theme={null}
```
#### Pip (Thread Mode ActionBar)
Picture-in-Picture button for screen recordings with camera in Thread Mode. This allows users to view the recording in a floating window during screen capture.
Picture-in-Picture is only supported in Chrome browsers and only works for screen recordings when the camera is active. This feature is disabled by default.
```jsx theme={null}
```
```html theme={null}
```
#### Stop (Thread Mode ActionBar)
```jsx theme={null}
```
```html theme={null}
```
#### Clear (Thread Mode ActionBar)
```jsx theme={null}
```
```html theme={null}
```
### ScreenMiniContainer (Thread Mode)
```jsx theme={null}
```
```html theme={null}
```
#### Video (Thread Mode ScreenMiniContainer)
This is the same Video component as used in the main Thread Mode section.
```jsx theme={null}
```
```html theme={null}
```
#### Screen (Thread Mode ScreenMiniContainer)
```jsx theme={null}
```
```html theme={null}
```
# Media Source Settings
Source: https://docs.velt.dev/ui-customization/features/async/recorder/media-source-settings
Media source settings component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## Audio
```jsx theme={null}
```
```html theme={null}
```
### ToggleIcon
```jsx theme={null}
```
```html theme={null}
```
### SelectedLabel
```jsx theme={null}
```
```html theme={null}
```
### Divider
```jsx theme={null}
```
```html theme={null}
```
### Options
```jsx theme={null}
```
```html theme={null}
```
#### Item
```jsx theme={null}
```
```html theme={null}
```
## Video
```jsx theme={null}
```
```html theme={null}
```
### ToggleIcon
```jsx theme={null}
```
```html theme={null}
```
### SelectedLabel
```jsx theme={null}
```
```html theme={null}
```
### Divider
```jsx theme={null}
```
```html theme={null}
```
### Options
```jsx theme={null}
```
```html theme={null}
```
#### Item
```jsx theme={null}
```
```html theme={null}
```
# Recorder player
Source: https://docs.velt.dev/ui-customization/features/async/recorder/recorder-player
Recorder player component. This is the small player that appears when a recording is done.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## VideoContainer
```jsx theme={null}
```
```html theme={null}
```
#### Video
```jsx theme={null}
```
```html theme={null}
```
#### Timeline
```jsx theme={null}
```
```html theme={null}
```
#### PlayButton
```jsx theme={null}
```
```html theme={null}
```
#### SeekBar
```jsx theme={null}
```
```html theme={null}
```
#### FullScreenButton
```jsx theme={null}
```
```html theme={null}
```
#### Overlay
```jsx theme={null}
```
```html theme={null}
```
#### PlayButton
```jsx theme={null}
```
```html theme={null}
```
#### Time
```jsx theme={null}
```
```html theme={null}
```
#### Subtitles
```jsx theme={null}
```
```html theme={null}
```
#### Avatar
```jsx theme={null}
```
```html theme={null}
```
#### Name
```jsx theme={null}
```
```html theme={null}
```
#### SubtitlesButton
```jsx theme={null}
```
```html theme={null}
```
#### Transcription
```jsx theme={null}
```
```html theme={null}
```
#### EditButton
```jsx theme={null}
```
```html theme={null}
```
#### CopyLink
```jsx theme={null}
```
```html theme={null}
```
#### Delete
```jsx theme={null}
```
```html theme={null}
```
## AudioContainer
```jsx theme={null}
```
```html theme={null}
```
#### AudioToggle
```jsx theme={null}
```
```html theme={null}
```
#### Pause
```jsx theme={null}
```
```html theme={null}
```
#### Play
```jsx theme={null}
```
```html theme={null}
```
#### Time
```jsx theme={null}
```
```html theme={null}
```
#### AudioWaveform
```jsx theme={null}
```
```html theme={null}
```
#### Subtitles
```jsx theme={null}
```
```html theme={null}
```
#### Avatar
```jsx theme={null}
```
```html theme={null}
```
#### Name
```jsx theme={null}
```
```html theme={null}
```
#### SubtitlesButton
```jsx theme={null}
```
```html theme={null}
```
#### Transcription
```jsx theme={null}
```
```html theme={null}
```
#### CopyLink
```jsx theme={null}
```
```html theme={null}
```
#### Delete
```jsx theme={null}
```
```html theme={null}
```
#### Audio
This tag loads the audio recording and is hidden by default to prevent it from displaying in the UI.
```jsx theme={null}
```
```html theme={null}
```
# Recorder Player Expanded
Source: https://docs.velt.dev/ui-customization/features/async/recorder/recorder-player-expanded
Recorder player expanded component. This is the expanded player that appears when user wants to view the recording in full screen.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## Panel
```jsx theme={null}
```
```html theme={null}
```
#### Display
```jsx theme={null}
```
```html theme={null}
```
#### Subtitles
```jsx theme={null}
```
```html theme={null}
```
#### CopyLink
```jsx theme={null}
```
```html theme={null}
```
#### MinimizeButton
```jsx theme={null}
```
```html theme={null}
```
#### Controls
```jsx theme={null}
```
```html theme={null}
```
#### ProgressBar
```jsx theme={null}
```
```html theme={null}
```
#### ToggleButton
```jsx theme={null}
```
```html theme={null}
```
##### Pause
```jsx theme={null}
```
```html theme={null}
```
##### Play
```jsx theme={null}
```
```html theme={null}
```
#### Time
```jsx theme={null}
```
```html theme={null}
```
##### CurrentTime
```jsx theme={null}
```
```html theme={null}
```
##### TotalTime
```jsx theme={null}
```
```html theme={null}
```
#### SubtitleButton
```jsx theme={null}
```
```html theme={null}
```
##### Icon
```jsx theme={null}
```
```html theme={null}
```
##### Tooltip
```jsx theme={null}
```
```html theme={null}
```
#### TranscriptionButton
```jsx theme={null}
```
```html theme={null}
```
##### Icon
```jsx theme={null}
```
```html theme={null}
```
##### Tooltip
```jsx theme={null}
```
```html theme={null}
```
#### VolumeButton
```jsx theme={null}
```
```html theme={null}
```
#### SettingsButton
```jsx theme={null}
```
```html theme={null}
```
#### DeleteButton
```jsx theme={null}
```
```html theme={null}
```
## Transcription
```jsx theme={null}
```
```html theme={null}
```
# Recorder Tool
Source: https://docs.velt.dev/ui-customization/features/async/recorder/recorder-tool
The button to add new recordings.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltRecorderAllToolWireframe
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
### VeltRecorderAllToolMenuWireframe
```jsx theme={null}
{/* ... Add your content here */}
{/* ... Add your content here */}
{/* ... Add your content here */}
```
```html theme={null}
```
#### Audio
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
#### Video
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
#### Screen
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
## VeltRecorderAudioToolWireframe
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
## VeltRecorderVideoToolWireframe
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
## VeltRecorderScreenToolWireframe
```jsx theme={null}
{/* ... Add your content here */}
```
```html theme={null}
```
# Styling
## Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
`Default: true`
```jsx theme={null}
```
```html theme={null}
```
## Dark Mode
`Default: false`
```js theme={null}
```
```html theme={null}
```
# Recording Preview Steps Dialog
Source: https://docs.velt.dev/ui-customization/features/async/recorder/recording-preview-steps-dialog
Recording preview steps dialog component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
### Audio
This component is used for audio recording preview.
```jsx theme={null}
```
```html theme={null}
```
#### Audio Bottom Panel
Bottom panel of the audio recording preview dialog.
```jsx theme={null}
```
```html theme={null}
```
##### Icon
```jsx theme={null}
```
```html theme={null}
```
##### Countdown
```jsx theme={null}
```
```html theme={null}
```
##### Close
```jsx theme={null}
```
```html theme={null}
```
#### Audio Button Panel
Button panel of the audio recording preview dialog.
```jsx theme={null}
```
```html theme={null}
```
##### Start Recording
```jsx theme={null}
```
```html theme={null}
```
##### Mic Button
```jsx theme={null}
```
```html theme={null}
```
###### On
```jsx theme={null}
```
```html theme={null}
```
###### Off
```jsx theme={null}
```
```html theme={null}
```
##### Settings
```jsx theme={null}
```
```html theme={null}
```
#### Audio Timer
Timer of the audio recording preview dialog.
```jsx theme={null}
```
```html theme={null}
```
##### Countdown
```jsx theme={null}
```
```html theme={null}
```
##### Cancel
```jsx theme={null}
```
```html theme={null}
```
#### Close Button
```jsx theme={null}
```
```html theme={null}
```
#### Waveform
```jsx theme={null}
```
```html theme={null}
```
#### Settings Panel
```jsx theme={null}
```
```html theme={null}
```
### Video
This component is used for both video and screen recording previews.
```jsx theme={null}
```
```html theme={null}
```
#### Video Bottom Panel
Bottom panel of the video recording preview dialog.
```jsx theme={null}
// If you want to customize the Icon for individual media types, you can use the following:
{/* Your custom icon here */}
{/* Your custom icon here */}
{/* Your custom icon here */}
```
```html theme={null}
```
##### Icon
```jsx theme={null}
// To customize for a specific media type:
{/* audio icon */}
{/* video icon */}
{/* screen icon */}
```
```html theme={null}
```
##### Countdown
```jsx theme={null}
```
```html theme={null}
```
##### Close
```jsx theme={null}
```
```html theme={null}
```
#### Video Button Panel
Button panel of the video recording preview dialog.
```jsx theme={null}
```
```html theme={null}
```
##### Start Recording
```jsx theme={null}
```
```html theme={null}
```
##### Mic Button
```jsx theme={null}
```
```html theme={null}
```
###### On
```jsx theme={null}
```
```html theme={null}
```
###### Off
```jsx theme={null}
```
```html theme={null}
```
##### Camera Button
```jsx theme={null}
```
```html theme={null}
```
###### On
```jsx theme={null}
```
```html theme={null}
```
###### Off
```jsx theme={null}
```
```html theme={null}
```
##### Settings
```jsx theme={null}
```
```html theme={null}
```
#### Video Timer
Timer of the video recording preview dialog.
```jsx theme={null}
```
```html theme={null}
```
##### Countdown
```jsx theme={null}
```
```html theme={null}
```
##### Cancel
```jsx theme={null}
```
```html theme={null}
```
#### Waveform
```jsx theme={null}
```
```html theme={null}
```
#### Close Button
```jsx theme={null}
```
```html theme={null}
```
#### Camera Off Message
```jsx theme={null}
```
```html theme={null}
```
#### Settings Panel
```jsx theme={null}
```
```html theme={null}
```
#### Video Player
```jsx theme={null}
```
```html theme={null}
```
#### ScreenPlayer (Video)
This component is used for screen recording preview within the video dialog. It displays the screen capture content for screen recordings.
```jsx theme={null}
```
```html theme={null}
```
# Subtitles
Source: https://docs.velt.dev/ui-customization/features/async/recorder/subtitles
Subtitles component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## EmbedMode
```jsx theme={null}
```
```html theme={null}
```
#### Text
```jsx theme={null}
```
```html theme={null}
```
## FloatingMode
```jsx theme={null}
```
```html theme={null}
```
#### Button
```jsx theme={null}
```
```html theme={null}
```
#### Tooltip
```jsx theme={null}
```
```html theme={null}
```
#### Panel
```jsx theme={null}
```
```html theme={null}
```
#### CloseButton
```jsx theme={null}
```
```html theme={null}
```
#### Text
```jsx theme={null}
```
```html theme={null}
```
# Transcription
Source: https://docs.velt.dev/ui-customization/features/async/recorder/transcription
Transcription component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
## FloatingMode
```jsx theme={null}
```
```html theme={null}
```
#### Button
```jsx theme={null}
```
```html theme={null}
```
#### Tooltip
```jsx theme={null}
```
```html theme={null}
```
#### PanelContainer
```jsx theme={null}
```
```html theme={null}
```
#### Panel
```jsx theme={null}
```
```html theme={null}
```
#### CloseButton
```jsx theme={null}
```
```html theme={null}
```
#### CopyLink
```jsx theme={null}
```
```html theme={null}
```
#### Button
```jsx theme={null}
```
```html theme={null}
```
#### Tooltip
```jsx theme={null}
```
```html theme={null}
```
#### Summary
```jsx theme={null}
```
```html theme={null}
```
#### Text
```jsx theme={null}
```
```html theme={null}
```
#### ExpandToggle
```jsx theme={null}
```
```html theme={null}
```
#### On
```jsx theme={null}
```
```html theme={null}
```
#### Off
```jsx theme={null}
```
```html theme={null}
```
#### Content
```jsx theme={null}
```
```html theme={null}
```
#### Item
```jsx theme={null}
```
```html theme={null}
```
#### Text
```jsx theme={null}
```
```html theme={null}
```
#### Time
```jsx theme={null}
```
```html theme={null}
```
## EmbedMode
```jsx theme={null}
```
```html theme={null}
```
#### Panel
```jsx theme={null}
```
```html theme={null}
```
#### CloseButton
```jsx theme={null}
```
```html theme={null}
```
#### CopyLink
```jsx theme={null}
```
```html theme={null}
```
#### Button
```jsx theme={null}
```
```html theme={null}
```
#### Tooltip
```jsx theme={null}
```
```html theme={null}
```
#### Summary
```jsx theme={null}
```
```html theme={null}
```
#### Text
```jsx theme={null}
```
```html theme={null}
```
#### ExpandToggle
```jsx theme={null}
```
```html theme={null}
```
#### On
```jsx theme={null}
```
```html theme={null}
```
#### Off
```jsx theme={null}
```
```html theme={null}
```
#### Content
```jsx theme={null}
```
```html theme={null}
```
#### Item
```jsx theme={null}
```
```html theme={null}
```
#### Text
```jsx theme={null}
```
```html theme={null}
```
#### Time
```jsx theme={null}
```
```html theme={null}
```
# Video Editor
Source: https://docs.velt.dev/ui-customization/features/async/recorder/video-editor
Video editor component.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## Overview
```jsx theme={null}
```
```html theme={null}
```
### Title
```jsx theme={null}
```
```html theme={null}
```
### ApplyButton
```jsx theme={null}
```
```html theme={null}
```
#### Loading
```jsx theme={null}
```
```html theme={null}
```
### RetakeButton
```jsx theme={null}
```
```html theme={null}
```
### DownloadButton
```jsx theme={null}
```
```html theme={null}
```
### CloseButton
```jsx theme={null}
```
```html theme={null}
```
### Preview
```jsx theme={null}
```
```html theme={null}
```
#### Loading
```jsx theme={null}
```
```html theme={null}
```
#### Video
```jsx theme={null}
```
```html theme={null}
```
### ToggleButton
**Template Variables:**
| Variable | Description |
| ---------------------- | ---------------------------------------------------------------------- |
| `isPlayingTrimPreview` | This is true when the video is playing in the preview else it is false |
```jsx theme={null}
```
```html theme={null}
```
### Time
```jsx theme={null}
```
```html theme={null}
```
#### CurrentTime
```jsx theme={null}
```
```html theme={null}
```
#### TotalTime
```jsx theme={null}
```
```html theme={null}
```
### SplitButton
```jsx theme={null}
```
```html theme={null}
```
#### CurrentTime
```jsx theme={null}
```
```html theme={null}
```
### DeleteButton
```jsx theme={null}
```
```html theme={null}
```
### AddZoomButton
```jsx theme={null}
```
```html theme={null}
```
### Timeline
```jsx theme={null}
```
```html theme={null}
```
#### BackspaceHint
```jsx theme={null}
```
```html theme={null}
```
#### Onboarding
```jsx theme={null}
```
```html theme={null}
```
##### **Content**
```jsx theme={null}
```
```html theme={null}
```
##### **Text**
```jsx theme={null}
```
```html theme={null}
```
###### **Title**
```jsx theme={null}
```
```html theme={null}
```
###### **Description**
```jsx theme={null}
```
```html theme={null}
```
##### **Arrow**
```jsx theme={null}
```
```html theme={null}
```
#### Container
```jsx theme={null}
```
```html theme={null}
```
##### **Playhead**
```jsx theme={null}
```
```html theme={null}
```
###### **Line**
```jsx theme={null}
```
```html theme={null}
```
###### **Actions**
```jsx theme={null}
```
```html theme={null}
```
##### **Trim**
```jsx theme={null}
```
```html theme={null}
```
##### **Scale**
```jsx theme={null}
```
```html theme={null}
```
###### **ZoomButton**
```jsx theme={null}
```
```html theme={null}
```
###### **Trigger**
```jsx theme={null}
```
```html theme={null}
```
###### **Options**
```jsx theme={null}
```
```html theme={null}
```
###### **List**
```jsx theme={null}
```
```html theme={null}
```
###### **Item**
```jsx theme={null}
```
```html theme={null}
```
###### **Input**
```jsx theme={null}
```
```html theme={null}
```
#### Marker
```jsx theme={null}
```
```html theme={null}
```
# Cursors
Source: https://docs.velt.dev/ui-customization/features/realtime/cursors
The Cursors component displays the cursors of users in a document.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltCursorPointerWireframe
```jsx theme={null}
```
```html theme={null}
```
### Arrow
```jsx theme={null}
```
```html theme={null}
```
### Avatar
```jsx theme={null}
```
```html theme={null}
```
### Default
```jsx theme={null}
```
```html theme={null}
```
#### Name
```jsx theme={null}
```
```html theme={null}
```
#### Comment
```jsx theme={null}
```
```html theme={null}
```
### AudioHuddle
```jsx theme={null}
```
```html theme={null}
```
#### Avatar
```jsx theme={null}
```
```html theme={null}
```
#### Audio
```jsx theme={null}
```
```html theme={null}
```
### VideoHuddle
```jsx theme={null}
```
```html theme={null}
```
# Parts
Source: https://docs.velt.dev/ui-customization/features/realtime/huddle/parts
We offer several parts which can be used like classes. Full list below.
The component is encapsulated in Shadow DOM, which is isolated from the normal DOM.
Set whatever CSS rules you want.
The part lets you target a specific element within a Shadow DOM.
Reference the table below to see what parts we expose.
Alternatively, you can directly inspect the component HTML to see what parts are available.
| property | description |
| ------------------ | ----------------------------------------- |
| `container` | Targets the comment tool container |
| `button-container` | Targets the comment tool button container |
| `button-icon` | Targets the comment tool button SVG icon |
```css Tool theme={null}
velt-huddle-tool::part(button-icon) {
width: 1.5rem;
height: 1.5rem;
}
```
# Slots
Source: https://docs.velt.dev/ui-customization/features/realtime/huddle/slots
Provide a template for the Huddle Tool.
Target the `button` slot with your own custom template.
```js theme={null}
Huddle
```
Provide a template for the Huddle Tool.
Target the `button` slot with your own custom template.
```html theme={null}
Huddle
```
```js React / Next.js theme={null}
import {
VeltHuddleTool
} from '@veltdev/react';
export default function App() {
return (
<>
Huddle
>
);
}
```
```html HTML theme={null}
Huddle documentation
Huddle
```
# Variables
Source: https://docs.velt.dev/ui-customization/features/realtime/huddle/variables
To update CSS variables for the Huddle Tool, please refer to [Global Styles](/global-styles/global-styles)
# Live Selection
Source: https://docs.velt.dev/ui-customization/features/realtime/live-selection
## 1. Enable/Disable Default Styling
* When enabled, the SDK will apply default styling to the selected elements.
* When disabled, the SDK will not apply any styling to the selected elements. You can use these classes to style the selected elements:
* `.velt-live-selection-on-element`: This dynamic class will be added to the selected element only when there is a user on that element.
* `.velt-live-selection-on-text`: This dynamic class will be added to the text node only when a user has selected that text.
* Default: `Enabled`.
```javascript theme={null}
const selectionElement = client.getSelectionElement();
selectionElement.enableDefaultStyling();
selectionElement.disableDefaultStyling();
```
**Custom Styling:**
```css theme={null}
.velt-live-selection-on-element {
outline: 2px solid var(--velt-color);
outline-offset: -2px;
}
```
```javascript theme={null}
const selectionElement = Velt.getSelectionElement();
selectionElement.enableDefaultStyling();
selectionElement.disableDefaultStyling();
```
**Custom Styling:**
```css theme={null}
.velt-live-selection-on-element {
outline: 2px solid var(--velt-color);
outline-offset: -2px;
}
```
# Presence
Source: https://docs.velt.dev/ui-customization/features/realtime/presence
The Presence component displays the avatars of users in a room.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltPresenceWireframe
```jsx theme={null}
```
```html theme={null}
```
### AvatarList
```jsx theme={null}
```
```html theme={null}
```
#### Item
```jsx theme={null}
```
```html theme={null}
```
### AvatarRemainingCount
```jsx theme={null}
```
```html theme={null}
```
## VeltPresenceTooltipWireframe
```jsx theme={null}
```
```html theme={null}
```
### Avatar
```jsx theme={null}
```
```html theme={null}
```
### StatusContainer
```jsx theme={null}
```
```html theme={null}
```
#### UserName
```jsx theme={null}
```
```html theme={null}
```
#### UserActive
```jsx theme={null}
```
```html theme={null}
```
#### UserInactive
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, ShadowDOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```jsx theme={null}
```
# Single Editor Mode
Source: https://docs.velt.dev/ui-customization/features/realtime/single-editor-mode
Wireframes for the Single Editor Mode Panel.
We recommend that you familiarize yourselves with [UI Customization Concepts](/ui-customization/overview) before attempting to modify any components.
## VeltSingleEditorModePanelWireframe
```jsx theme={null}
{/* Editor sees this button when they try to edit in a different tab */}
{/* Editor sees these buttons when Viewer requests access */}
{/* Viewer sees this button by default */}
{/* Viewer sees this button when they have already requested access */}
```
```html theme={null}
```
### ViewerText
```jsx theme={null}
```
```html theme={null}
```
### EditorText
```jsx theme={null}
```
```html theme={null}
```
### Countdown
```jsx theme={null}
```
```html theme={null}
```
### EditHere
```jsx theme={null}
```
```html theme={null}
```
### AcceptRequest
```jsx theme={null}
```
```html theme={null}
```
### RejectRequest
```jsx theme={null}
```
```html theme={null}
```
### RequestAccess
```jsx theme={null}
```
```html theme={null}
```
### CancelRequest
```jsx theme={null}
```
```html theme={null}
```
## Styling
### Disable ShadowDOM
* By default, Shadow DOM is used to ensure that your app's CSS does not interfere with the styling of the SDK components.
* Disable the shadow dom to apply your custom CSS to the component.
Default: `true`
```jsx theme={null}
```
```html theme={null}
```
### Dark Mode
By default, all components are in Light Mode. Use this prop to enable Dark Mode.
Default: `false`
```js theme={null}
```
```html theme={null}
```
### Variants
You can apply variants like you do for other components. You need to define variants using Wireframes. Read more here [here](/ui-customization/layout#create-custom-variants).
```js theme={null}
```
```html theme={null}
```
# Layout Customization
Source: https://docs.velt.dev/ui-customization/layout
# Overview
This lets you modify how components are structured and rendered in your app. You can:
* Replace default HTML with your own components and HTML structure
* Remove or reorder components
* Create multiple variants of the same component
All layout customizations are done using Wireframe components that act as templates. Changes apply globally wherever that component is used.
Styling note: Passing `className`/`style` to Velt components does not change their UI. Add your classes/styles inside the Wireframe templates, or use CSS overrides with Shadow DOM disabled. See [Styling](/ui-customization/styling).
# Ways to Customize Layout
### Single Component Customization (Targeted)
This lets you customize individual parts of a component without dealing with the entire structure. **We recommend this approach for most use cases**.
**Benefits:**
* Simpler, more maintainable code
* Focus on just the parts you need to change
* Greater flexibility for specific UI elements
**Example:** Customizing just the Comment Dialog header:
```jsx theme={null}
```
```html theme={null}
```
### Full Component Tree Customization (Comprehensive)
Use this pattern when you need to modify the entire component structure or multiple related components.
**Benefits:**
* Complete control over component hierarchy
* Easier to modify relationships between components
* Better for large-scale structural changes
**Drawbacks:**
* Custom CSS required for the entire component tree since adding children components to wireframes removes Velt's default styles.
**Example:** Customizing the entire Comment Dialog structure:
```jsx theme={null}
```
```html theme={null}
```
# Variants
Variants allow you to:
* Create multiple styled versions of the same component
* Switch between them dynamically in different parts of your app
* Maintain consistent behavior while having different looks
### Create Custom Variants
Custom variants let you define your own versions of components. For example: You can have a variant of Comment Sidebar for one page and another for another page.
On the wireframe component, add the `variant` prop to define your custom variants.
```jsx theme={null}
{/* Variant for preview page */}
{/* Custom layout for preview */}
{/* Variant for editor page */}
{/* Custom layout for editor */}
```
```html theme={null}
{/* Variant for preview page */}
{/* Custom layout for preview */}
{/* Variant for editor page */}
{/* Custom layout for editor */}
```
* Use the `variant` prop on the related Velt component to apply the custom variant you defined. The value should match the variant name from step 1.
* For example, use `variant="preview-page"` to apply the preview page variant you created above.
```jsx theme={null}
```
```html theme={null}
```
### Use Pre-defined Variants
Many components come with built-in variants optimized for specific use cases. For example, the Comment Dialog has two pre-defined variants for different contexts:
```jsx theme={null}
{/* For Pin, Area, and Text comments */}
{/* Custom layout */}
{/* For Sidebar comments */}
{/* Custom layout */}
```
```html theme={null}
{/* For Pin, Area, and Text comments */}
{/* Custom layout */}
{/* For Sidebar comments */}
{/* Custom layout */}
```
* Each component's documentation lists its supported pre-defined variants
* Pre-defined variants are optimized for specific use cases but can still be customized
* You can combine pre-defined variants with your own custom styling
# Common Customization Tasks
### Replace Default Layout
Simply provide your own HTML or components as children of the wireframe component:
```jsx theme={null}
Add Files
```
```html theme={null}
Add Files
```
### Remove Components
To remove a component, you either:
* simply omit it from the wireframe template as shown below, or
* use [Conditional Templates](/ui-customization/conditional-templates)
```jsx theme={null}
{/* Priority and CopyLink buttons removed */}
```
```html theme={null}
{/* Priority and CopyLink buttons removed */}
```
### Reorder Components
To reorder components, simply rearrange them within the wireframe template.
```jsx theme={null}
```
```html theme={null}
```
# Localisation
Source: https://docs.velt.dev/ui-customization/localisation
Localize all static strings visible in the SDK Components.
If you notice any missing strings, please reach out to us.
## Steps
Get the complete list of strings that can be localized [here](https://firebasestorage.googleapis.com/v0/b/snippyly.appspot.com/o/external%2Flocalization-strings-map.json?alt=media\&token=0cdd2b52-10ed-4033-a08a-5c2b622ce7df).
* Translate the strings in the languages you want to support.
* Create an object with the translations for the languages you want to support in the following format:
```json theme={null}
{
"language-code-1": {
"original string 1": "translated string 1",
"original string 2": "translated string 2",
...
},
"language-code-2": {
"original string 1": "translated string 1",
"original string 2": "translated string 2",
...
},
...
}
```
Call the `setTranslations` method with the translations object you created in the previous step.
```js theme={null}
client.setTranslations(translationsObject);
```
Set the language you want to use for the SDK. The language code should be the same as the language code you used in the translations object.
```js theme={null}
client.setLanguage('en');
```
## Example
```jsx theme={null}
// Provide the localization object for the languages you want to support.
client.setTranslations({
'en': {
'All comments': 'All comments',
},
'fr': {
'All comments': 'Tous les commentaires',
},
// Add more languages as needed.
});
// Set one of the languages you've provided the translations for.
client.setLanguage('en');
```
```js theme={null}
// Provide the localization object for the languages you want to support.
Velt.setTranslations({
'en': {
'All comments': 'All comments',
},
'fr': {
'All comments': 'Tous les commentaires',
},
// Add more languages as needed.
});
// Set one of the languages you've provided the translations for.
Velt.setLanguage('en');
```
# Overview
Source: https://docs.velt.dev/ui-customization/overview
Velt Components can be customized in 5 key ways:
1. [**Layout**](#1-layout): For structure and organization of components
2. [**Styling**](#2-styling): For visual design using CSS, themes, or your CSS framework
3. [**Template Variables**](#3-template-variables): For rendering dynamic content
4. [**Conditional Templates**](#4-conditional-templates): For custom rendering logic
5. [**Action Components**](#5-action-components): For interactivity and custom behaviors
# 1. Layout
### Understanding Wireframes
Think of Wireframes like blueprints - they're templates that let you customize how Velt components render in your app. They are not functional components. Define them once, and Velt will swap in your custom template wherever a component renders, overriding the default.
When you create a wireframe:
* It acts as a global template
* Changes apply everywhere that component is used
* You can use your own components and HTML structure
**What you can do:**
* ✅ Use your own Components/HTML
* ✅ Apply your styling
* ✅ Nest regular HTML elements
* ✅ Use other Velt wireframe components
**Limitations with Wireframes:**
* ❌ Cannot nest regular Velt components inside wireframes
* ❌ Host App's dynamic variables: Won't work directly inside wireframes.
* ❌ Host App's JavaScript: Custom scripts won't run natively inside wireframes.
**How Velt solves these:**
* **Host App's dynamic variables:** Use [UI State](/ui-customization/template-variables#2-injecting-your-own-data) to inject your app's dynamic variables into Velt components seamlessly. This is available across all Velt Wireframes.
* **Host App's JavaScript:** Wrap your component in [`VeltButtonWireframe`](/ui-customization/custom-action-component). It exposes a callback. Listen for it to trigger your custom JS logic.
Empty wireframes keep Velt's default styling.
Adding any children to a wireframe template removes most of its default styling. This gives you full control over the layout.
However, you will still need to use a combination of CSS overrides on any remaining styles and your own components to achieve the desired styling.
### a. Replace Layout
Replace Velt's default HTML with your own structure.
[Learn more](/ui-customization/layout#replace-default-layout)
### b. Remove Components
Remove specific parts of a Velt component you don't need.
[Learn more](/ui-customization/layout#remove-components)
### c. Reorder Components
Change the order of elements within a Velt component.
[Learn more](/ui-customization/layout#reorder-components)
### d. Use Variants
Need different versions of the same component? Use variants to:
* Create multiple styled versions
* Switch between them dynamically
* Maintain consistent behavior with different looks
[Learn more about variants](/ui-customization/layout#variants)
# 2. Styling
Velt components can be styled in two main ways:
### a. Themes
**NEW:** Try the [Theme Playground](https://playground.velt.dev/themes) to visually customize and preview themes.
1. **CSS Variables:** Quickest way to match your design system using CSS variables. This gets applied to all components globally. [Learn more](/ui-customization/styling#themes)
2. **Dark Mode:** Built in support for light and dark modes. [Learn more](/ui-customization/styling#dark-mode)
### b. Custom CSS or libraries
1. **Custom CSS or frameworks:** Gives you full control over the styling of Velt components using your own CSS or frameworks. This requires disabling Shadow DOM. [Learn more](/ui-customization/styling#custom-css-or-libraries)
2. **Conditional Classes:** Apply classes conditionally based available template variables. [Learn more](/ui-customization/styling#conditional-classes)
Passing `className` or `style` to Velt components does not change their UI. Style via Wireframes (by adding your own HTML and classes inside the templates) or via CSS overrides with Shadow DOM disabled.
# 3. Template Variables
Template Variables let you work with dynamic data in two ways:
a. **Use Existing Velt Component Data**
* Access and render data that's already present inside Velt components.
* eg: Customer metadata set on comment threads.
b. **Inject Your App's Data**
* Inject and render custom dynamic data from your application into Velt components.
[Learn more](/ui-customization/template-variables)
# 4. Conditional Templates
Add logic to show/hide components based on the template variables.
[Learn more](/ui-customization/conditional-templates)
# 5. Action Components
* Extend the functionality of any Velt component.
* Clicking an action button provides a callback where you can write your own custom code.
[Learn more](/ui-customization/custom-action-component)
# Setup Wireframes
Source: https://docs.velt.dev/ui-customization/setup
# Understanding Wireframes
Wireframes are templates that define how Velt components should render in your app:
* Act as global templates. Changes apply everywhere the component is used.
* Allow full customization of layout and styling.
* Wireframes themselves don't render in the DOM.
Adding any children to a wireframe template removes most of its default styling. This gives you full control over the layout.
However, you will still need to use a combination of CSS overrides on any remaining styles and your own components to achieve the desired styling.
## Step-by-Step Setup
```jsx theme={null}
import { VeltWireframe, VeltProvider } from '@veltdev/react'
{/* Your customization templates */}
```
```html theme={null}
```
Copy the wireframe template of the component you want to customize. You can find the wireframe templates in the component's UI Customization documentation.
```jsx theme={null}
{/* Example: Customizing Comment Dialog Header */}
```
```html theme={null}
```
You can customize using the options like:
* [Layout](/ui-customization/layout): Customize the layout of the component
* [Styling](/ui-customization/styling): Apply custom CSS and [themes](https://playground.velt.dev/themes)
* [Template Variables](/ui-customization/template-variables): Add dynamic content to your templates
* [Conditional Templates](/ui-customization/conditional-templates): Add conditional rendering logic
* [Custom Actions](/ui-customization/custom-action-component): Add custom interactivity
**Example:**
```jsx theme={null}
Custom HTML
```
```html theme={null}
Custom HTML
```
* After defining templates, use the actual feature components in your app as normal.
* The wireframe components are not meant to be rendered directly in your application. They merely serve as template definitions.
```jsx theme={null}
function MyApp() {
return (
{/* Will use your custom template */}
)
}
```
```html theme={null}
```
# CSS Customization
Source: https://docs.velt.dev/ui-customization/styling
**NEW:** Try out the [Theme Playground](https://playground.velt.dev/themes) to visually customize and preview your themes.
Velt components can be styled in two main ways:
1. **Themes**
2. **Custom CSS**
Direct classes and inline styles on Velt components are not applied. Passing `className` or `style` to a Velt component does not change its UI. Add classes/styles to your HTML inside the corresponding Wireframe template, or use CSS overrides after disabling Shadow DOM.
# Themes
You can use the [Theme Playground](https://playground.velt.dev/themes) to customize and preview the themes fast.
### CSS Variables
* You can customize:
* Border radius
* Spacing
* Typography
* Colors for light and dark modes
* Z-Index
* Set CSS variables on your `` tag to customize all Velt components:
**Example:**
```css theme={null}
body {
/* Colors */
--velt-light-mode-accent: #0BA528;
/* Border Radius */
--velt-border-radius-sm: 4px;
/* Spacing */
--velt-spacing-sm: 8px;
}
```
**Available Theme Variables:**
```css theme={null}
--velt-border-radius-2xs: 0.125rem; // 2px
--velt-border-radius-xs: 0.25rem; // 4px
--velt-border-radius-sm: 0.5rem; // 8px
--velt-border-radius-md: 0.75rem; // 12px
--velt-border-radius-lg: 1rem; // 16px
--velt-border-radius-xl: 1.25rem; // 20px
--velt-border-radius-2xl: 1.5rem; // 24px
--velt-border-radius-3xl: 2rem; // 32px
--velt-border-radius-full: 5rem; // 80px
```
```css theme={null}
--velt-spacing-2xs: 0.125rem; // 2px
--velt-spacing-xs: 0.25rem; // 4px
--velt-spacing-sm: 0.5rem; // 8px
--velt-spacing-md: 0.75rem; // 12px
--velt-spacing-lg: 1rem; // 16px
--velt-spacing-xl: 1.25rem; // 20px
--velt-spacing-2xl: 1.5rem; // 24px
```
```css theme={null}
--velt-default-font-family: sans-serif;
--velt-font-size-2xs: 0.625rem; // 10px
--velt-font-size-xs: 0.75rem; // 12px
--velt-font-size-sm: 0.875rem; // 14px
--velt-font-size-md: 1rem; // 16px
--velt-font-size-lg: 1.5rem; // 24px
--velt-font-size-xl: 1.75rem; // 28px
--velt-font-size-2xl: 2rem; // 32px
```
```css theme={null}
/* Base Colors */
--velt-light-mode-green: #0DCF82;
--velt-light-mode-magenta: #A259FE;
--velt-light-mode-amber: #FF7162;
--velt-light-mode-purple: #625DF5;
--velt-light-mode-cyan: #4BC9F0;
--velt-light-mode-orange: #FE965C;
--velt-light-mode-black: #080808;
--velt-light-mode-white: #FFFFFF;
--velt-light-mode-gray: #EBEBEB;
/* Accent Colors */
--velt-light-mode-accent: #625DF5;
--velt-light-mode-accent-text: #9491F8;
--velt-light-mode-accent-hover: #534FCF;
--velt-light-mode-accent-foreground: #FFFFFF;
--velt-light-mode-accent-light: #F2F2FE;
--velt-light-mode-accent-transparent: rgba(148, 145, 248, 0.08);
/* Text Shades */
--velt-light-mode-text-0: #0A0A0A;
--velt-light-mode-text-1: #141414;
--velt-light-mode-text-2: #1F1F1F;
--velt-light-mode-text-3: #292929;
--velt-light-mode-text-4: #3D3D3D;
--velt-light-mode-text-5: #525252;
--velt-light-mode-text-6: #666666;
--velt-light-mode-text-7: #7A7A7A;
--velt-light-mode-text-8: #858585;
--velt-light-mode-text-9: #999999;
--velt-light-mode-text-10: #B8B8B8;
--velt-light-mode-text-11: #A3A3A3;
--velt-light-mode-text-12: #8F8F8F;
/* Background Shades */
--velt-light-mode-background-0: #FFFFFF;
--velt-light-mode-background-1: #FAFAFA;
--velt-light-mode-background-2: #F5F5F5;
--velt-light-mode-background-3: #F0F0F0;
--velt-light-mode-background-4: #EBEBEB;
--velt-light-mode-background-5: #E5E5E5;
--velt-light-mode-background-6: #E0E0E0;
--velt-light-mode-background-7: #DBDBDB;
--velt-light-mode-background-8: #D6D6D6;
--velt-light-mode-background-9: #D1D1D1;
--velt-light-mode-background-10: #CCCCCC;
/* Border Shades */
--velt-light-mode-border-0: #FFFFFF;
--velt-light-mode-border-1: #FAFAFA;
--velt-light-mode-border-2: #F5F5F5;
--velt-light-mode-border-3: #F0F0F0;
--velt-light-mode-border-4: #EBEBEB;
--velt-light-mode-border-5: #E5E5E5;
--velt-light-mode-border-6: #E0E0E0;
--velt-light-mode-border-7: #DBDBDB;
--velt-light-mode-border-8: #D6D6D6;
--velt-light-mode-border-9: #D1D1D1;
--velt-light-mode-border-10: #CCCCCC;
/* Status Colors */
/* Error */
--velt-light-mode-error: #FF7162;
--velt-light-mode-error-hover: #DE5041;
--velt-light-mode-error-foreground: #FFFFFF;
--velt-light-mode-error-light: #FFF4F2;
--velt-light-mode-error-transparent: rgba(255, 113, 98, 0.08);
/* Warning */
--velt-light-mode-warning: #FFCD2E;
--velt-light-mode-warning-hover: #C69400;
--velt-light-mode-warning-foreground: #474747;
--velt-light-mode-warning-light: #FFFBEE;
--velt-light-mode-warning-transparent: rgba(255, 205, 46, 0.08);
/* Success */
--velt-light-mode-success: #198F65;
--velt-light-mode-success-hover: #006B41;
--velt-light-mode-success-foreground: #FFFFFF;
--velt-light-mode-success-light: #EDF6F3;
--velt-light-mode-success-transparent: rgba(25, 143, 101, 0.08);
/* Transparent Colors */
--velt-light-mode-background-transparent: rgba(255, 255, 255, 0.80);
--velt-light-mode-border-transparent: rgba(0, 0, 0, 0.16);
--velt-light-mode-animation-transparent: rgba(255, 255, 255, 0.2);
```
```css theme={null}
/* Base Colors */
--velt-dark-mode-green: #0DCF82;
--velt-dark-mode-magenta: #A259FE;
--velt-dark-mode-amber: #FF7162;
--velt-dark-mode-purple: #625DF5;
--velt-dark-mode-cyan: #4BC9F0;
--velt-dark-mode-orange: #FE965C;
--velt-dark-mode-black: #080808;
--velt-dark-mode-white: #FFFFFF;
--velt-dark-mode-gray: #EBEBEB;
/* Accent Colors */
--velt-dark-mode-accent: #625DF5;
--velt-dark-mode-accent-text: #9491F8;
--velt-dark-mode-accent-hover: #534FCF;
--velt-dark-mode-accent-foreground: #FFFFFF;
--velt-dark-mode-accent-light: #F2F2FE;
--velt-dark-mode-accent-transparent: rgba(148, 145, 248, 0.08);
/* Text Shades */
--velt-dark-mode-text-0: #FFFFFF;
--velt-dark-mode-text-1: #F5F5F5;
--velt-dark-mode-text-2: #EBEBEB;
--velt-dark-mode-text-3: #E0E0E0;
--velt-dark-mode-text-4: #D6D6D6;
--velt-dark-mode-text-5: #C2C2C2;
--velt-dark-mode-text-6: #ADADAD;
--velt-dark-mode-text-7: #8F8F8F;
--velt-dark-mode-text-8: #7A7A7A;
--velt-dark-mode-text-9: #666666;
--velt-dark-mode-text-10: #525252;
--velt-dark-mode-text-11: #474747;
--velt-dark-mode-text-12: #3D3D3D;
/* Background Shades */
--velt-dark-mode-background-0: #0F0F0F;
--velt-dark-mode-background-1: #1A1A1A;
--velt-dark-mode-background-2: #1F1F1F;
--velt-dark-mode-background-3: #242424;
--velt-dark-mode-background-4: #292929;
--velt-dark-mode-background-5: #2E2E2E;
--velt-dark-mode-background-6: #333333;
--velt-dark-mode-background-7: #383838;
--velt-dark-mode-background-8: #3D3D3D;
--velt-dark-mode-background-9: #424242;
--velt-dark-mode-background-10: #474747;
/* Border Shades */
--velt-dark-mode-border-0: #0F0F0F;
--velt-dark-mode-border-1: #1A1A1A;
--velt-dark-mode-border-2: #1F1F1F;
--velt-dark-mode-border-3: #242424;
--velt-dark-mode-border-4: #292929;
--velt-dark-mode-border-5: #2E2E2E;
--velt-dark-mode-border-6: #333333;
--velt-dark-mode-border-7: #383838;
--velt-dark-mode-border-8: #3D3D3D;
--velt-dark-mode-border-9: #424242;
--velt-dark-mode-border-10: #474747;
/* Status Colors */
/* Error */
--velt-dark-mode-error: #FF7162;
--velt-dark-mode-error-hover: #DE5041;
--velt-dark-mode-error-foreground: #FFFFFF;
--velt-dark-mode-error-light: #FFF4F2;
--velt-dark-mode-error-transparent: rgba(255, 113, 98, 0.08);
/* Warning */
--velt-dark-mode-warning: #FFCD2E;
--velt-dark-mode-warning-hover: #C69400;
--velt-dark-mode-warning-foreground: #474747;
--velt-dark-mode-warning-light: #FFFBEE;
--velt-dark-mode-warning-transparent: rgba(255, 205, 46, 0.08);
/* Success */
--velt-dark-mode-success: #198F65;
--velt-dark-mode-success-hover: #006B41;
--velt-dark-mode-success-foreground: #FFFFFF;
--velt-dark-mode-success-light: #EDF6F3;
--velt-dark-mode-success-transparent: rgba(25, 143, 101, 0.08);
/* Transparent Colors */
--velt-dark-mode-background-transparent: rgba(0, 0, 0, 0.80);
--velt-dark-mode-border-transparent: rgba(255, 255, 255, 0.16);
--velt-dark-mode-animation-transparent: rgba(255, 255, 255, 0.2);
```
```css theme={null}
/* Comment Pin, Triangle */
--velt-comment-pin-z-index: 2147483557;
/* Comments Minimap */
--velt-comments-minimap-z-index: 2147483637;
/* Persistent Comment Frame */
--velt-persistent-comment-frame-z-index: 2147483647;
/* Global Overlay */
--velt-global-overlay-z-index: 2147483637;
/* Recorder Player */
--velt-recorder-player-z-index: 2147483557;
/* Cursor */
--velt-cursor-z-index: 2147483647;
/* Arrow */
--velt-arrow-z-index: 2147483557;
/* Toast Popup */
--velt-toast-popup-z-index: 2147483647;
/* Live State Sync Overlay */
--velt-live-state-sync-overlay-z-index: 2147483647;
/* Follow Mode Overlay */
--velt-follow-mode-overlay-z-index: 2147483647;
```
### Dark Mode
* Enable dark mode globally or per component.
* Most components accept a `darkMode` prop (`dark-mode="true"` for non-React).
* Some components that are not directly injected by you like dialogs and pins use specific props (e.g. `dialogDarkMode`, `pinDarkMode`). You can add these props to the root component responsible for injecting them.
* Check each component's UI customization documentation for its dark mode props.
```jsx theme={null}
// Global
const client = useVeltClient();
client.setDarkMode(true);
// Per component example
```
```js theme={null}
// Global
const client = useVeltClient();
client.setDarkMode(true);
// Per component example
```
# Custom CSS or libraries
You can use your own CSS or libraries like Tailwind CSS to style the Velt components. You will need to disable the Shadow DOM to apply your styles.
### 1. **Using CSS on rendered components**
* Inspect the DOM using browser dev tools to find the class names and selectors of the target Velt components.
* Override the styles using your CSS.
* Make sure to [disable the Shadow DOM](#disable-shadow-dom) to apply your styles.
```css theme={null}
.velt-composer--input-button {
background-color: red;
border-radius: 0;
}
```
```css theme={null}
.velt-composer--input-button {
background-color: red;
border-radius: 0;
}
```
### 2. **Using CSS on Wireframes**
* Add the relevant wireframe component.
* Add class or inline styles to the wireframe component like you would on a normal HTML element.
* When the actual component is rendered, the styles will be applied to that component.
* Make sure to [disable the Shadow DOM](#disable-shadow-dom) to apply your styles.
* Passing `className` or `style` directly to Velt components does not change their UI; apply styling in the Wireframe template (or your own elements within it).
```jsx theme={null}
{/* Use CSS classes */}
{/* Or inline styles */}
```
```html theme={null}
```
### Component-Specific CSS Classes
Certain Velt components include CSS classes for specific states that you can target for custom styling. For example, the assign dropdown checkbox includes a CSS class to indicate the selected state on checkboxes.
To target the selected state, inspect the assign dropdown checkbox element in your browser dev tools to find the specific class name, then apply your custom styles. Make sure to [disable the Shadow DOM](#disable-shadow-dom) first.
```css theme={null}
/* Example: Style selected checkboxes in assign dropdown */
/* Inspect the DOM to find the exact class name */
.velt-assign-dropdown-checkbox--selected {
background-color: #625DF5;
border-color: #625DF5;
}
/* Composer attachment classes */
.velt-composer-attachment-container { /* Container for attachment display */ }
.velt-composer-attachment--loading { /* Applied during attachment upload/processing */ }
.velt-composer-attachment--edit-mode { /* Applied when viewing attachments in edit mode */ }
/* Assign-to menu classes */
.s-comment-dialog-assign-to-menu { /* Assign-to menu in dialog context */ }
.velt-thread-card--assign-to-menu { /* Assign-to menu in thread card context */ }
```
```css theme={null}
/* Example: Style selected checkboxes in assign dropdown */
/* Inspect the DOM to find the exact class name */
.velt-assign-dropdown-checkbox--selected {
background-color: #625DF5;
border-color: #625DF5;
}
/* Composer attachment classes */
.velt-composer-attachment-container { /* Container for attachment display */ }
.velt-composer-attachment--loading { /* Applied during attachment upload/processing */ }
.velt-composer-attachment--edit-mode { /* Applied when viewing attachments in edit mode */ }
/* Assign-to menu classes */
.s-comment-dialog-assign-to-menu { /* Assign-to menu in dialog context */ }
.velt-thread-card--assign-to-menu { /* Assign-to menu in thread card context */ }
```
#### Disable Shadow DOM
* By default, Velt components use Shadow DOM to isolate their styles from your application's CSS. This ensures that styles don't conflict with each other.
* If you want your application's CSS to affect the styling of Velt components, you can disable the Shadow DOM.
* Most components accept a `shadowDom` prop (`shadow-dom="false"` for non-React).
* Some components that are not directly injected by you like dialogs and pins use specific props (e.g. `dialogShadowDom`, `pinShadowDom`).
* Check each component's UI customization documentation for its shadow DOM props.
```jsx theme={null}
```
```html theme={null}
```
#### Disable Global Styles
When implementing custom themes, you can disable Velt's default global CSS styles by setting `globalStyles: false` in the SDK configuration. This prevents conflicts with your custom styling.
```jsx theme={null}
// Using VeltProvider
{/* Your app content */}
// Using API method
const client = useVeltClient();
client.initConfig('API_KEY', {
globalStyles: false
});
```
```html theme={null}
```
### Conditional Classes
Apply classes conditionally based available [template variables](/ui-customization/template-variables).
**Syntax:**
```jsx theme={null}
```
```html theme={null}
```
`: 'class-name': {velt-template-variable} 'value'`
* **class-name:** The class name to apply. This should be within single quotes `'`.
* **template-variable:** The template variable to evaluate. This should be within curly braces `{}`.
* **operator:** The operator to use for the comparison. This should be one of the following: `===`, `!==`, `>`, `>=`, `<`, `<=`. You can also use `&&` and `||` to combine multiple conditions.
* **value:** The value to compare the template variable to. This should be within single quotes `'`.
**Example:**
```jsx theme={null}
```
```html theme={null}
```
# Template Variables
Source: https://docs.velt.dev/ui-customization/template-variables
Template variables allow you to:
* Display dynamic data within Velt components.
* Use the dynamic data to apply conditional templates or CSS classes.
There are two main ways to use them:
1. Using built-in Velt data
2. Injecting your own application data
## 1. Using Built-in Velt Data
Velt provides access to various data objects that you can display in your components using the `VeltData` component.
### Basic Usage
```jsx theme={null}
// Display user name
// Access nested properties
```
```jsx theme={null}
// Display user name
// Access nested properties
```
### Available Data Objects
#### Global Variables
These are available across all Velt components:
| Variable | Description | Data Fields |
| ------------------------------ | ---------------------------- | ------------------------------------------------------------------------------ |
| `user` | Current logged-in user | You can find all the fields [here](/api-reference/sdk/models/data-models#user) |
| `unreadCommentAnnotationCount` | Number of unread annotations | - |
| `unreadCommentCount` | Total unread comments | - |
#### Context-Specific Variables
These are only available within relevant components they are used in:
| Variable | Available In | Description |
| --------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------- |
| `userContact` | Autocomplete components | You can find all the fields [here](/api-reference/sdk/models/data-models#user) |
| `commentAnnotation` | Comment components | You can find all the fields [here](/api-reference/sdk/models/data-models#commentannotation) |
| `comment` | Comment thread components | You can find all the fields [here](/api-reference/sdk/models/data-models#comment) |
| `notification` | Notification components | You can find all the fields [here](/api-reference/sdk/models/data-models#notification) |
| `filteredCommentAnnotationsCount` | Comments Sidebar | Number of visible comments in sidebar when system filters are applied |
### Example: Building a Custom User Card
```jsx theme={null}
```
```jsx theme={null}
```
## 2. Injecting Your Own Data
* You can inject custom data from your application to use within Velt components.
* This data is available in all Velt Wireframes, Velt If and Velt Data components.
### Setting Custom Data
```jsx theme={null}
// Set custom data
client.setUiState({
projectName: 'Dashboard 2.0',
teamSize: 5,
customFlag: true
});
// Read custom data
client.getUiState().subscribe((data) => {
console.log('Custom Data:', data);
});
```
```js theme={null}
// Set custom data
Velt.setUiState({
projectName: 'Dashboard 2.0',
teamSize: 5,
customFlag: true
});
// Read custom data
Velt.getUiState().subscribe((data) => {
console.log('Custom Data:', data);
});
```
### Using Custom Data in Components
```jsx theme={null}
Team Size:
```
```jsx theme={null}
```
# Advanced Webhooks
Source: https://docs.velt.dev/webhooks/advanced
This is only available to Enterprise customers.
Webhooks are how services notify each other of events.
At their core they are just a POST request to a pre-determined endpoint.
The endpoint can be whatever you want, and you can just add them from the UI.
You normally use one endpoint per service, and that endpoint listens to all of the event types.
For example, if you receive webhooks from Acme Inc., you can structure your URL like: [https://www.example.com/acme/webhooks/](https://www.example.com/acme/webhooks/).
The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15s).
It's also important to disable CSRF protection for this endpoint if the framework you use enables them by default.
Another important aspect of handling webhooks is to verify the signature and timestamp when processing them.
You can learn more about it in the [signature verification section](#verifying-webhook-signatures).
## Endpoints
### Adding an endpoint
In order to start listening to messages, you will need to configure your endpoints.
Adding an endpoint is as simple as providing a URL that you control and selecting the event types that you want to listen to.
### Playground
If your endpoint isn't quite ready to start receiving events, you can press the "with Svix Play" button to have a unique URL generated for you.
You'll be able to view and inspect webhooks sent to your Svix Play URL, making it effortless to get started.
### Testing endpoints
Once you've added an endpoint, you'll want to make sure its working.
The "Testing" tab lets you send test events to your endpoint.
After sending an example event, you can click into the message to view the message payload, all of the message attempts, and whether it succeeded or failed.
## Events
If you don't specify any event types, by default, your endpoint will receive all events, regardless of type.
This can be helpful for getting started and for testing, but we recommend changing this to a subset later on to avoid receiving extraneous messages.
### Payload Schema
1. **Default payload**: [WebhookV2Payload](/api-reference/sdk/models/data-models#webhookv2payload) — common fields shared across all events
* [CommentPayload](/api-reference/sdk/models/data-models#commentpayload) — comment-specific fields
* [HuddlePayload](/api-reference/sdk/models/data-models#huddlepayload) — huddle-specific fields
* [CRDTPayload](/api-reference/sdk/models/data-models#crdtpayload) — CRDT-specific fields
2. **Encoded payload**: If you have enabled payload encoding, you will receive the payload in this format: [WebhookV2PayloadEncoded](/api-reference/sdk/models/data-models#webhookv2payloadencoded)
3. **Encrypted payload**: If you have enabled payload encryption, you will receive the payload in this format: [WebhookV2PayloadEncrypted](/api-reference/sdk/models/data-models#webhookv2payloadencrypted)
If you have configured [notification settings](/async-collaboration/notifications/customize-behavior), per-user notification preferences (`usersOrganizationNotificationsConfig` or `usersDocumentNotificationsConfig`) will be included in the webhook payload.
### Comments
#### Comment Annotations (Threads)
| Event Type | Description |
| --------------------------------------- | -------------------------------------------------------------------------------------------- |
| `comment_annotation.add` | When a new comment thread is created with the first comment. |
| `comment_annotation.assign` | When a comment thread is assigned to a specific user. |
| `comment_annotation.status_change` | When a comment thread's status is updated (e.g., open, in progress, resolved). |
| `comment_annotation.priority_change` | When a comment thread's priority level is modified (e.g., P0, P1, P2, or custom priorities). |
| `comment_annotation.custom_list_change` | When a custom list item is added to or modified on a comment thread. |
| `comment_annotation.subscribe` | When a user subscribes to receive notifications for a comment thread. |
| `comment_annotation.unsubscribe` | When a user unsubscribes from notifications for a comment thread. |
| `comment_annotation.accept` | When a moderator accepts a comment thread. Only available when Moderator Mode is enabled. |
| `comment_annotation.reject` | When a moderator rejects a comment thread. Only available when Moderator Mode is enabled. |
| `comment_annotation.approve` | When a moderator approves a comment thread. Only available when Moderator Mode is enabled. |
#### Comments (Messages)
| Event Type | Description |
| ------------------------- | ----------------------------------------------------------------- |
| `comment.add` | When a new comment is added to a comment thread. |
| `comment.update` | When an existing comment's content or metadata is modified. |
| `comment.delete` | When an existing comment is permanently removed from a thread. |
| `comment.reaction_add` | When a user adds an emoji reaction to a specific comment. |
| `comment.reaction_delete` | When a user removes their emoji reaction from a specific comment. |
#### Sample Payload
You will find the sample schema and payload in the advanced webhooks section of the console.
```json expandable lines theme={null}
{
"actionType": "add",
"data": {
"actionUser": {
"clientUserName": "actorUser",
"email": "actor@example.com",
"initial": "A",
"name": "Actor User",
"organizationId": "org123",
"userId": "user123"
},
"commentAnnotation": {
"annotationId": "anno-xyz",
"annotationIndex": 1,
"comments": [
{
"commentHtml": "Hello world
",
"commentId": 101,
"commentText": "Hello world",
"createdAt": 1610000000000,
"from": {
"clientUserName": "commenter",
"email": "commenter@example.com",
"initial": "C",
"name": "Commenter One",
"organizationId": "org123",
"userId": "user456"
},
"isCommentTextAvailable": true,
"isDraft": false,
"lastUpdated": "2025-05-28T12:00:00.000Z",
"status": "added",
"type": "text"
}
],
"createdAt": 1610000000000,
"from": {
"clientUserName": "commenter",
"email": "commenter@example.com",
"initial": "C",
"name": "Commenter One",
"organizationId": "org123",
"userId": "user456"
},
"lastUpdated": 1610000001000,
"location": {
"browser": "chrome",
"browserVersion": "90.0",
"deviceName": "Laptop",
"id": "loc-001",
"locationName": "chrome-1920x1080",
"resolution": "1920x1080"
},
"locationId": 1001,
"metadata": {
"apiKey": "dummyApiKey"
},
"pageInfo": {
"baseUrl": "https://example.com",
"commentUrl": "https://example.com/page#comment-anno",
"deviceInfo": {
"browserName": "Chrome",
"browserVersion": "90.0",
"deviceType": "Desktop",
"orientation": "landscape",
"osName": "Windows",
"osVersion": "10",
"screenHeight": 1080,
"screenWidth": 1920,
"userAgent": "Mozilla/5.0"
},
"path": "/page",
"queryParams": "?foo=bar",
"screenWidth": 1920,
"title": "Example Page",
"url": "https://example.com/page?foo=bar"
},
"resolvedByUserId": "user123",
"status": {
"color": "#000000",
"id": "RESOLVED",
"lightColor": "#CCCCCC",
"name": "Resolved",
"svg": " ",
"type": "terminal"
},
"subscribedUsers": {},
"type": "comment"
},
"metadata": {
"apiKey": "metaKey",
"buildCreatedBy": "builderService",
"buildId": "build-001",
"buildName": "example-build",
"document": {
"buildCreatedBy": "builderService",
"buildId": "build-001",
"buildName": "example-build",
"documentId": "doc-001",
"documentName": "Example Doc",
"isBaseline": false,
"isDemoProject": false,
"latestBuildStatus": "Pending",
"projectId": "proj-123",
"projectName": "Example Project",
"projectUserId": 12345
},
"documentMetadata": {
"apiKey": "metaKey",
"buildCreatedBy": "builderService",
"buildId": "build-001",
"buildName": "example-build",
"documentId": "doc-001",
"documentName": "Example Doc",
"isBaseline": false,
"isDemoProject": false,
"latestBuildStatus": "Pending",
"organizationId": "org123",
"pageInfo": {
"baseUrl": "https://example.com",
"path": "/doc",
"queryParams": "?x=1",
"title": "Example Doc",
"url": "https://example.com/doc?x=1"
},
"projectId": "proj-123",
"projectName": "Example Project",
"projectUserId": 12345
},
"isBaseline": false,
"isDemoProject": false,
"latestBuildStatus": "Pending",
"organization": {
"organizationId": "org123"
},
"organizationMetadata": {
"apiKey": "metaKey",
"organizationId": "org123"
},
"pageInfo": {
"baseUrl": "https://example.com",
"path": "/doc",
"queryParams": "?x=1",
"title": "Example Doc",
"url": "https://example.com/doc?x=1"
},
"projectId": "proj-123",
"projectName": "Example Project",
"projectUserId": 12345
},
"targetComment": {
"commentHtml": "Target comment
",
"commentId": 202,
"commentText": "Target comment",
"createdAt": 1610000020000,
"from": {
"clientUserName": "actorUser",
"email": "actor@example.com",
"initial": "A",
"name": "Actor User",
"organizationId": "org123",
"userId": "user123"
},
"isCommentTextAvailable": true,
"isDraft": false,
"lastUpdated": "2025-05-29T12:00:00.000Z",
"status": "added",
"type": "text"
}
},
"event": "comment.add",
"platform": "sdk",
"source": "comment",
"webhookId": "dummyWebhookId",
"usersOrganizationNotificationsConfig": {
"user0": {
"inbox": "ALL",
"email": "MINE"
},
"user1": {
"inbox": "NONE",
"email": "ALL"
}
}
}
```
### Huddle
| Event Type | Description |
| --------------- | ----------------------------- |
| `huddle.create` | When a user creates a Huddle. |
| `huddle.join` | When a user joins a Huddle. |
### CRDT
Learn how to configure CRDT webhooks in the [CRDT Core Setup documentation](/realtime-collaboration/crdt/setup/core#webhooks).
| Event Type | Description |
| ------------------ | ------------------------------------------------------------------------------------ |
| `crdt.update_data` | When CRDT data is updated. Webhooks are enabled by default with a 5-second debounce. |
#### Sample Payload
You will find the sample schema and payload in the advanced webhooks section of the console.
```json expandable lines theme={null}
{
"actionType": "updateData",
"data": {
"actionUser": {
"clientUserName": "Michael Scott",
"color": "#ffcb00",
"email": "michael.scott@dundermifflin.com",
"initial": "M",
"isAdmin": false,
"name": "Michael Scott",
"organizationId": "crdt-array-demo-org1",
"type": "signedIn",
"userId": "michael"
},
"crdtData": {
"data": [
{
"completed": false,
"id": "seed-1",
"text": "Welcome Todo - Edit me!"
},
{
"completed": false,
"id": "bmg6tpj7we",
"text": "Todo1"
},
{
"completed": false,
"id": "dt0vhs2t3os",
"text": "Todo2"
}
],
"id": "crdt-array-demo-todos-1",
"lastUpdate": "2026-03-17T06:23:24.514Z",
"lastUpdatedBy": "michael",
"sessionId": "tvLupvP0L2jiztlba4P0"
},
"metadata": {
"apiKey": "VKabul7W2DJhLu1BiMaj",
"document": {
"documentId": "crdt-array-demo-doc-1",
"documentName": "CRDT Array Demo"
},
"organization": {
"organizationId": "crdt-array-demo-org1"
},
"pageInfo": {
"baseUrl": "http://localhost:5225",
"path": "/",
"queryParams": "",
"title": "CRDT Array Demo",
"url": "http://localhost:5225/"
}
}
},
"event": "crdt.update_data",
"platform": "sdk",
"source": "crdt",
"webhookId": "-OnuSfG_ffGIwNkodE2a"
}
```
## Rate Limits
The rate limit is defined as a limit for the number of messages per second to send to the endpoint. After the limit is reached, requests will get throttled so to keep a consistent rate under the limit.
Due to the nature of distributed systems the actual rate of messages can sometimes be slightly above the enforce rate limit. So for example, if you set a rate limit of 1,000 per seconds, an endpoint may potentially get messages at a rate of 1,050 or even higher.
You can set the rate limit on each of the endpoints you create.
## Retries
We attempt to deliver each webhook message based on a retry schedule with exponential backoff.
Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:
* Immediately
* 5 seconds
* 5 minutes
* 30 minutes
* 2 hours
* 5 hours
* 10 hours
* 10 hours (in addition to the previous)
For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.
If an endpoint is removed or disabled delivery attempts to the endpoint will be disabled as well.
### Indicating successful delivery
The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15s). Any other status code, including 3xx redirects are treated as failures.
### Failed delivery handling
After the conclusion of the above attempts the message will be marked as Failed for this endpoint, and you will get a webhook of type `message.attempt.exhausted` notifying you of this error.
### Manual retries
Use the console to manually retry each message at any time, or automatically retry ("Recover") all failed messages starting from a given date.
## Transformations
Transformations are a powerful feature that allows the modification of the received webhook data in-flight. When you enable Transformations, you can write JavaScript code on the endpoints that can change a webhook's HTTP method, target URL, and body payload.
You can enable Transformations in the Advanced tab of the endpoint configuration.
### How to write a Transformation
We expect a Transformation to declare a function named `handler`. We will pass a `WebhookObject` to this function as its only argument, and expect the function to always return a `WebhookObject`.
`WebhookObject` is a JSON object containing 4 properties:
* `method`, a string representing the HTTP method the webhook will be sent with. It is always `POST` by default, and its only valid values are `POST` or `PUT`
* `url`, a string representing the endpoint's URL. It can be changed to any valid URL.
* `payload`, which contains the webhook's payload as a JSON object. It can be changed as needed.
* `cancel`, a Boolean which controls whether or not to cancel the dispatch of a webhook. This value defaults to `false`. Note that canceled messages appear as successful dispatches.
The Transformation will only work if the `handler` function returns the modified `WebhookObject`.
### Example
Suppose that sometimes, you want to redirect webhooks to a custom URL instead of the endpoint's defined URL. You only want to do this redirect if a custom URL is present in the webhook payload. You can write a transformation like this:
```javascript theme={null}
function handler(webhook) {
if (webhook.payload.customUrl) {
webhook.url = webhook.payload.customUrl;
}
return webhook;
}
```
Great, the webhook is redirected to the custom URL if the customUrl property exists on the payload. Otherwise, it is sent to the endpoint's defined URL.
## Security
### Verifying webhook signatures
Webhook signatures let you verify that webhook messages are actually sent by us and not a malicious actor.
Each webhook call includes three headers with additional information that are used for verification:
* `webhook-id`: the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure).
* `webhook-timestamp`: timestamp in [seconds since epoch](https://en.wikipedia.org/wiki/Unix_time).
* `webhook-signature`: the [Base64](https://en.wikipedia.org/wiki/Base64) encoded list of signatures (space delimited).
#### Constructing the signed content
The content to sign is composed by concatenating the id, timestamp and payload, separated by the full-stop character (`.`). In code, it will look something like:
```javascript theme={null}
const signedContent = `${webhook_id}.${webhook_timestamp}.${body}`;
```
Where `body` is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should *not* change the body in any way before verifying.
#### Determining the expected signature
We use an [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) with [SHA-256](https://en.wikipedia.org/wiki/SHA-2) to sign its webhooks.
So to calculate the expected signature, you should HMAC the `signed_content` from above using the base64 portion of your signing secret (this is the part after the `whsec_` prefix) as the key.
For example, given the secret `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw` you will want to use `MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`.
For example, this is how you can calculate the signature in Node.js:
```javascript theme={null}
const crypto = require('crypto');
const signedContent = `${webhook_id}.${webhook_timestamp}.${body}`;
const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";
// Need to base64 decode the secret
const secretBytes = Buffer.from(secret.split('_')[1], "base64");
const signature = crypto
.createHmac('sha256', secretBytes)
.update(signedContent)
.digest('base64');
console.log(signature);
```
This generated signature should match one of the ones sent in the `webhook-signature` header.
The `webhook-signature` header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one. Though there could be any number of signatures. For example:
```
v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=
```
Make sure to remove the version prefix and delimiter (e.g. `v1,`) before verifying the signature.
Please note that to compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.
#### Verify timestamp
As mentioned above, we also send the timestamp of the attempt in the `webhook-timestamp` header. You should compare this timestamp against your system timestamp and make sure it's within your tolerance in order to prevent timestamp attacks.
#### Example signatures
Here is an example you can use to verify you implemented everything correctly. Please note that this may fail verification due to the timestamp being old.
```javascript theme={null}
secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl';
payload = '{"event_type":"ping","data":{"success":true}}';
msg_id = 'msg_loFOjxBNrRLzqYUf';
timestamp = '1731705121';
// Would generate the following signature:
signature = 'v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0=';
```
Additionally, you can use the [webhook simulation tool](https://www.standardwebhooks.com/simulate) to generate as many examples as you need.
### Additional Authentication
We sign all webhooks in order to ensure the security and authenticity of all of the webhooks being sent.
This security mechanism is already sufficient (and better) than other methods such as [HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication), and using an authentication token. However, some systems and IT departments have varying requirements for any HTTP request hitting their services (including webhooks), so we've built in support for these additional authentication modes.
#### HTTP Basic Authentication
HTTP Basic Authentication (Basic Auth), is a common way of sending a server a pair of username and password, or more often a username and auth token. While there are different ways of passing these credentials, the simplest and most common way is by including it as part of the URL.
You can add it to the URL by prefixing the URL with the username and password (or token) and the @ symbol as such:
```
https://USERNAME:PASSWORD@example.com/webhook/
```
#### Header based authentication
Some services require specific headers to be passed in order to be processed by their load balancers or application servers. These services often require a specific authentication token passed in the `Authorization` header, but sometimes there could also be different headers and values.
You can set custom headers using the Advanced tab in the endpoint configuration.
#### Firewalls (IP blocking)
Many organizations have strict firewall rules for which IPs are allowed to send traffic to their systems. While this is not a very strong security mechanism on its own, it's often useful when used in conjunction with other methods (such as webhook signatures).
We only send webhooks requests from a specific set of IPs as detailed below:
#### Static Source IP Addresses
In case your webhook receiving endpoint is behind a firewall or NAT, you may need to allow traffic from these static IP addresses.
```plaintext theme={null}
44.228.126.217
50.112.21.217
52.24.126.164
54.148.139.208
2600:1f24:64:8000::/56
```
### Payload Encoding
* Enable Base64 encoding for webhook payloads (disabled by default).
* Addresses issues with payloads containing HTML tags that may fail due to strict endpoint policies.
* If enabled, ensure your server can decode Base64 encoded payloads.
* Example of decoding a Base64 encoded payload:
```js theme={null}
const encodedData = "eyJ0ZXN0IjoxLCJ0ZXN0MSI6IjxkaXY+PC9kaXY+In0="
const decodedData = Buffer.from(encodedData, 'base64').toString('utf-8');
console.log(JSON.parse(decodedData));
```
### Payload Encryption
* Enable payload encryption for enhanced security (disabled by default).
* Configure this option in the [Velt Console](https://console.velt.dev/dashboard/config/webhook).
* Encryption details:
* Payload encryption: AES-256-CBC
* Key encryption: RSA with PKCS1 OAEP padding and SHA-256 hash
* Public key format:
* Provide only the base64-encoded key string, without PEM headers/footers
* Recommended key size: 2048 bits
* Example of setting up decryption for Node.js:
```js theme={null}
{
"encryptedData": "1rtsa9UVvXzkP+u0ax2TOlz6xKcwKXhmtHyQF1I4II8X4n9uYb944Q/6AfUNFc2zQj9+AWJIV1Gtoo0j+j5VI8qS4kCVnP4In6v0I3wVECldgZsNAwwD4wKp85OJZUJL4scQmJJK+XXmMNGOW094BcIIa6zKRqYKja5RBm5zEj3k1qsP3WZkUXpggJ4FNuHkWX2nkoDLP5Rby6CY186TEeBIxY+aKS6FyWmOiDDC6ZfuY++BFNJbksNvsbBogDqHB2qa30nK9oEcOKSsXdU4AYof/mPOG01fK2diK3vyk4qcL83mJ0cXm7+SbM+FBFeJpdR+A7iIez1XrdnGlAqppnSfDoNBv2WZ/lRyZJWOyW7QHySMNTn746+JDr8oltIBDVUx5c2m8A/YeQ6E3wWEjjRZcfz3GNSzpEx+jqeNxS0StN7BUXUyHt3786EaXiUtjb2OtrP56mlidXytdHhPZPOy7stRwHnwgXfm5aLsS2yJSs3gSSUubL+ka4fhaJsqxtgXQATSh0RtNXSmAbx930DKn2DipbP23fJRduju/GP1nHnKuy8bOuyB5Du//RrysvKVC4+lMd4mVIc7cSXe25qcPjJFZGpJtJdkNwOZoWCmxMSdR32HBgo7KWJeOWqnWyuLdjQOaxol+JtTu8lopeQk7qfncEXMLcT7YRVQ4t1LZ5T9o4pZEtaOg1LwyX58VQS1OHvgBFWlEPxLfdS1r4c1YzMXLNA4sfYEp06Z11IlEFVCtWobK5//tLc+sIpwfMzdJ3VtVl9Z2XB9kASlnHf88eOdtzvn5A0CRhVBY/v855CttAy/WlPINtXxXSxm9oVMjrBFueWAZ3LQiXDl25to62L5i0NR93zEBKj1BG8egy3F27o8s5kcvrwpc3NGrmDe7x3S11noDAFsxZRWpHnRIapHcsrLWOjWVEumvUxlApKGKL3Ax80XBoN+aTNG4SXGq3dRHSneIs/MNSb0BGWoOD5U5ow58R1tvpzJHtLLnmesL1Vhr23Cug8KHU2q7+e8AnGGPTJIRKfVXjocMDclhDAk5/nuvtUTYG/hRZEQ1yCx3T7H08I6GvyOv4ErtKr+r883hXSYzf1K9eqk7de5mnmxwSEiAh0zagvZ+lMYhWpayeo+xHvtoyzfTsLNyXKc6AYZxfoIVK6UuBfkDnXiAh+NuJDa3wKwig13gQX8GmdJXeSSatI6uuXI1IU5xKIXysaHeAOaHfni+cfDgvUZTtVbWc1qDcNOVEUSl9KsjOUUgdzvST1tJ1ezMNZFbhlrPB3t5y0XvM9QQh1GyyeABxHl8nH/Icrp2Shf5vBntNbRZ3PlzK7nVtgTxXaKhZnGobwY7uruPpahNfkEi83JvOOnHeHBMXrVMAr8GHDRi8099wzvJRHYcb2p6eWocQsDV1X6tcTLuxj3EHGwykWREkkTDQ5C/F40n97PP0U2cxSGJIMePUwgAYw5OFo0dJMsU1HvXjm+2JoO8DkdwPl3Bc9F22trvsA3QecUCKQDGMTuFrFxtlubtJYtVl7w3pBST0SCKx3G2QiycRz0FMWv2FJpazQl6jE4xEqeKf7fiUn/QIo4Levk745LPhfr2tzlXbkdZ2q9TtmSAs5hjpK7ndswbIbvV8Ju5V8mDJXSR0y0NKG2C/8/vTB0xfqYtW/Bv3cXj6do9UQzP6fOFC4SGvYh/l8yohJmCTFq0tETqvZr9Atw9ZOz2cIBFx76wlS/eR9iB/JZ3DGM+2THC6Mjv70ipWX32UW7620Bb5KONm3Vw0eeIHckUn6QaHGfFL/URT6mr7YCJhG5lZynWYZcLv/ffWuFcSmO9p0xCrwqqPEjdaaGs52mqmA4Ikt9MulKAEp6p65V1vxt7Tdy6m9UVjzbEy1zFuU9iOHBAAaj6A8Mj1EEUe6sNx3fLHnC2c0+2Zf3eUxMZPm5dQZPOUXLI28yoCliBIhTYTSh7ATULDDvcnNMs/ziuG7WT/U1wuIHkT5kEE73tnG1EZY4RDODbQobmpBegcuUEh64HEGS7+aK/KPYWxFxWW5oVd0Dc7kvpariXqEhlNdDY65b2T8uBw8bI/HrfvT8d0EnsPz26B1xKZYqyusWnlR+10KdYzPNoupx8vWk74PW8zI5qlcV497SPtvn12a3wvZ8adJzMuP4hsBoKHG/M2nf0lOMbo1gcbHbT0FqcHE3mixY3lU+UnNC5jpmNCs1tK8yqeQdVtHE3YM4Y5SsnBTJddUWVpUxZ6rlU+H2NW/uGcDLBs3HmERTn1l6E1mmqKB2kPA/+Y/YbILXNojbkgRE/3lki5kX4+pjHDxF/mWEEeXpjIl4yKG97mVS2J0dGoJ5CqLv6/CdHhtwu35UydBVDVGHywufVLwPgEiDA9RklM/bQw3ojdlTrn6+irDcz8/Tj7KmK2votLaN6yIEM8Ex2htyBlyX/47eEsh63nSNwSx+uPcTxjH9N5cJpWzJ2KcBMIqZsWOTgISBUndgRdoVTFySY2XwbHlDjh8RCLLBsYRhvOK+nvNqEBnrfzz81B/sqDO1whQDTKT3ZcFnZouaVImRGHcOt0sRioq/JGHAHzRjyc/V9Gb/zTlI8QQob5y5k7dfReAy1rGdkeIa3LXSwWGz8hDjEnGsGGIC4evdiefgoJHkhzEywi/QUEOOnqms/0BzexbLP+89qMgGMlEbA9iLAW/BZgsAkxm+NHqGNtz9HDJStpqewElgjMQ+wV3TUGbrmY0O/FyQn/CXyhXjdRC0/5S1tZnzBMyolHF2a5L5EAzGck2MuV7TgLs6LcvGm7kIeq0vmBCkiUB4IBHMhraU7Ba+cC+CW7tDK0Tkanri5KSMXSXamJpU869Jcsk1JLm69ATMl3eIb5rPx5+GbPUrRogEUP3HQeLMQP8jjq6fVwzGPQByF70t0fE+Z23NuCLzhVss0YkMmzcKK8GjKCJ0vnCA0qanxovpDgCOHjgxvy44N+QNWfUynIKVHS9m7FDE3RgKf7rOfSM9vJ7F/KWo7kywi36ajuFbWcON/MTvlpPUhGm5dboiz3vyfpTWkQbd9XX7SPVBWCkvGg+A87R7RSN8bsWbmYm5m2wt3jrkBVSDn5FV3rek6X0GSpTDTWJ9ktmjKtshplXn7fx7XAKtS4hpEMGhZwi/LWvfTsGqOJlqi2FwYPLI7SVunch2VSfssejrfwxJHPqF50wTv6ax28lp7wToqsVunZprdhyY++gds/LAz083dZLM3EYcbHuGVXiNRFxptpiQNjEnyjZX0fc8UF1W2icDt7Gd5Pp2ckaPERLE+tJ+ackMxomH2/HjFB3XRXlDCoKuljtJ2cbw/gVPmHtV7Qw2w6tWaCzYP3g1D47BlrIqBV4RWjcPRjthfcWPnwUSSHwlJ4dLMQ+cJ402ol+HUukAKpkh5lcjME0uaD8KKReD/Ee9r4kubIR7z9JViXjnJJl3Jxr6KtK3abrg8cG8qVFRr5NDhxbfs9NY/zGDvbgt0GMWXRTi4oMrSkDKthZSWjVezDzPk11AMQ1E+SJSoSXgwUl1rbWPg0O29prkQdfdKQmZcaO5oj7+f3kSPsIOE9+Qn43VOxOWWybkCzSvEbzLgmuov5C8EWYeJgh13qDcNSwNdt4PgAqIq+tikKNUo9qeM9/q20an+i20fatPAcvrRes+xxnIBXmlPDCj02THjX4EulV2KE+nNxFnCrNvFKYp2bEAegJ2neqfeefDDDhn+t7OK9/73v3O3qnEwSyBlt+pEyHfLjv3Cm7Ik7JA5NUQ/nsS3JdC8OYy2i1DWSvi1qsP3ixAVCR7qBVdoOF2Lv5y2GWrJ0EvVcGqaPBnUezMGMdozNjreschNJvRlp3D72dGGQgs00GHyHbIQ5wicC5p+PiZ2z1EUBN7DiDy9ShQPKEDJtISiSrSaPkDPKpW7SxmSfDaLOIxEy4daAupV0gj7yTtrkpEvJjRECpa0kuKFP3/eFVVp/nIjWDzFASfDvYiry90dDrvLxO3tosuvMVfhXcOy/zbyeCkObaFgc3OkO4z2r4X4Vwt18BoRAammiEfgCbnhywl/CmLrSwV1qSjUgALh/XUPkqXCkqerNjYTlZw5NdRUKmheUXHYGwo4Z+xPfDtiHk1N5vRgNL9/qXsgt813spju9kDMGQGiXlrOgIyhArHR5p2B4S3FjRQ/lEoP5+5wN+9tBKYrR79sZXNS8CwR0BPrOoY9GQCYFdxrBtyH6KOWg29FVXNodt2Yvot7ktofcen1zwQJOAr0KTyqF9/TIltO+hS7swSzZMjV368SEPYjrtXfnXNWYltOS2zJAWYeqr0XLrL+iHbbOQLC7Rk0mnizmUt9wdefz4MtfXZNcdKR4LPsOqYyIz5ux90XiCbvcNZJaRa2/dzecv/koLQPbKzFPGxKiUOsHAa5SEGgbWFZE4Y9CBFS4nCuEOgUnVz9XtFAEP4dazc2cxjYLVzaG5msOiOY1O5ZygYMeVZfdKaITg7gMPbkL3Lpzo7QBMXcHmT5YAUeNaSbHxvgg45Jn8r7W72EQP9tF7SPKiPvxo91xkB7MA3JOcZXC1qymTUWqjO038wSShK48kE+qgu7V9rjP5fOCDW3+3338eifxqS7Zq6FSO053c5W2c8wFR4iw==",
"encryptedKey": "OzSHFXzrXFC5wDvM5NPRkriY/NaC/USvFUPE+f4NZ30tiD2qb8sJM2XT2K7uNIZ05uDLfsJ6/BbEoYC1SOPXcFJMYqRiYFiI9RWrNgR4EtPWZ84RgrmxGcZZjzSqHzjuls8g++cuqJGRV+ePbTRH+z2OuZZu0vMKZiemaZpHL46Ewi9HUnbRDXvOlKFFHmQm5tayZ7m7Mv5iu4T5R3DPEAHlZnGqtP98ToLxUJUS2Eku/iLHXRmhmZXn55Qt5GYiyss8P5/miPqJCu1QG0CStn5Nsl4KvU+I4QYAOcMFWWUAGofOwPWtt8vPh8Bx+7t7BbayKpA4ZUEWWAjC+zASxg==",
"iv": "SHM0UHU5WXoyTG03TnExWA=="
}
```
```javascript theme={null}
const crypto = require('crypto');
/**
* Decrypts the symmetric key using the provided private key.
* @param {string} encryptedKey - Base64 encoded encrypted symmetric key
* @param {string} privateKey - RSA private key
* @returns {Buffer} Decrypted symmetric key
*/
function decryptSymmetricKey(encryptedKey, privateKey) {
try {
const encryptedSymmetricKey = Buffer.from(encryptedKey, 'base64');
const decryptedSymmetricKey = crypto.privateDecrypt(
{
key: `-----BEGIN RSA PRIVATE KEY-----\n${privateKey}\n-----END RSA PRIVATE KEY-----`,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
},
encryptedSymmetricKey
);
return decryptedSymmetricKey;
} catch (error) {
console.error('Error decrypting symmetric key:', error);
throw new Error('Failed to decrypt symmetric key');
}
}
/**
* Decrypts the webhook data using the provided symmetric key and IV.
* @param {string} encryptedWebhookData - Base64 encoded encrypted webhook data
* @param {Buffer} symmetricKey - Decrypted symmetric key
* @param {string} payloadIv - Base64 encoded initialization vector
* @returns {Object} Decrypted webhook data as a JSON object
*/
function decryptWebhookData(encryptedWebhookData, symmetricKey, payloadIv) {
try {
const iv = Buffer.from(payloadIv, 'base64');
const decipher = crypto.createDecipheriv('aes-256-cbc', symmetricKey, iv);
let decryptedData = decipher.update(encryptedWebhookData, 'base64', 'utf8');
decryptedData += decipher.final('utf8');
return JSON.parse(decryptedData);
} catch (error) {
console.error('Error decrypting webhook data:', error);
throw new Error('Failed to decrypt webhook data');
}
}
// Example usage:
// const decryptedKey = decryptSymmetricKey(encryptedKey, privateKey);
// const decryptedData = decryptWebhookData(encryptedWebhookData, decryptedKey, payloadIv);
```
## Debugging
### Troubleshooting tips
There are some common reasons why your webhook endpoint is failing:
**Not using the raw payload body**
This is the most common issue. When generating the signed content, we use the raw string body of the message payload.
If you convert JSON payloads into strings using methods like stringify, different implementations may produce different string representations of the JSON object, which can lead to discrepancies when verifying the signature. It's crucial to verify the payload exactly as it was sent, byte-for-byte or string-for-string, to ensure accurate verification.
**Missing the secret key**
From time to time we see people using the wrong secret key. Remember that keys are unique to endpoints.
**Sending the wrong response codes**
When we receive a response with a 2xx status code, we interpret that as a successful delivery even if you indicate a failure in the response payload. Make sure to use the right response status codes so we know when message are supposed to succeed vs fail.
**Responses timing out**
We will consider any message that fails to send a response within 15 seconds a failed message. If your endpoint is also processing complicated workflows, it may timeout and result in failed messages.
We suggest having your endpoint simply receive the message and add it to a queue to be processed asynchronously so you can respond promptly and avoiding getting timed out.
### Failure Recovery
#### Re-enable a disabled endpoint
If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled. To re-enable a disabled endpoint, go to the webhook dashboard, find the endpoint from the list and select "Enable Endpoint".
#### Recovering/Resending failed messages
If your service has downtime or if your endpoint was misconfigured, you probably want to recover any messages that failed during the downtime.
If you want to replay a single event, you can find the message from the UI and click the options menu next to any of the attempts.
From there, click "resend" to have the same message send to your endpoint again.
If you need to recover from a service outage and want to replay all the events since a given time, you can do so from the Endpoint page. On an endpoint's details page, click "Options > Recover Failed Messages".
From there, you can choose a time window to recover from.
For a more granular recovery - for example, if you know the exact timestamp that you want to recover from - you can click the options menu on any message from the endpoint page.
From there, you can click "Replay..." and choose to "Replay all failed messages since this time."
### Logs
You can see all of the activity logs for your endpoint in the "Logs" tab.
# Basic Webhooks
Source: https://docs.velt.dev/webhooks/basic
Set up a webhook endpoint to receive real-time notifications for the following Velt events:
* [Comments](#comments-events)
* [Huddle](#huddle-events)
* [CRDT](#crdt-events)
## Setting up a Webhook
To enable Webhooks go to the Configurations -> Webhook Service in the Velt Console, or [click here](https://console.velt.dev/dashboard/config/webhook)
### **Webhook Auth Token**
* Optional security feature to authenticate webhook requests.
* Set a unique auth token in your Velt console's webhook settings.
* We add this token to the Authorization header of each request as `Basic YOUR_AUTH_TOKEN`.
* Helps you verify that requests are from Velt, not from unauthorized sources.
### **Endpoint URL**
* This is the endpoint that we will send the webhook data to. This is usually hosted on your server.
### **Payload Encoding**
* Enable Base64 encoding for webhook payloads (disabled by default).
* Addresses issues with payloads containing HTML tags that may fail due to strict endpoint policies.
* If enabled, ensure your server can decode Base64 encoded payloads.
* Example of decoding a Base64 encoded payload:
```js theme={null}
const encodedData = "eyJ0ZXN0IjoxLCJ0ZXN0MSI6IjxkaXY+PC9kaXY+In0="
const decodedData = Buffer.from(encodedData, 'base64').toString('utf-8');
console.log(JSON.parse(decodedData));
```
### **Payload Encryption**
* Enable payload encryption for enhanced security (disabled by default).
* Configure this option in the [Velt Console](https://console.velt.dev/dashboard/config/webhook).
* Encryption details:
* Payload encryption: AES-256-CBC
* Key encryption: RSA with PKCS1 OAEP padding and SHA-256 hash
* Public key format:
* Provide only the base64-encoded key string, without PEM headers/footers
* Recommended key size: 2048 bits
* Example of setting up decryption for Node.js:
```js theme={null}
{
"encryptedData": "1rtsa9UVvXzkP+u0ax2TOlz6xKcwKXhmtHyQF1I4II8X4n9uYb944Q/6AfUNFc2zQj9+AWJIV1Gtoo0j+j5VI8qS4kCVnP4In6v0I3wVECldgZsNAwwD4wKp85OJZUJL4scQmJJK+XXmMNGOW094BcIIa6zKRqYKja5RBm5zEj3k1qsP3WZkUXpggJ4FNuHkWX2nkoDLP5Rby6CY186TEeBIxY+aKS6FyWmOiDDC6ZfuY++BFNJbksNvsbBogDqHB2qa30nK9oEcOKSsXdU4AYof/mPOG01fK2diK3vyk4qcL83mJ0cXm7+SbM+FBFeJpdR+A7iIez1XrdnGlAqppnSfDoNBv2WZ/lRyZJWOyW7QHySMNTn746+JDr8oltIBDVUx5c2m8A/YeQ6E3wWEjjRZcfz3GNSzpEx+jqeNxS0StN7BUXUyHt3786EaXiUtjb2OtrP56mlidXytdHhPZPOy7stRwHnwgXfm5aLsS2yJSs3gSSUubL+ka4fhaJsqxtgXQATSh0RtNXSmAbx930DKn2DipbP23fJRduju/GP1nHnKuy8bOuyB5Du//RrysvKVC4+lMd4mVIc7cSXe25qcPjJFZGpJtJdkNwOZoWCmxMSdR32HBgo7KWJeOWqnWyuLdjQOaxol+JtTu8lopeQk7qfncEXMLcT7YRVQ4t1LZ5T9o4pZEtaOg1LwyX58VQS1OHvgBFWlEPxLfdS1r4c1YzMXLNA4sfYEp06Z11IlEFVCtWobK5//tLc+sIpwfMzdJ3VtVl9Z2XB9kASlnHf88eOdtzvn5A0CRhVBY/v855CttAy/WlPINtXxXSxm9oVMjrBFueWAZ3LQiXDl25to62L5i0NR93zEBKj1BG8egy3F27o8s5kcvrwpc3NGrmDe7x3S11noDAFsxZRWpHnRIapHcsrLWOjWVEumvUxlApKGKL3Ax80XBoN+aTNG4SXGq3dRHSneIs/MNSb0BGWoOD5U5ow58R1tvpzJHtLLnmesL1Vhr23Cug8KHU2q7+e8AnGGPTJIRKfVXjocMDclhDAk5/nuvtUTYG/hRZEQ1yCx3T7H08I6GvyOv4ErtKr+r883hXSYzf1K9eqk7de5mnmxwSEiAh0zagvZ+lMYhWpayeo+xHvtoyzfTsLNyXKc6AYZxfoIVK6UuBfkDnXiAh+NuJDa3wKwig13gQX8GmdJXeSSatI6uuXI1IU5xKIXysaHeAOaHfni+cfDgvUZTtVbWc1qDcNOVEUSl9KsjOUUgdzvST1tJ1ezMNZFbhlrPB3t5y0XvM9QQh1GyyeABxHl8nH/Icrp2Shf5vBntNbRZ3PlzK7nVtgTxXaKhZnGobwY7uruPpahNfkEi83JvOOnHeHBMXrVMAr8GHDRi8099wzvJRHYcb2p6eWocQsDV1X6tcTLuxj3EHGwykWREkkTDQ5C/F40n97PP0U2cxSGJIMePUwgAYw5OFo0dJMsU1HvXjm+2JoO8DkdwPl3Bc9F22trvsA3QecUCKQDGMTuFrFxtlubtJYtVl7w3pBST0SCKx3G2QiycRz0FMWv2FJpazQl6jE4xEqeKf7fiUn/QIo4Levk745LPhfr2tzlXbkdZ2q9TtmSAs5hjpK7ndswbIbvV8Ju5V8mDJXSR0y0NKG2C/8/vTB0xfqYtW/Bv3cXj6do9UQzP6fOFC4SGvYh/l8yohJmCTFq0tETqvZr9Atw9ZOz2cIBFx76wlS/eR9iB/JZ3DGM+2THC6Mjv70ipWX32UW7620Bb5KONm3Vw0eeIHckUn6QaHGfFL/URT6mr7YCJhG5lZynWYZcLv/ffWuFcSmO9p0xCrwqqPEjdaaGs52mqmA4Ikt9MulKAEp6p65V1vxt7Tdy6m9UVjzbEy1zFuU9iOHBAAaj6A8Mj1EEUe6sNx3fLHnC2c0+2Zf3eUxMZPm5dQZPOUXLI28yoCliBIhTYTSh7ATULDDvcnNMs/ziuG7WT/U1wuIHkT5kEE73tnG1EZY4RDODbQobmpBegcuUEh64HEGS7+aK/KPYWxFxWW5oVd0Dc7kvpariXqEhlNdDY65b2T8uBw8bI/HrfvT8d0EnsPz26B1xKZYqyusWnlR+10KdYzPNoupx8vWk74PW8zI5qlcV497SPtvn12a3wvZ8adJzMuP4hsBoKHG/M2nf0lOMbo1gcbHbT0FqcHE3mixY3lU+UnNC5jpmNCs1tK8yqeQdVtHE3YM4Y5SsnBTJddUWVpUxZ6rlU+H2NW/uGcDLBs3HmERTn1l6E1mmqKB2kPA/+Y/YbILXNojbkgRE/3lki5kX4+pjHDxF/mWEEeXpjIl4yKG97mVS2J0dGoJ5CqLv6/CdHhtwu35UydBVDVGHywufVLwPgEiDA9RklM/bQw3ojdlTrn6+irDcz8/Tj7KmK2votLaN6yIEM8Ex2htyBlyX/47eEsh63nSNwSx+uPcTxjH9N5cJpWzJ2KcBMIqZsWOTgISBUndgRdoVTFySY2XwbHlDjh8RCLLBsYRhvOK+nvNqEBnrfzz81B/sqDO1whQDTKT3ZcFnZouaVImRGHcOt0sRioq/JGHAHzRjyc/V9Gb/zTlI8QQob5y5k7dfReAy1rGdkeIa3LXSwWGz8hDjEnGsGGIC4evdiefgoJHkhzEywi/QUEOOnqms/0BzexbLP+89qMgGMlEbA9iLAW/BZgsAkxm+NHqGNtz9HDJStpqewElgjMQ+wV3TUGbrmY0O/FyQn/CXyhXjdRC0/5S1tZnzBMyolHF2a5L5EAzGck2MuV7TgLs6LcvGm7kIeq0vmBCkiUB4IBHMhraU7Ba+cC+CW7tDK0Tkanri5KSMXSXamJpU869Jcsk1JLm69ATMl3eIb5rPx5+GbPUrRogEUP3HQeLMQP8jjq6fVwzGPQByF70t0fE+Z23NuCLzhVss0YkMmzcKK8GjKCJ0vnCA0qanxovpDgCOHjgxvy44N+QNWfUynIKVHS9m7FDE3RgKf7rOfSM9vJ7F/KWo7kywi36ajuFbWcON/MTvlpPUhGm5dboiz3vyfpTWkQbd9XX7SPVBWCkvGg+A87R7RSN8bsWbmYm5m2wt3jrkBVSDn5FV3rek6X0GSpTDTWJ9ktmjKtshplXn7fx7XAKtS4hpEMGhZwi/LWvfTsGqOJlqi2FwYPLI7SVunch2VSfssejrfwxJHPqF50wTv6ax28lp7wToqsVunZprdhyY++gds/LAz083dZLM3EYcbHuGVXiNRFxptpiQNjEnyjZX0fc8UF1W2icDt7Gd5Pp2ckaPERLE+tJ+ackMxomH2/HjFB3XRXlDCoKuljtJ2cbw/gVPmHtV7Qw2w6tWaCzYP3g1D47BlrIqBV4RWjcPRjthfcWPnwUSSHwlJ4dLMQ+cJ402ol+HUukAKpkh5lcjME0uaD8KKReD/Ee9r4kubIR7z9JViXjnJJl3Jxr6KtK3abrg8cG8qVFRr5NDhxbfs9NY/zGDvbgt0GMWXRTi4oMrSkDKthZSWjVezDzPk11AMQ1E+SJSoSXgwUl1rbWPg0O29prkQdfdKQmZcaO5oj7+f3kSPsIOE9+Qn43VOxOWWybkCzSvEbzLgmuov5C8EWYeJgh13qDcNSwNdt4PgAqIq+tikKNUo9qeM9/q20an+i20fatPAcvrRes+xxnIBXmlPDCj02THjX4EulV2KE+nNxFnCrNvFKYp2bEAegJ2neqfeefDDDhn+t7OK9/73v3O3qnEwSyBlt+pEyHfLjv3Cm7Ik7JA5NUQ/nsS3JdC8OYy2i1DWSvi1qsP3ixAVCR7qBVdoOF2Lv5y2GWrJ0EvVcGqaPBnUezMGMdozNjreschNJvRlp3D72dGGQgs00GHyHbIQ5wicC5p+PiZ2z1EUBN7DiDy9ShQPKEDJtISiSrSaPkDPKpW7SxmSfDaLOIxEy4daAupV0gj7yTtrkpEvJjRECpa0kuKFP3/eFVVp/nIjWDzFASfDvYiry90dDrvLxO3tosuvMVfhXcOy/zbyeCkObaFgc3OkO4z2r4X4Vwt18BoRAammiEfgCbnhywl/CmLrSwV1qSjUgALh/XUPkqXCkqerNjYTlZw5NdRUKmheUXHYGwo4Z+xPfDtiHk1N5vRgNL9/qXsgt813spju9kDMGQGiXlrOgIyhArHR5p2B4S3FjRQ/lEoP5+5wN+9tBKYrR79sZXNS8CwR0BPrOoY9GQCYFdxrBtyH6KOWg29FVXNodt2Yvot7ktofcen1zwQJOAr0KTyqF9/TIltO+hS7swSzZMjV368SEPYjrtXfnXNWYltOS2zJAWYeqr0XLrL+iHbbOQLC7Rk0mnizmUt9wdefz4MtfXZNcdKR4LPsOqYyIz5ux90XiCbvcNZJaRa2/dzecv/koLQPbKzFPGxKiUOsHAa5SEGgbWFZE4Y9CBFS4nCuEOgUnVz9XtFAEP4dazc2cxjYLVzaG5msOiOY1O5ZygYMeVZfdKaITg7gMPbkL3Lpzo7QBMXcHmT5YAUeNaSbHxvgg45Jn8r7W72EQP9tF7SPKiPvxo91xkB7MA3JOcZXC1qymTUWqjO038wSShK48kE+qgu7V9rjP5fOCDW3+3338eifxqS7Zq6FSO053c5W2c8wFR4iw==",
"encryptedKey": "OzSHFXzrXFC5wDvM5NPRkriY/NaC/USvFUPE+f4NZ30tiD2qb8sJM2XT2K7uNIZ05uDLfsJ6/BbEoYC1SOPXcFJMYqRiYFiI9RWrNgR4EtPWZ84RgrmxGcZZjzSqHzjuls8g++cuqJGRV+ePbTRH+z2OuZZu0vMKZiemaZpHL46Ewi9HUnbRDXvOlKFFHmQm5tayZ7m7Mv5iu4T5R3DPEAHlZnGqtP98ToLxUJUS2Eku/iLHXRmhmZXn55Qt5GYiyss8P5/miPqJCu1QG0CStn5Nsl4KvU+I4QYAOcMFWWUAGofOwPWtt8vPh8Bx+7t7BbayKpA4ZUEWWAjC+zASxg==",
"iv": "SHM0UHU5WXoyTG03TnExWA=="
}
```
```javascript theme={null}
const crypto = require('crypto');
/**
* Decrypts the symmetric key using the provided private key.
* @param {string} encryptedKey - Base64 encoded encrypted symmetric key
* @param {string} privateKey - RSA private key
* @returns {Buffer} Decrypted symmetric key
*/
function decryptSymmetricKey(encryptedKey, privateKey) {
try {
const encryptedSymmetricKey = Buffer.from(encryptedKey, 'base64');
const decryptedSymmetricKey = crypto.privateDecrypt(
{
key: `-----BEGIN RSA PRIVATE KEY-----\n${privateKey}\n-----END RSA PRIVATE KEY-----`,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
},
encryptedSymmetricKey
);
return decryptedSymmetricKey;
} catch (error) {
console.error('Error decrypting symmetric key:', error);
throw new Error('Failed to decrypt symmetric key');
}
}
/**
* Decrypts the webhook data using the provided symmetric key and IV.
* @param {string} encryptedWebhookData - Base64 encoded encrypted webhook data
* @param {Buffer} symmetricKey - Decrypted symmetric key
* @param {string} payloadIv - Base64 encoded initialization vector
* @returns {Object} Decrypted webhook data as a JSON object
*/
function decryptWebhookData(encryptedWebhookData, symmetricKey, payloadIv) {
try {
const iv = Buffer.from(payloadIv, 'base64');
const decipher = crypto.createDecipheriv('aes-256-cbc', symmetricKey, iv);
let decryptedData = decipher.update(encryptedWebhookData, 'base64', 'utf8');
decryptedData += decipher.final('utf8');
return JSON.parse(decryptedData);
} catch (error) {
console.error('Error decrypting webhook data:', error);
throw new Error('Failed to decrypt webhook data');
}
}
// Example usage:
// const decryptedKey = decryptSymmetricKey(encryptedKey, privateKey);
// const decryptedData = decryptWebhookData(encryptedWebhookData, decryptedKey, payloadIv);
```
### **Payload Schema**
1. **Default payload**: [WebhookV1Payload](/api-reference/sdk/models/data-models#webhookv1payload) — common fields shared across all events
* [Comment Events](/api-reference/sdk/models/data-models#comment-events) — comment-specific fields
* [Huddle Events](/api-reference/sdk/models/data-models#huddle-events) — no additional fields
* [CRDT Events](/api-reference/sdk/models/data-models#crdt-events) — CRDT-specific fields
2. **Encoded payload**: If you have enabled payload encoding, you will receive the payload in this format: [WebhookV1PayloadEncoded](/api-reference/sdk/models/data-models#webhookv1payloadencoded)
3. **Encrypted payload**: If you have enabled payload encryption, you will receive the payload in this format: [WebhookV1PayloadEncrypted](/api-reference/sdk/models/data-models#webhookv1payloadencrypted)
If you have configured [notification settings](/async-collaboration/notifications/customize-behavior), per-user notification preferences (`usersOrganizationNotificationsConfig` or `usersDocumentNotificationsConfig`) will be included in the webhook payload.
## Comments Events
The `Comments` component will emit webhook notifications whenever an `action type` occurs on a comment.
**Payload Schema**: [WebhookV1Payload](/api-reference/sdk/models/data-models#webhookv1payload) + [Comment Events](/api-reference/sdk/models/data-models#comment-events)
### List of Action Types
| Action Type | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------ |
| `newlyAdded` | When the first comment in a thread is added |
| `added` | When a new comment is added. Not used for the first comment in a thread - see `newlyAdded` |
| `updated` | When an existing comment content gets updated |
| `deleted` | When an existing comments gets deleted |
| `approved` | When the comment is approved by the moderator. This is only applicable if you have turned on Moderator Mode. |
| `assigned` | When a comment gets assigned to a user |
| `statusChanged` | When a comment has its status changed (e.g. in progress, resolved, opened) |
| `priorityChanged` | When a comment has its priority changed (e.g. P0, P1, P2 or custom set priorities) |
| `accessModeChanged` | When a comment is changed from private to public or vice-versa |
| `accepted` | When a comment gets accepted by the moderator. This is only applicable if you have turned on Moderator Mode. |
| `rejected` | When a comment gets rejected by the moderator. This is only applicable if you have turned on Moderator Mode. |
| `reactionAdded` | When a reaction is added to a comment. |
| `reactionDeleted` | When a reaction is removed from a comment. |
| `subscribed` | When a user subscribes to a comment annotation using the option in the UI. |
| `unsubscribed` | When a user unsubscribes from a comment annotation using the option in the UI. |
```js Sample Webhook Data expandable lines theme={null}
{
"webhookId": "-Nvmw84XtUUHIsrcKAvI",
"commentAnnotation": {
"annotationId": "-O7Yi14ES3EPayuzQ54J",
"annotationIndex": 20,
"comments": [
{
"commentHtml": "@Jim Halpert can you take a look?",
"commentId": 822004,
"commentText": "@Jim Halpert can you take a look?",
"from": {
"clientOrganizationId": "velt-sample-app",
"color": "#67DBF4",
"email": "peppa.pig@velt.dev",
"initial": "P",
"isAdmin": false,
"name": "Peppa Pig",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Fpeppa_pig.svg?alt=media&token=a9fc83f7-b347-4868-8d52-0e888f0de73a&_gl=1*jzxghx*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMTQ5LjQyLjAuMA..",
"textColor": "#fff",
"type": "signedIn",
"userId": "user0",
"userSnippylyId": "195298461116078"
},
"lastUpdated": "2024-09-24T11:08:49.724Z",
"status": "added",
"taggedUserContacts": [
{
"contact": {
"email": "jim.halpert@dundermifflin.com",
"initial": "J",
"name": "Jim Halpert",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Frosy_rabbit.svg?alt=media&token=4e65a9e3-080d-4416-839d-e761b6b37181&_gl=1*156om5t*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMjUzLjYwLjAuMA..",
"userId": "e54d19af-0e43-4364-819f-0a8b18a280fb",
"userSnippylyId": "5236622904561187"
},
"text": "@Jim Halpert",
"userId": "e54d19af-0e43-4364-819f-0a8b18a280fb"
}
],
"to": [
{
"email": "jim.halpert@dundermifflin.com",
"initial": "J",
"name": "Jim Halpert",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Frosy_rabbit.svg?alt=media&token=4e65a9e3-080d-4416-839d-e761b6b37181&_gl=1*156om5t*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMjUzLjYwLjAuMA..",
"userId": "e54d19af-0e43-4364-819f-0a8b18a280fb",
"userSnippylyId": "5236622904561187"
}
],
"type": "text"
},
{
"commentHtml": "test",
"commentId": 719893,
"commentText": "test",
"from": {
"clientOrganizationId": "velt-sample-app",
"color": "#2c83fc",
"email": "freddy.froglet@velt.dev",
"initial": "F",
"isAdmin": false,
"name": "Freddy Froglet",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Ffreddy_froglet.svg?alt=media&token=e3b2e292-6480-4507-9da3-21a62a1346d4&_gl=1*s4yycs*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMDQzLjcuMC4w",
"textColor": "#fff",
"type": "signedIn",
"userId": "user8",
"userSnippylyId": "4291391246842733"
},
"lastUpdated": "2024-09-24T11:11:58.344Z",
"status": "added",
"type": "text"
}
],
"from": {
"clientOrganizationId": "velt-sample-app",
"color": "#67DBF4",
"email": "peppa.pig@velt.dev",
"initial": "P",
"isAdmin": false,
"name": "Peppa Pig",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Fpeppa_pig.svg?alt=media&token=a9fc83f7-b347-4868-8d52-0e888f0de73a&_gl=1*jzxghx*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMTQ5LjQyLjAuMA..",
"textColor": "#fff",
"type": "signedIn",
"userId": "user0",
"userSnippylyId": "195298461116078"
},
"lastUpdated": 1727176311596,
"metadata": {
"apiKey": "AN5s6iaYIuLLXul0X4zf",
"documentId": "toolbar",
"organizationId": "velt-sample-app"
},
"pageInfo": {
"baseUrl": "https://velt-vercel-style-toolbar-demo.vercel.app",
"commentUrl": "https://velt-vercel-style-toolbar-demo.vercel.app/?scommentId=-O7Yi14ES3EPayuzQ54J",
"deviceInfo": {
"browserName": "Chrome",
"browserVersion": "128",
"deviceType": "Mobile",
"orientation": "landscape",
"osName": "Macintosh",
"osVersion": "10.15.7",
"screenHeight": 900,
"screenWidth": 1440,
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
},
"path": "/",
"screenWidth": 804,
"title": "Web App | Velt Demo",
"url": "https://velt-vercel-style-toolbar-demo.vercel.app/"
},
"status": {
"color": "#625DF5",
"id": "OPEN",
"lightColor": "#E7E8FA",
"name": "Open",
"svg": "\n \n \n \n ",
"type": "default"
},
"subscribedUsers": {
"3604c43f40557d7aa27ff4d0f72202a3": {
"type": "auto",
"user": {
"email": "jim.halpert@dundermifflin.com",
"initial": "J",
"name": "Jim Halpert",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Frosy_rabbit.svg?alt=media&token=4e65a9e3-080d-4416-839d-e761b6b37181&_gl=1*156om5t*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMjUzLjYwLjAuMA..",
"userId": "e54d19af-0e43-4364-819f-0a8b18a280fb",
"userSnippylyId": "5236622904561187"
}
},
"3d517fe6ebab7b8cfcf98db6259c8a59": {
"type": "auto",
"user": {
"clientOrganizationId": "velt-sample-app",
"color": "#67DBF4",
"email": "peppa.pig@velt.dev",
"initial": "P",
"isAdmin": false,
"name": "Peppa Pig",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Fpeppa_pig.svg?alt=media&token=a9fc83f7-b347-4868-8d52-0e888f0de73a&_gl=1*jzxghx*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMTQ5LjQyLjAuMA..",
"textColor": "#fff",
"type": "signedIn",
"userId": "user0",
"userSnippylyId": "195298461116078"
}
},
"7668f673d5669995175ef91b5d171945": {
"type": "manual",
"user": {
"clientOrganizationId": "velt-sample-app",
"color": "#2c83fc",
"email": "freddy.froglet@velt.dev",
"initial": "F",
"isAdmin": false,
"name": "Freddy Froglet",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Ffreddy_froglet.svg?alt=media&token=e3b2e292-6480-4507-9da3-21a62a1346d4&_gl=1*s4yycs*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMDQzLjcuMC4w",
"textColor": "#fff",
"type": "signedIn",
"userId": "user8",
"userSnippylyId": "4291391246842733"
}
}
},
"type": "comment"
},
"targetComment": {
"commentHtml": "test",
"commentId": 719893,
"commentText": "test",
"from": {
"clientOrganizationId": "velt-sample-app",
"color": "#2c83fc",
"email": "freddy.froglet@velt.dev",
"initial": "F",
"isAdmin": false,
"name": "Freddy Froglet",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Ffreddy_froglet.svg?alt=media&token=e3b2e292-6480-4507-9da3-21a62a1346d4&_gl=1*s4yycs*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMDQzLjcuMC4w",
"textColor": "#fff",
"type": "signedIn",
"userId": "user8",
"userSnippylyId": "4291391246842733"
},
"lastUpdated": "2024-09-24T11:11:58.344Z",
"status": "added",
"type": "text"
},
"actionType": "added",
"notificationSource": "comment",
"actionUser": {
"clientOrganizationId": "velt-sample-app",
"color": "#2c83fc",
"email": "freddy.froglet@velt.dev",
"initial": "F",
"isAdmin": false,
"name": "Freddy Froglet",
"organizationId": "392580a690394bfadd823101f2b05513",
"photoUrl": "https://firebasestorage.googleapis.com/v0/b/snippyly-sdk-external/o/avatars%2Ffreddy_froglet.svg?alt=media&token=e3b2e292-6480-4507-9da3-21a62a1346d4&_gl=1*s4yycs*_ga*NTc3MjEzMjIwLjE2NjEwODkwMDU.*_ga_CW55HF8NVT*MTY5NzE0MTIzNC4zMzMuMS4xNjk3MTQyMDQzLjcuMC4w",
"textColor": "#fff",
"type": "signedIn",
"userId": "user8",
"userSnippylyId": "4291391246842733"
},
"metadata": {
"apiKey": "AN5s6iaYIuLLXul0X4zf",
"clientDocumentId": "toolbar",
"clientOrganizationId": "velt-sample-app",
"documentId": "toolbar",
"documentName": "Your_Document_Name",
"organizationId": "velt-sample-app",
"pageInfo": {
"baseUrl": "https://velt-vercel-style-toolbar-demo.vercel.app",
"path": "/",
"title": "Web App | Velt Demo",
"url": "https://velt-vercel-style-toolbar-demo.vercel.app/"
}
},
"platform": "sdk",
"usersOrganizationNotificationsConfig": {
"user0": {
"inbox": "ALL",
"email": "MINE"
},
"user8": {
"inbox": "NONE",
"email": "ALL"
}
}
}
```
## Huddle Events
The `Huddle` component will emit webhook notifications when a user creates or joins a group huddle.
**Payload Schema**: [WebhookV1Payload](/api-reference/sdk/models/data-models#webhookv1payload) + [Huddle Events](/api-reference/sdk/models/data-models#huddle-events)
### List of Action Types
| Action Type | Description |
| ----------- | -------------------------------- |
| `created` | When a `User` created a `Huddle` |
| `join` | When a `User` joins a `Huddle` |
```js expandable lines theme={null}
{
"webhookId": "-Nvmw84XtUUHIsrcKAvI",
"actionType": "created",
"notificationSource": "huddle",
"actionUser": {
"clientOrganizationId": "7e2aed5bc102d06f740ab92afdf58e78f9d34d409555d19a35389309c80f4b4f",
"color": "#19bcfe",
"email": "john@trysnippyly.com",
"organizationId": "7e2aed5bc102d06f740ab92afdf58e78f9d34d409555d19a35389309c80f4b4f",
"name": "John Smith",
"plan": "free",
"type": "signedIn",
"userId": "1",
},
"metadata": {
"apiKey": "Emcfab4ysRXaC1CZ8hmG",
"clientDocumentId": "12-4-24",
"documentId": "1856907974154638",
"locations": {
"5638605251172150": {
"location": {
"id": "location1",
"locationName": "Location 1"
},
"locationId": 5638605251172150,
"pageInfo": {
"baseUrl": "http://localhost:3000",
"path": "/",
"title": "Velt React Demo",
"url": "http://localhost:3000/"
}
}
},
"pageInfo": {
"baseUrl": "http://localhost:3000",
"path": "/",
"title": "Velt React Demo",
"url": "http://localhost:3000/"
}
},
"platform": "sdk"
}
```
```js theme={null}
{
"webhookId": "-Nvmw84XtUUHIsrcKAvI",
"actionType": "joined",
"notificationSource": "huddle",
"actionUser": {
"clientOrganizationId": "7e2aed5bc102d06f740ab92afdf58e78f9d34d409555d19a35389309c80f4b4f",
"color": "#ff7162",
"contacts": [
{
"email": "john@trysnippyly.com",
"name": "John Smith",
"userId": "1"
},
{
"email": "sarah@trysnippyly.com",
"name": "Sarah Wilson",
"userId": "3"
}
],
"email": "maria@trysnippyly.com",
"organizationId": "7e2aed5bc102d06f740ab92afdf58e78f9d34d409555d19a35389309c80f4b4f",
"name": "Maria Garcia",
"plan": "paid",
"type": "signedIn",
"userId": "2",
},
"metadata": {
"apiKey": "Emcfab4ysRXaC1CZ8hmG",
"clientDocumentId": "12-4-24",
"documentId": "1856907974154638",
"pageInfo": {
"baseUrl": "http://localhost:3000",
"path": "/",
"title": "Velt React Demo",
"url": "http://localhost:3000/"
}
},
"platform": "sdk"
}
```
## CRDT Events
The CRDT component will emit webhook notifications when data changes occur. Webhooks are enabled by default with a 5-second debounce.
**Payload Schema**: [WebhookV1Payload](/api-reference/sdk/models/data-models#webhookv1payload) + [CRDT Events](/api-reference/sdk/models/data-models#crdt-events)
Learn how to configure CRDT webhooks in the [CRDT Core Setup documentation](/realtime-collaboration/crdt/setup/core#webhooks).
### Action Type
| Action Type | Description |
| ------------ | ------------------------- |
| `updateData` | When CRDT data is updated |
```js Sample Webhook Data expandable lines theme={null}
{
"actionType": "updateData",
"notificationSource": "crdt",
"actionUser": {
"organizationId": "9eab9b808921fa1c682be3516bb49ab3",
"userSnippylyId": "6171441707478284",
"color": "#ffcb00",
"initial": "M",
"clientUserName": "Michael Scott",
"name": "Michael Scott",
"clientOrganizationId": "crdt-array-demo-org1",
"name_lowercase": "michaelscott",
"email_lowercase": "michael.scott@dundermifflin.com",
"textColor": "#FFFFFF",
"userId": "michael",
"email": "michael.scott@dundermifflin.com",
"photoUrl": null,
"isAdmin": false,
"type": "signedIn",
"contacts": []
},
"metadata": {
"apiKey": "VKabul7W2DJhLu1BiMaj",
"clientDocumentId": "crdt-array-demo-doc-1",
"clientOrganizationId": "crdt-array-demo-org1",
"documentId": "crdt-array-demo-doc-1",
"documentMetadata": {
"apiKey": "VKabul7W2DJhLu1BiMaj",
"clientDocumentId": "crdt-array-demo-doc-1",
"clientOrganizationId": "crdt-array-demo-org1",
"documentId": "crdt-array-demo-doc-1",
"documentName": "CRDT Array Demo",
"organizationId": "crdt-array-demo-org1",
"pageInfo": {
"baseUrl": "http://localhost:5225",
"path": "/",
"queryParams": "",
"title": "CRDT Array Demo",
"url": "http://localhost:5225/"
}
},
"documentName": "CRDT Array Demo",
"organizationId": "crdt-array-demo-org1",
"organizationMetadata": {
"apiKey": "VKabul7W2DJhLu1BiMaj",
"clientOrganizationId": "crdt-array-demo-org1",
"organizationId": "crdt-array-demo-org1"
},
"pageInfo": {
"baseUrl": "http://localhost:5225",
"path": "/",
"queryParams": "",
"title": "CRDT Array Demo",
"url": "http://localhost:5225/"
}
},
"platform": "sdk",
"crdtData": {
"data": [
{
"completed": false,
"id": "seed-1",
"text": "Welcome Todo - Edit me!"
},
{
"completed": false,
"id": "bmg6tpj7we",
"text": "Todo1"
},
{
"completed": false,
"id": "dt0vhs2t3os",
"text": "Todo2"
}
],
"id": "crdt-array-demo-todos-1",
"lastUpdate": "2026-03-17T06:23:24.514Z",
"lastUpdatedBy": "michael",
"sessionId": "tvLupvP0L2jiztlba4P0"
},
"webhookId": "-OnuSfG_ffGIwNkodE2a",
"accessDeniedUsers": []
}
```