From 4a3415378b21f9451dcf1c7f2e0a1fb56b9d18f5 Mon Sep 17 00:00:00 2001 From: "agent-skills-sync[bot]" Date: Mon, 15 Jun 2026 21:37:44 +0000 Subject: [PATCH 1/2] sync: update agent-skills from velt-js/agent-skills [automated] --- .../AGENTS.full.md | 135 ++++++++++++++++-- .../velt-rest-apis-best-practices/AGENTS.md | 4 +- skills/velt-rest-apis-best-practices/SKILL.md | 1 + .../metadata.json | 6 +- .../rules/shared/core/core-rest-api-auth.md | 45 ++++-- .../shared/rest-api/rest-advanced-webhooks.md | 125 ++++++++++++++++ .../AGENTS.full.md | 17 ++- .../AGENTS.md | 2 +- .../metadata.json | 2 +- .../attachment-multipart-provider.md | 10 +- .../rules/shared/data/data-types-reference.md | 13 +- .../shared/provider/provider-notification.md | 2 +- .../provider/provider-reaction-recording.md | 1 + .../shared/provider/provider-recorder.md | 14 +- 14 files changed, 323 insertions(+), 54 deletions(-) create mode 100644 skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-advanced-webhooks.md diff --git a/skills/velt-rest-apis-best-practices/AGENTS.full.md b/skills/velt-rest-apis-best-practices/AGENTS.full.md index e9496d1..1d6559b 100644 --- a/skills/velt-rest-apis-best-practices/AGENTS.full.md +++ b/skills/velt-rest-apis-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Rest Apis Best Practices -**Version 1.0.2** +**Version 1.0.4** Velt May 2026 @@ -29,8 +29,9 @@ Comprehensive guide for integrating Velt's server-side surface: the Velt REST AP - 2.2 [Approval Engine REST API — moved to its own skill](#22-approval-engine-rest-api-moved-to-its-own-skill) - 2.3 [Comment Annotations and Comments CRUD via REST API](#23-comment-annotations-and-comments-crud-via-rest-api) - 2.4 [Document, Organization, and Folder Management via REST API](#24-document-organization-and-folder-management-via-rest-api) - - 2.5 [Notification Management via REST API](#25-notification-management-via-rest-api) - - 2.6 [User Management via REST API](#26-user-management-via-rest-api) + - 2.5 [Manage Advanced Webhooks via REST API](#25-manage-advanced-webhooks-via-rest-api) + - 2.6 [Notification Management via REST API](#26-notification-management-via-rest-api) + - 2.7 [User Management via REST API](#27-user-management-via-rest-api) 3. [Webhooks](#3-webhooks) — **MEDIUM** - 3.1 [Webhook v1 Setup and Event Handling](#31-webhook-v1-setup-and-event-handling) @@ -51,9 +52,14 @@ Foundational requirements for every server-side Velt integration. Covers JWT tok **Impact: CRITICAL (Missing authentication headers cause 401 errors on every API call)** -Every Velt REST API v2 call requires two authentication headers. Without both, the request will be rejected. +Every Velt REST API v2 call requires two authentication headers. Without both, the request will be rejected. The header *pair* depends on the endpoint's scope — api-key-level vs. workspace-level. Sending the wrong pair fails with a 401, even if both headers are present. -**Incorrect (missing auth token header):** +**API-key-level endpoints (most of the v2 surface — `/organizations/*`, `/users/*`, `/comments/*`, `/notifications/*`, `/workspace/add-domain`, `/workspace/emailconfig-update`, etc.):** + +- `x-velt-api-key` — Your API key from the Velt console +- `x-velt-auth-token` — Auth token from Velt console (Configuration > Auth Token), or retrieved via `POST https://api.velt.dev/v2/workspace/authtokens-get` + +**Incorrect (api-key-level endpoint, missing auth token header):** ```bash curl -X POST https://api.velt.dev/v2/organizations/get \ @@ -62,7 +68,7 @@ curl -X POST https://api.velt.dev/v2/organizations/get \ -d '{"data": {"organizationId": "org_123"}}' ``` -**Correct (curl with both headers):** +**Correct (api-key-level endpoint, curl with both headers):** ```bash curl -X POST https://api.velt.dev/v2/organizations/get \ @@ -72,6 +78,26 @@ curl -X POST https://api.velt.dev/v2/organizations/get \ -d '{"data": {"organizationId": "org_123"}}' ``` +**Incorrect (workspace-level endpoint called with the api-key-level pair):** + +```bash +curl -X POST https://api.velt.dev/v2/workspace/get \ + -H 'Content-Type: application/json' \ + -H 'x-velt-api-key: your_api_key' \ + -H 'x-velt-auth-token: your_auth_token' \ + -d '{"data": {}}' +``` + +**Correct (workspace-level endpoint with workspace-id + workspace-auth-token):** + +```bash +curl -X POST https://api.velt.dev/v2/workspace/get \ + -H 'Content-Type: application/json' \ + -H 'x-velt-workspace-id: workspace_abc123' \ + -H 'x-velt-workspace-auth-token: your_workspace_auth_token' \ + -d '{"data": {}}' +``` + **Incorrect (using GET method):** ```javascript @@ -104,8 +130,6 @@ const response = await fetch('https://api.velt.dev/v2/organizations/get', { const result = await response.json(); ``` -Reference: `https://docs.velt.dev/api-reference/rest-apis/overview` (## REST API > ### Authentication) - --- ### 1.2 Generate JWT Tokens for Frontend User Authentication @@ -731,7 +755,96 @@ References: --- -### 2.5 Notification Management via REST API +### 2.5 Manage Advanced Webhooks via REST API + +**Impact: MEDIUM (Programmatically enable advanced webhooks and manage delivery endpoints, signing secrets, and per-endpoint event/channel filters)** + +Advanced Webhooks add multiple delivery endpoints, per-endpoint event/channel filtering, rate limiting, and signed payloads on top of basic webhooks. These management endpoints live under `https://api.velt.dev/v2/workspace/` — all are POST, all use **API-key-level auth** (both `x-velt-api-key` and `x-velt-auth-token` headers), and all wrap the payload in `{ "data": { ... } }`. This rule covers *managing* advanced webhooks; for receiving and verifying the delivered events, see `webhooks-advanced` (Svix). + +**Required headers (every request):** + +```bash +x-velt-api-key: YOUR_API_KEY +x-velt-auth-token: YOUR_AUTH_TOKEN +# Get config (no body params required) +POST https://api.velt.dev/v2/workspace/advancedwebhookconfig/get +{ "data": {} } +# → data: { isEnabled, encryptData, encodeData, publicKey, enableDataProtection } + +# Update config (partial; at least one field required; first call must set isEnabled:true) +POST https://api.velt.dev/v2/workspace/advancedwebhookconfig/update +{ "data": { "isEnabled": true, "encryptData": false, "encodeData": false } } +# Create an endpoint — url is required and must be a valid http(s) URL. +# The signing secret is ALWAYS generated server-side; never pass it in the request. +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/create +{ "data": { + "url": "https://example.com/webhooks/velt", + "description": "Primary endpoint", + "filterTypes": ["comment.add", "comment.update"], // optional; non-empty when provided; omit = all events + "channels": ["channel-a"], // optional; non-empty when provided; omit = all channels + "disabled": false, // optional; default false + "rateLimit": 10, // optional; max deliveries/sec (positive integer) + "uid": "my-endpoint-1", // optional caller-assigned id + "metadata": { "team": "platform" } // optional string-valued key/value pairs +} } +# → data: { id: "ep_...", url, description, filterTypes, channels, disabled, rateLimit, uid, createdAt, updatedAt } + +# List endpoints — paginated via opaque iterator cursor (limit 1–250) +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/get +{ "data": { "limit": 50, "iterator": "" } } +# → data: { endpoints: [...], iterator, prevIterator, done } +# When done === false, pass the returned iterator to fetch the next page. + +# Update an endpoint — endpointId required; at least one other field; partial (omitted fields unchanged) +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/update +{ "data": { "endpointId": "ep_...", "description": "Updated", "disabled": false } } + +# Delete an endpoint — permanent; immediately stops deliveries and invalidates the signing secret +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/delete +{ "data": { "endpointId": "ep_..." } } +# → data: { endpointId, deleted: true } +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/secret/get +{ "data": { "endpointId": "ep_..." } } +# → data: { endpointId, secret: "whsec_..." } +``` + +**Response envelope:** success responses return `{ "result": { "status": "success", "message", "data" } }`; failures return `{ "error": { "status", "message" } }` where `status` is one of `INVALID_ARGUMENT`, `FAILED_PRECONDITION`, or `NOT_FOUND`. +Advanced webhooks must be provisioned before any endpoint can be created. The **first** `update` call must include `isEnabled: true` — this provisions the underlying webhook application. Until then, the endpoint-management APIs return `FAILED_PRECONDITION`. +If advanced webhooks are not available for the workspace at all, `advancedwebhookconfig/get` returns `FAILED_PRECONDITION` ("Advanced webhooks are not available for this workspace.") — contact Velt to enable the feature. +All four endpoint operations require advanced webhooks to already be enabled (else `FAILED_PRECONDITION`). +The signing secret is generated at creation and fetched separately. Use it to verify the signature on delivered webhook payloads (see `webhooks-advanced`). Treat it like a credential. + +**Incorrect (creating an endpoint before enabling advanced webhooks, or trying to supply your own secret):** + +```bash +# BUG 1: no prior advancedwebhookconfig/update with { isEnabled: true } → +# { "error": { "status": "FAILED_PRECONDITION", "message": "Advanced webhooks are disabled for this workspace..." } } +# BUG 2: "secret" is ignored — the signing secret is always server-generated and only readable via endpoints/secret/get. +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/create +{ "data": { "url": "https://example.com/webhooks/velt", "secret": "whsec_mine" } } +``` + +**Correct (enable once, then create the endpoint and read back the generated secret):** + +```bash +# 1) Enable advanced webhooks for the workspace (first call must set isEnabled:true) +POST https://api.velt.dev/v2/workspace/advancedwebhookconfig/update +{ "data": { "isEnabled": true } } + +# 2) Create the delivery endpoint +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/create +{ "data": { "url": "https://example.com/webhooks/velt", "filterTypes": ["comment.add"] } } +# → data.id = "ep_..." + +# 3) Fetch the server-generated signing secret to verify payloads +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/secret/get +{ "data": { "endpointId": "ep_..." } } +# → data.secret = "whsec_..." +``` + +--- + +### 2.6 Notification Management via REST API **Impact: MEDIUM (Notifications keep users informed of collaboration events — misconfigured templates produce broken messages)** @@ -888,7 +1001,7 @@ Reference: `https://docs.velt.dev/api-reference/rest-api/notifications` (## REST --- -### 2.6 User Management via REST API +### 2.7 User Management via REST API **Impact: HIGH (User provisioning and GDPR compliance are critical for production deployments)** @@ -1244,3 +1357,5 @@ Reference: `https://docs.velt.dev/api-reference/rest-api/overview` (## REST API - https://docs.velt.dev/api-reference/rest-apis/v2/comments-feature/comments/get-comments - https://console.velt.dev - https://docs.velt.dev/api-reference/rest-apis/v2/notifications/add-notifications +- https://docs.velt.dev/api-reference/rest-apis/v2/workspace/create +- https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-update diff --git a/skills/velt-rest-apis-best-practices/AGENTS.md b/skills/velt-rest-apis-best-practices/AGENTS.md index ca3b8df..b6fc681 100644 --- a/skills/velt-rest-apis-best-practices/AGENTS.md +++ b/skills/velt-rest-apis-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Rest Apis Best Practices -|v1.0.2|Velt|May 2026 +|v1.0.4|Velt|May 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules @@ -7,7 +7,7 @@ |shared/core:{core-rest-api-auth.md,core-jwt-tokens.md} ## 2. REST API Endpoints — HIGH -|shared/rest-api:{rest-activities-crdt.md,rest-approval-engine.md,rest-comments.md,rest-documents-orgs.md,rest-notifications.md,rest-users.md} +|shared/rest-api:{rest-activities-crdt.md,rest-approval-engine.md,rest-comments.md,rest-documents-orgs.md,rest-advanced-webhooks.md,rest-notifications.md,rest-users.md} ## 3. Webhooks — MEDIUM |shared/webhooks:{webhooks-basic.md,webhooks-advanced.md} diff --git a/skills/velt-rest-apis-best-practices/SKILL.md b/skills/velt-rest-apis-best-practices/SKILL.md index 1bbd46c..05668b6 100644 --- a/skills/velt-rest-apis-best-practices/SKILL.md +++ b/skills/velt-rest-apis-best-practices/SKILL.md @@ -41,6 +41,7 @@ Reference these guidelines when: - `rest-documents-orgs` — Document, organization, folder management - `rest-notifications` — Notification add/get/update/delete + config - `rest-activities-crdt` — Activity logs + CRDT data endpoints +- `rest-advanced-webhooks` — Manage advanced webhooks: config enable + endpoint CRUD + signing-secret retrieval - `rest-approval-engine` — pointer (Approval Engine is now its own skill: see `velt-approval-engine-best-practices`) ### Webhooks (MEDIUM) diff --git a/skills/velt-rest-apis-best-practices/metadata.json b/skills/velt-rest-apis-best-practices/metadata.json index 7774bf4..2c9ff27 100644 --- a/skills/velt-rest-apis-best-practices/metadata.json +++ b/skills/velt-rest-apis-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.0.2", + "version": "1.0.4", "organization": "Velt", "date": "May 2026", "abstract": "Comprehensive guide for integrating Velt's server-side surface: the Velt REST API v2, JWT-based authentication for the frontend SDK, and webhook event handling. Covers the required `x-velt-api-key` and `x-velt-auth-token` header contract, JWT token generation and refresh flows (48h expiry), full CRUD over comment annotations, comments, notifications, users (including GDPR data export/delete), documents, organizations, folders, activity logs and CRDT documents via REST. Also covers v1 webhook setup plus v2 / Svix enterprise webhooks with retries and transformations, payload shapes for comment, huddle and CRDT events, and signature verification. All guidance is evidence-backed from official Velt documentation. For the self-hosted Python SDK (`velt-py`) used to store data on your own infrastructure, see `velt-self-hosting-data-best-practices`.", @@ -12,6 +12,8 @@ "https://docs.velt.dev/api-reference/rest-apis/v2/comments-feature/comment-annotations/get-comment-annotations-v2", "https://docs.velt.dev/api-reference/rest-apis/v2/comments-feature/comments/get-comments", "https://console.velt.dev", - "https://docs.velt.dev/api-reference/rest-apis/v2/notifications/add-notifications" + "https://docs.velt.dev/api-reference/rest-apis/v2/notifications/add-notifications", + "https://docs.velt.dev/api-reference/rest-apis/v2/workspace/create", + "https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-update" ] } diff --git a/skills/velt-rest-apis-best-practices/rules/shared/core/core-rest-api-auth.md b/skills/velt-rest-apis-best-practices/rules/shared/core/core-rest-api-auth.md index 667d297..af5d2b5 100644 --- a/skills/velt-rest-apis-best-practices/rules/shared/core/core-rest-api-auth.md +++ b/skills/velt-rest-apis-best-practices/rules/shared/core/core-rest-api-auth.md @@ -7,18 +7,23 @@ tags: rest, api, authentication, headers, curl, fetch ## Authenticate All Velt REST API Calls with Required Headers -Every Velt REST API v2 call requires two authentication headers. Without both, the request will be rejected. +Every Velt REST API v2 call requires two authentication headers. Without both, the request will be rejected. The header *pair* depends on the endpoint's scope — api-key-level vs. workspace-level. Sending the wrong pair fails with a 401, even if both headers are present. -**Required headers for ALL endpoints:** +**API-key-level endpoints (most of the v2 surface — `/organizations/*`, `/users/*`, `/comments/*`, `/notifications/*`, `/workspace/add-domain`, `/workspace/emailconfig-update`, etc.):** - `x-velt-api-key` — Your API key from the Velt console -- `x-velt-auth-token` — Auth token from Velt console (Configuration > Auth Token) +- `x-velt-auth-token` — Auth token from Velt console (Configuration > Auth Token), or retrieved via `POST https://api.velt.dev/v2/workspace/authtokens-get` + +**Workspace-level endpoints (e.g., `/v2/workspace/get`, `/v2/workspace/apikey-create`):** + +- `x-velt-workspace-id` — The workspace ID (`result.data.id` from `POST https://api.velt.dev/v2/workspace/create`) +- `x-velt-workspace-auth-token` — The workspace auth token (`result.data.authToken` from `POST https://api.velt.dev/v2/workspace/create`) **Base URL:** `https://api.velt.dev/v2` **All endpoints use POST method** — even for read and delete operations. Do not use GET or DELETE. -**Incorrect (missing auth token header):** +**Incorrect (api-key-level endpoint, missing auth token header):** ```bash curl -X POST https://api.velt.dev/v2/organizations/get \ @@ -27,7 +32,7 @@ curl -X POST https://api.velt.dev/v2/organizations/get \ -d '{"data": {"organizationId": "org_123"}}' ``` -**Correct (curl with both headers):** +**Correct (api-key-level endpoint, curl with both headers):** ```bash curl -X POST https://api.velt.dev/v2/organizations/get \ @@ -37,6 +42,26 @@ curl -X POST https://api.velt.dev/v2/organizations/get \ -d '{"data": {"organizationId": "org_123"}}' ``` +**Incorrect (workspace-level endpoint called with the api-key-level pair):** + +```bash +curl -X POST https://api.velt.dev/v2/workspace/get \ + -H 'Content-Type: application/json' \ + -H 'x-velt-api-key: your_api_key' \ + -H 'x-velt-auth-token: your_auth_token' \ + -d '{"data": {}}' +``` + +**Correct (workspace-level endpoint with workspace-id + workspace-auth-token):** + +```bash +curl -X POST https://api.velt.dev/v2/workspace/get \ + -H 'Content-Type: application/json' \ + -H 'x-velt-workspace-id: workspace_abc123' \ + -H 'x-velt-workspace-auth-token: your_workspace_auth_token' \ + -d '{"data": {}}' +``` + **Incorrect (using GET method):** ```javascript @@ -72,16 +97,20 @@ const result = await response.json(); **Key points:** - Both headers must be present on every request — omitting either causes a 401. +- Header pair depends on endpoint scope: api-key-level → `x-velt-api-key` + `x-velt-auth-token`; workspace-level → `x-velt-workspace-id` + `x-velt-workspace-auth-token`. The two pairs are not interchangeable. +- The workspace auth token (`result.data.authToken` from `/v2/workspace/create`) is distinct from the per-API-key auth token returned by `/v2/workspace/authtokens-get`. Don't swap them. - The auth token is separate from JWT tokens used for frontend user authentication. - All endpoints accept POST regardless of whether the operation is a read, create, update, or delete. - Request bodies use a `{ data: { ... } }` wrapper format. -- Never expose `x-velt-auth-token` in client-side code; make API calls from your server only. +- Never expose any auth token (`x-velt-auth-token` or `x-velt-workspace-auth-token`) in client-side code; make API calls from your server only. **Verification:** -- [ ] Both `x-velt-api-key` and `x-velt-auth-token` headers are set +- [ ] Correct header pair for the endpoint's scope: api-key-level uses `x-velt-api-key` + `x-velt-auth-token`; workspace-level uses `x-velt-workspace-id` + `x-velt-workspace-auth-token` - [ ] Request method is POST - [ ] Base URL is `https://api.velt.dev/v2` - [ ] Auth token is kept server-side only, never sent to the browser - [ ] Request body uses the `{ data: { ... } }` wrapper format -**Source Pointer:** `https://docs.velt.dev/api-reference/rest-apis/overview` (## REST API > ### Authentication) +**Source Pointers:** +- `https://docs.velt.dev/api-reference/rest-apis/overview` (## REST API > ### Authentication) +- `https://docs.velt.dev/api-reference/rest-apis/v2/workspace/create` (## Next Steps — workspace-level vs. api-key-level header pairs) diff --git a/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-advanced-webhooks.md b/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-advanced-webhooks.md new file mode 100644 index 0000000..1f85e25 --- /dev/null +++ b/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-advanced-webhooks.md @@ -0,0 +1,125 @@ +--- +title: Manage Advanced Webhooks via REST API +impact: MEDIUM +impactDescription: Programmatically enable advanced webhooks and manage delivery endpoints, signing secrets, and per-endpoint event/channel filters +tags: rest, api, webhooks, advanced-webhooks, endpoints, signing-secret, filterTypes, channels, rateLimit, FAILED_PRECONDITION +--- + +## Manage Advanced Webhooks via REST API + +Advanced Webhooks add multiple delivery endpoints, per-endpoint event/channel filtering, rate limiting, and signed payloads on top of basic webhooks. These management endpoints live under `https://api.velt.dev/v2/workspace/` — all are POST, all use **API-key-level auth** (both `x-velt-api-key` and `x-velt-auth-token` headers), and all wrap the payload in `{ "data": { ... } }`. This rule covers *managing* advanced webhooks; for receiving and verifying the delivered events, see `webhooks-advanced` (Svix). + +**Required headers (every request):** + +``` +x-velt-api-key: YOUR_API_KEY +x-velt-auth-token: YOUR_AUTH_TOKEN +``` + +**Response envelope:** success responses return `{ "result": { "status": "success", "message", "data" } }`; failures return `{ "error": { "status", "message" } }` where `status` is one of `INVALID_ARGUMENT`, `FAILED_PRECONDITION`, or `NOT_FOUND`. + +### Enable first: workspace config + +Advanced webhooks must be provisioned before any endpoint can be created. The **first** `update` call must include `isEnabled: true` — this provisions the underlying webhook application. Until then, the endpoint-management APIs return `FAILED_PRECONDITION`. + +```bash +# Get config (no body params required) +POST https://api.velt.dev/v2/workspace/advancedwebhookconfig/get +{ "data": {} } +# → data: { isEnabled, encryptData, encodeData, publicKey, enableDataProtection } + +# Update config (partial; at least one field required; first call must set isEnabled:true) +POST https://api.velt.dev/v2/workspace/advancedwebhookconfig/update +{ "data": { "isEnabled": true, "encryptData": false, "encodeData": false } } +``` + +If advanced webhooks are not available for the workspace at all, `advancedwebhookconfig/get` returns `FAILED_PRECONDITION` ("Advanced webhooks are not available for this workspace.") — contact Velt to enable the feature. + +### Manage delivery endpoints + +All four endpoint operations require advanced webhooks to already be enabled (else `FAILED_PRECONDITION`). + +```bash +# Create an endpoint — url is required and must be a valid http(s) URL. +# The signing secret is ALWAYS generated server-side; never pass it in the request. +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/create +{ "data": { + "url": "https://example.com/webhooks/velt", + "description": "Primary endpoint", + "filterTypes": ["comment.add", "comment.update"], // optional; non-empty when provided; omit = all events + "channels": ["channel-a"], // optional; non-empty when provided; omit = all channels + "disabled": false, // optional; default false + "rateLimit": 10, // optional; max deliveries/sec (positive integer) + "uid": "my-endpoint-1", // optional caller-assigned id + "metadata": { "team": "platform" } // optional string-valued key/value pairs +} } +# → data: { id: "ep_...", url, description, filterTypes, channels, disabled, rateLimit, uid, createdAt, updatedAt } + +# List endpoints — paginated via opaque iterator cursor (limit 1–250) +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/get +{ "data": { "limit": 50, "iterator": "" } } +# → data: { endpoints: [...], iterator, prevIterator, done } +# When done === false, pass the returned iterator to fetch the next page. + +# Update an endpoint — endpointId required; at least one other field; partial (omitted fields unchanged) +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/update +{ "data": { "endpointId": "ep_...", "description": "Updated", "disabled": false } } + +# Delete an endpoint — permanent; immediately stops deliveries and invalidates the signing secret +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/delete +{ "data": { "endpointId": "ep_..." } } +# → data: { endpointId, deleted: true } +``` + +### Retrieve the signing secret + +The signing secret is generated at creation and fetched separately. Use it to verify the signature on delivered webhook payloads (see `webhooks-advanced`). Treat it like a credential. + +```bash +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/secret/get +{ "data": { "endpointId": "ep_..." } } +# → data: { endpointId, secret: "whsec_..." } +``` + +**Incorrect (creating an endpoint before enabling advanced webhooks, or trying to supply your own secret):** + +```bash +# BUG 1: no prior advancedwebhookconfig/update with { isEnabled: true } → +# { "error": { "status": "FAILED_PRECONDITION", "message": "Advanced webhooks are disabled for this workspace..." } } +# BUG 2: "secret" is ignored — the signing secret is always server-generated and only readable via endpoints/secret/get. +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/create +{ "data": { "url": "https://example.com/webhooks/velt", "secret": "whsec_mine" } } +``` + +**Correct (enable once, then create the endpoint and read back the generated secret):** + +```bash +# 1) Enable advanced webhooks for the workspace (first call must set isEnabled:true) +POST https://api.velt.dev/v2/workspace/advancedwebhookconfig/update +{ "data": { "isEnabled": true } } + +# 2) Create the delivery endpoint +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/create +{ "data": { "url": "https://example.com/webhooks/velt", "filterTypes": ["comment.add"] } } +# → data.id = "ep_..." + +# 3) Fetch the server-generated signing secret to verify payloads +POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/secret/get +{ "data": { "endpointId": "ep_..." } } +# → data.secret = "whsec_..." +``` + +**Verification Checklist:** +- [ ] Both `x-velt-api-key` and `x-velt-auth-token` headers sent on every request (API-key-level auth) +- [ ] Advanced webhooks enabled via `advancedwebhookconfig/update` with `{ isEnabled: true }` before any endpoint call +- [ ] Endpoint URLs used verbatim including the `/v2/workspace/advancedwebhook/...` path (basic-webhook config endpoints `webhookconfig-get/update` are separate) +- [ ] `filterTypes` / `channels` are non-empty arrays when provided (omit them to receive all events / all channels) +- [ ] Signing secret is never sent in `create`; it is read back from `endpoints/secret/get` and stored securely (never client-side) +- [ ] `FAILED_PRECONDITION` handled as "feature disabled/not provisioned"; `INVALID_ARGUMENT` as validation failure; `NOT_FOUND` as unknown endpoint +- [ ] List pagination loops on `data.iterator` while `data.done === false` + +**Source Pointers:** +- https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-get — "Get Advanced Webhook Config" +- https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-update — "Update Advanced Webhook Config" +- https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhook-endpoints-create — "Create Advanced Webhook Endpoint" +- https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhook-endpoints-secret-get — "Get Advanced Webhook Endpoint Secret" diff --git a/skills/velt-self-hosting-data-best-practices/AGENTS.full.md b/skills/velt-self-hosting-data-best-practices/AGENTS.full.md index cc7e4c2..8cbb306 100644 --- a/skills/velt-self-hosting-data-best-practices/AGENTS.full.md +++ b/skills/velt-self-hosting-data-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Self Hosting Data Best Practices -**Version 1.0.5** +**Version 1.0.8** Velt March 2026 @@ -1034,16 +1034,16 @@ interface SaveAttachmentResolverData { url: string; } // persisted back onto A The attachment provider sits **outside** the `Partial` strip model used by every other provider. There is no `get` and no `Partial` — attachments are binary files. When Velt hands a save call to your storage provider, the payload is a fixed shape: The JSON `request` body in URL (endpoint) mode is exactly `{ attachment: { attachmentId, name, mimeType }, metadata, event }` — the `File` is destructured out and sent as a separate multipart binary part to your storage, **never to Velt**. On delete, Velt sends `{ attachmentId, metadata: { apiKey, documentId, organizationId, folderId? }, event }` where `event` is `ATTACHMENT_DELETE` (`"attachment.delete"`). -What persists on Velt's side after a successful save (everything except the binary bytes): `attachmentId` (PK), `name`, `bucketPath`, `size`, `type`, `url` (the URL **your** storage returned), `thumbnail`, `thumbnailWithPlayIconUrl`, `metadata` (arbitrary), `mimeType`, `previewImages`, and the `isAttachmentResolverUsed` flag. The `url` is the only field that comes from your `save` response; the rest are structural. +What persists on Velt's side after a successful save (everything except the binary bytes): `attachmentId` (PK), `name`, `size`, `type`, `url` (the URL **your** storage returned), `thumbnail`, `thumbnailWithPlayIconUrl`, `metadata` (arbitrary), `mimeType`, `previewImages`, and the `isAttachmentResolverUsed` flag. The `url` is the only field that comes from your `save` response; the rest are structural. **Incorrect (assuming `request.attachment` is a full `Attachment` object — only three sub-fields are guaranteed):** ```tsx const saveAttachment = async (request: SaveAttachmentResolverRequest) => { - // BUG: `bucketPath`, `size`, `thumbnail`, `previewImages` are not in `request.attachment`. + // BUG: `size`, `thumbnail`, `previewImages` are not in `request.attachment`. // Velt computes those on its side from the `{ url }` you return plus the binary it just handed you. - const { attachmentId, name, mimeType, bucketPath, size } = request.attachment as any; - const url = await storage.put(request.file, { bucketPath }); // `bucketPath` is undefined + const { attachmentId, name, mimeType, size, thumbnail } = request.attachment as any; + const url = await storage.put(request.file, { size }); // `size` is undefined return { data: { url }, success: true, statusCode: 200 }; }; ``` @@ -1855,9 +1855,9 @@ const recorderStorage: AttachmentDataProvider = { ```tsx const saveRecorder = async (request) => { - // BUG: Velt still tracks { attachmentId, name, bucketPath } stubs for storage cleanup. + // BUG: Velt still tracks { attachmentId, name } stubs for each attachment. // If your DB is the only source of truth for attachment IDs, you risk orphaning bucket objects - // because Velt expects bucketPath to round-trip through the stub. + // because Velt no longer retains a storage path back to your bucket. for (const partial of Object.values(request.recorderAnnotations)) { await db.saveAttachments(partial.attachments); // assumes Velt has nothing — wrong } @@ -1865,7 +1865,7 @@ const saveRecorder = async (request) => { }; ``` -**Correct (your DB stores the PII-bearing fields; Velt keeps stubs for cleanup; both halves are needed):** +**Correct (your DB stores the PII-bearing fields; Velt keeps `{ attachmentId, name }` stubs; both halves are needed):** ```tsx const saveRecorder = async (request) => { @@ -1873,7 +1873,6 @@ const saveRecorder = async (request) => { // partial.transcription → entire object, your DB only // partial.from → full User object (PII) // partial.attachments[] → full attachment objects including url - // partial.chunkUrls → full map // partial.recordingEditVersions → per-version PII (only versions with ≥1 PII field present) await db.upsertRecorderPII(annotationId, partial); } diff --git a/skills/velt-self-hosting-data-best-practices/AGENTS.md b/skills/velt-self-hosting-data-best-practices/AGENTS.md index 3a86911..25dab53 100644 --- a/skills/velt-self-hosting-data-best-practices/AGENTS.md +++ b/skills/velt-self-hosting-data-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Self Hosting Data Best Practices -|v1.0.5|Velt|March 2026 +|v1.0.8|Velt|March 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules diff --git a/skills/velt-self-hosting-data-best-practices/metadata.json b/skills/velt-self-hosting-data-best-practices/metadata.json index 8ef754c..d042d64 100644 --- a/skills/velt-self-hosting-data-best-practices/metadata.json +++ b/skills/velt-self-hosting-data-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.0.5", + "version": "1.0.8", "organization": "Velt", "date": "March 2026", "abstract": "Comprehensive guide for Velt self-hosting data feature, enabling storage of sensitive user-generated content (comments, attachments, reactions, recordings, user PII) on your own infrastructure. Covers endpoint-based and function-based data providers, VeltProvider dataProviders configuration, backend API route patterns, database schemas, file storage, retry and timeout configuration, and debugging. All guidance is evidence-backed from official Velt documentation and sample applications.", diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/attachment/attachment-multipart-provider.md b/skills/velt-self-hosting-data-best-practices/rules/shared/attachment/attachment-multipart-provider.md index 4cfcf7a..5cd0e63 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/attachment/attachment-multipart-provider.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/attachment/attachment-multipart-provider.md @@ -295,16 +295,16 @@ interface SaveAttachmentResolverData { url: string; } // persisted back onto A The JSON `request` body in URL (endpoint) mode is exactly `{ attachment: { attachmentId, name, mimeType }, metadata, event }` — the `File` is destructured out and sent as a separate multipart binary part to your storage, **never to Velt**. On delete, Velt sends `{ attachmentId, metadata: { apiKey, documentId, organizationId, folderId? }, event }` where `event` is `ATTACHMENT_DELETE` (`"attachment.delete"`). -What persists on Velt's side after a successful save (everything except the binary bytes): `attachmentId` (PK), `name`, `bucketPath`, `size`, `type`, `url` (the URL **your** storage returned), `thumbnail`, `thumbnailWithPlayIconUrl`, `metadata` (arbitrary), `mimeType`, `previewImages`, and the `isAttachmentResolverUsed` flag. The `url` is the only field that comes from your `save` response; the rest are structural. +What persists on Velt's side after a successful save (everything except the binary bytes): `attachmentId` (PK), `name`, `size`, `type`, `url` (the URL **your** storage returned), `thumbnail`, `thumbnailWithPlayIconUrl`, `metadata` (arbitrary), `mimeType`, `previewImages`, and the `isAttachmentResolverUsed` flag. The `url` is the only field that comes from your `save` response; the rest are structural. **Incorrect (assuming `request.attachment` is a full `Attachment` object — only three sub-fields are guaranteed):** ```tsx const saveAttachment = async (request: SaveAttachmentResolverRequest) => { - // BUG: `bucketPath`, `size`, `thumbnail`, `previewImages` are not in `request.attachment`. + // BUG: `size`, `thumbnail`, `previewImages` are not in `request.attachment`. // Velt computes those on its side from the `{ url }` you return plus the binary it just handed you. - const { attachmentId, name, mimeType, bucketPath, size } = request.attachment as any; - const url = await storage.put(request.file, { bucketPath }); // `bucketPath` is undefined + const { attachmentId, name, mimeType, size, thumbnail } = request.attachment as any; + const url = await storage.put(request.file, { size }); // `size` is undefined return { data: { url }, success: true, statusCode: 200 }; }; ``` @@ -329,7 +329,7 @@ const saveAttachment = async (request: SaveAttachmentResolverRequest) => { - [ ] Timeout is longer than for other providers (file upload latency) - [ ] Delete handler reads `attachmentId` from the top-level request field, not from `metadata` - [ ] Comment attachments wired on `dataProviders.attachment`; recording files wired on `dataProviders.recorder.storage` — separate scopes, never collapsed -- [ ] Save handler only reads `attachmentId`, `name`, `mimeType` from `request.attachment` — does not assume `bucketPath` / `size` / `thumbnail` etc. are present (Velt populates those from the returned `url` and the binary) +- [ ] Save handler only reads `attachmentId`, `name`, `mimeType` from `request.attachment` — does not assume `size` / `thumbnail` / `previewImages` etc. are present (Velt populates those from the returned `url` and the binary) - [ ] `event` is treated as one of `ResolverActions` (`ATTACHMENT_ADD` / `ATTACHMENT_DELETE`); handlers gate side effects on it rather than HTTP method alone **Source Pointer:** https://docs.velt.dev/self-host-data/attachments - Endpoint-Based, Function-Based; https://docs.velt.dev/self-host-data/overview - "Attachment & recording storage"; https://docs.velt.dev/self-host-data/field-inventory - "Attachments" diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md b/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md index 1facb4b..1340d6a 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md @@ -279,12 +279,12 @@ interface PartialComment { interface PartialReactionAnnotation { annotationId: string; // join key metadata?: BaseMetadata; // getClientMetadata(annotation.metadata ?? {}) - icon: string; // the only field never sent to Velt + icon: string; // the only relocated field — never sent to Velt from?: PartialUser; // { userId }; copied-not-moved } ``` -`iconUrl` and `iconEmoji` (custom reaction icon variants) are **kept** — they are sent to Velt verbatim. Only the emoji-code `icon` is withheld. +`icon` is the only field withheld from Velt; the strip operates on the emoji-code `icon` only. Per-element `Reaction` entries on the Velt-side `reactions[]` carry their own `variant` field — they are kept verbatim and are not part of the `Partial` payload. #### `PartialRecorderAnnotation` (your DB) @@ -295,8 +295,7 @@ interface PartialRecorderAnnotation { from?: User; // full user object (PII); deep-cloned transcription?: Transcription; // entire object → your DB, never sent to Velt attachment?: Attachment | null; // @deprecated; value written as null on Velt's side - attachments?: Attachment[]; // full list incl. URLs — Velt keeps only stubs { attachmentId, name, bucketPath } - chunkUrls?: Record; // full map → your DB; Velt's side written as {} + attachments?: Attachment[]; // full list incl. URLs — Velt keeps only stubs { attachmentId, name } recordingEditVersions?: Record; isUrlAvailable?: boolean; // copied-not-moved // plus config.additionalFields @@ -312,7 +311,7 @@ interface Transcription { } ``` -`bucketPath` inside `attachments[]` is deliberately **kept** in Velt's stub form so Velt can clean up storage; `url` and the other PII fields are stripped. +Velt keeps each `attachments[]` entry as a stub `{ attachmentId, name }`; `url` and the other PII fields are stripped. #### `PartialNotification` (your DB — custom notifications only) @@ -379,7 +378,7 @@ interface AttachmentResolverMetadata { interface SaveAttachmentResolverData { url: string; } ``` -There is no `Partial` strip and no `get` for attachments — they are binary files. The `file` is destructured out and sent as binary to your storage; the JSON request body is exactly `{ attachment: { attachmentId, name, mimeType }, metadata, event }`. Velt receives the returned `url` plus the structural fields on the `Attachment` record (`bucketPath`, `size`, `type`, `thumbnail`, etc.). +There is no `Partial` strip and no `get` for attachments — they are binary files. The `file` is destructured out and sent as binary to your storage; the JSON request body is exactly `{ attachment: { attachmentId, name, mimeType }, metadata, event }`. Velt receives the returned `url` plus the structural fields on the `Attachment` record (`size`, `type`, `thumbnail`, etc.). ### Shared building blocks @@ -503,6 +502,6 @@ The SDK sets these on the Velt-side record whenever PII was withheld for the cor - [ ] Partial types include only the PII fields stored on your infrastructure - [ ] `BaseMetadata` payloads sent to your DB go through `getClientMetadata` (raw `clientDocumentId` → `documentId`) - [ ] `TargetElement.targetText` is kept (sent to Velt); `targetTextRange.text` is stripped to your DB only -- [ ] Recorder attachment stubs preserve `bucketPath` for Velt-side storage cleanup; `url` is never sent to Velt +- [ ] Recorder attachment stubs are reduced to `{ attachmentId, name }`; `url` is never sent to Velt **Source Pointer:** https://docs.velt.dev/api-reference/sdk/models/data-models - Self-Hosting Types; https://docs.velt.dev/self-host-data/field-inventory - "Complete Field Inventory" diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-notification.md b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-notification.md index 073ac2e..542340a 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-notification.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-notification.md @@ -164,7 +164,7 @@ The notification provider is unusual: **read-only enrichment**. There is no writ - **Client-computed fields** (`isUnread`, `forYou`, the rendered `displayHeadlineMessage`) are recomputed on the client from `notificationViews` / `notifyUsers*` / the templates and stored in **neither DB**. - **Resolution order is notification → user → comment.** Notification PII fills userIds that the user resolver then enriches. - **Delete payload is minimal.** Velt calls your provider with `{ notificationId, organizationId }`. -- **`notifyUsers` / `notifyUsersByUserId` are keyed by hashes,** not raw emails / userIds. The hash keys are kept on Velt's side; the raw identifiers are not. +- **`notifyUsers` / `notifyUsersByUserId` are keyed by hashes,** not raw emails / userIds. On Velt's DB `notifyUsers` is a map `{ [emailHash]: boolean }` and `notifyUsersByUserId` is a map `{ [userIdHash]: boolean }` — not the array-of-`{ userId, email }` shape you POST when *writing* a notification. The hash keys are kept on Velt's side; the raw identifiers are not. **Incorrect (implementing a `save` handler that never fires — silent dead code):** diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-reaction-recording.md b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-reaction-recording.md index 56ec037..b78d658 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-reaction-recording.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-reaction-recording.md @@ -175,6 +175,7 @@ The reaction strip is intentionally narrow — only the emoji-code `icon` is wit - **`from` is copied-not-moved** — both your DB and Velt's DB receive `from`. The per-element `reactions[].from` is reduced to `{ userId }` (when the `user` provider is active) **only inside Velt's DB** — it is not part of the `Partial` payload. - **`position`'s value is never sent to Velt** — written as `null` on every write to Velt's DB regardless of self-hosting. This is independent of the reaction resolver. - **Unchanged save short-circuits.** A deep-compare against the cache decides whether to strip at all. If nothing changed, the icon is not re-processed and `isReactionResolverUsed` is not set — your `save` handler is not called either. +- **No `fieldsToRemove` on the reaction resolver.** The reaction resolver config supports only `additionalFields` (extra `ReactionAnnotation` fields to include in resolver payloads) — there is no `fieldsToRemove`, because the only PII is the emoji-code `icon`, which is stripped automatically. **Incorrect (treating `iconUrl` as PII and writing it to your DB instead of Velt's):** diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-recorder.md b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-recorder.md index 8e33a0c..4a82320 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-recorder.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-recorder.md @@ -128,12 +128,11 @@ const recorderStorage: AttachmentDataProvider = { ### Recorder strip rules -The recorder splits along a few axes simultaneously — transcript, attachment binaries, chunk URLs, and per-version edit history — and the rules differ for each. Important: the recorder **metadata** resolver and the recorder **file** storage (`recorder.storage`) are independent toggles. Recording files stay on Velt unless you also set `recorder.storage`. +The recorder splits along a few axes simultaneously — transcript, attachment binaries, and per-version edit history — and the rules differ for each. Important: the recorder **metadata** resolver and the recorder **file** storage (`recorder.storage`) are independent toggles. Recording files stay on Velt unless you also set `recorder.storage`. - **`transcription`** — the entire object is **never sent to Velt** when the recorder resolver is active (→ your DB). It is present in Velt's DB **only** when no recorder resolver is set. - **`attachment`** (deprecated single) — the value is **never sent to Velt** (written as `null` there). The full object goes to your DB. -- **`attachments[]`** — Velt's DB keeps **stubs only**: `{ attachmentId, name, bucketPath }`. `url` and the binary-pointing fields are stripped. `bucketPath` is deliberately preserved so Velt can perform server-side storage cleanup. -- **`chunkUrls`** — the value is **never sent to Velt** (written as `{}` there); the full chunk-URL map goes to your DB. +- **`attachments[]`** — Velt's DB keeps **stubs only**: `{ attachmentId, name }`. `url` and the binary-pointing fields are stripped (Velt no longer retains `bucketPath`). - **`from`** — **reduced** to `{ userId }` in Velt's DB; the full user object (name / email / `photoUrl`) goes to your DB only. - **`recordingEditVersions`** — per-version PII is stripped the same way (`from` → `{ userId }`, `attachment` → `null`, `attachments` → stubs, `transcription` never sent). Non-PII per-version fields (`recordedTime`, `waveformData`, `displayName`, `boundedTrimRanges`, `boundedScaleRanges`) are **kept** in Velt's DB. - **Top-level `displayName` / `waveformData` / `recordedTime`** — sent to Velt verbatim; not part of the `Partial` payload to your DB. @@ -143,9 +142,9 @@ The recorder splits along a few axes simultaneously — transcript, attachment b ```tsx const saveRecorder = async (request) => { - // BUG: Velt still tracks { attachmentId, name, bucketPath } stubs for storage cleanup. + // BUG: Velt still tracks { attachmentId, name } stubs for each attachment. // If your DB is the only source of truth for attachment IDs, you risk orphaning bucket objects - // because Velt expects bucketPath to round-trip through the stub. + // because Velt no longer retains a storage path back to your bucket. for (const partial of Object.values(request.recorderAnnotations)) { await db.saveAttachments(partial.attachments); // assumes Velt has nothing — wrong } @@ -153,7 +152,7 @@ const saveRecorder = async (request) => { }; ``` -**Correct (your DB stores the PII-bearing fields; Velt keeps stubs for cleanup; both halves are needed):** +**Correct (your DB stores the PII-bearing fields; Velt keeps `{ attachmentId, name }` stubs; both halves are needed):** ```tsx const saveRecorder = async (request) => { @@ -161,7 +160,6 @@ const saveRecorder = async (request) => { // partial.transcription → entire object, your DB only // partial.from → full User object (PII) // partial.attachments[] → full attachment objects including url - // partial.chunkUrls → full map // partial.recordingEditVersions → per-version PII (only versions with ≥1 PII field present) await db.upsertRecorderPII(annotationId, partial); } @@ -176,7 +174,7 @@ const saveRecorder = async (request) => { - [ ] Longer resolveTimeout set for media operations (20-30s) - [ ] storage provider configured if media files should be stored on your infrastructure - [ ] Provider set before identify() -- [ ] `attachments[]` round-trip preserves Velt-side stubs `{ attachmentId, name, bucketPath }` so Velt can clean up storage +- [ ] `attachments[]` round-trip preserves Velt-side stubs `{ attachmentId, name }` (Velt no longer stores `bucketPath`) - [ ] `recordingEditVersions` per-version PII is treated as optional (versions without PII are absent from the payload) **Source Pointer:** https://docs.velt.dev/self-host-data/recordings; https://docs.velt.dev/self-host-data/field-inventory - "Recorder strip rules" From 2dd5c8ccd7846bad4d15ef0e6b0a94357139d309 Mon Sep 17 00:00:00 2001 From: "agent-skills-sync[bot]" Date: Wed, 24 Jun 2026 20:34:45 +0000 Subject: [PATCH 2/2] sync: update agent-skills from velt-js/agent-skills [automated] --- .../AGENTS.full.md | 53 +- .../AGENTS.md | 4 +- .../SKILL.md | 2 + .../metadata.json | 5 +- .../rules/shared/_sections.md | 2 +- .../concepts/concepts-workflow-model.md | 40 +- .../webhooks/webhooks-inbound-handler.md | 59 ++ .../AGENTS.full.md | 836 +++++++++++++++++- skills/velt-comments-best-practices/AGENTS.md | 4 +- skills/velt-comments-best-practices/SKILL.md | 6 +- .../metadata.json | 15 +- .../rules/react/mode/mode-apryse.md | 160 ++++ .../rules/shared/config/config-ui-behavior.md | 18 +- .../shared/data/data-agent-fields-query.md | 19 +- .../rules/shared/data/data-types-reference.md | 28 +- .../shared/surface/surface-sidebar-setup.md | 2 - .../shared/surface/surface-sidebar-v2.md | 248 +++++- .../rules/shared/surface/surface-sidebar.md | 119 ++- .../shared/ui/ui-autocomplete-primitives.md | 43 +- .../rules/shared/ui/ui-comment-dialog.md | 192 +++- .../rules/shared/ui/ui-v2-primitives.md | 181 +++- .../rules/shared/ui/ui-wireframes.md | 73 ++ .../wireframe-variables-comment-dialog.md | 5 +- .../wireframe-variables-comment-sidebar.md | 59 +- .../AGENTS.full.md | 180 +++- skills/velt-node-sdk-best-practices/AGENTS.md | 8 +- skills/velt-node-sdk-best-practices/SKILL.md | 5 +- .../metadata.json | 4 +- .../rules/shared/_sections.md | 6 +- .../shared/api/api-envelope-and-services.md | 20 +- .../rules/shared/api/api-field-allowlist.md | 79 ++ .../selfhost-lazy-load-and-services.md | 26 + .../AGENTS.full.md | 54 +- .../AGENTS.md | 2 +- .../SKILL.md | 2 +- .../metadata.json | 5 +- .../config/config-cross-organization.md | 9 +- .../panel/panel-current-document-only.md | 27 +- .../rules/shared/panel/panel-display.md | 3 +- .../rules/shared/panel/panel-tabs.md | 31 +- .../AGENTS.full.md | 175 +++- .../velt-rest-apis-best-practices/AGENTS.md | 4 +- skills/velt-rest-apis-best-practices/SKILL.md | 4 +- .../metadata.json | 26 +- .../rules/shared/_sections.md | 2 +- .../rules/shared/rest-api/rest-agents.md | 72 ++ .../rules/shared/rest-api/rest-memory.md | 99 +++ .../AGENTS.full.md | 112 ++- .../AGENTS.md | 2 +- .../SKILL.md | 25 +- .../metadata.json | 5 +- .../rules/shared/data/data-types-reference.md | 4 +- .../shared/provider/provider-activity.md | 60 +- .../shared/provider/provider-retry-timeout.md | 11 +- .../python-sdk/python-rest-api-backend.md | 8 +- .../python-sdk/python-users-reactions.md | 90 ++ .../velt-setup-best-practices/AGENTS.full.md | 58 +- skills/velt-setup-best-practices/AGENTS.md | 4 +- .../velt-setup-best-practices/metadata.json | 2 +- .../document-identity/document-page-info.md | 61 ++ 60 files changed, 3241 insertions(+), 217 deletions(-) create mode 100644 skills/velt-approval-engine-best-practices/rules/shared/webhooks/webhooks-inbound-handler.md create mode 100644 skills/velt-comments-best-practices/rules/react/mode/mode-apryse.md create mode 100644 skills/velt-node-sdk-best-practices/rules/shared/api/api-field-allowlist.md create mode 100644 skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-agents.md create mode 100644 skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-memory.md create mode 100644 skills/velt-setup-best-practices/rules/shared/document-identity/document-page-info.md diff --git a/skills/velt-approval-engine-best-practices/AGENTS.full.md b/skills/velt-approval-engine-best-practices/AGENTS.full.md index 92e5b7a..4c7fa51 100644 --- a/skills/velt-approval-engine-best-practices/AGENTS.full.md +++ b/skills/velt-approval-engine-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Approval Engine Best Practices -**Version 1.0.0** +**Version 1.0.2** Velt May 2026 @@ -31,7 +31,8 @@ Velt Approval Engine implementation guide covering the declarative workflow runt - 2.5 [Steps endpoints — recordReviewerDecision, recordAgentResolution, cancel (admin), resolve (admin)](#25-steps-endpoints-recordreviewerdecision-recordagentresolution-cancel-admin-resolve-admin) 3. [Webhooks](#3-webhooks) — **HIGH** - - 3.1 [Webhook delivery — HMAC verification on raw bytes, payload shape, event catalog with data highlights, retry schedule, idempotency on (executionId, seq)](#31-webhook-delivery-hmac-verification-on-raw-bytes-payload-shape-event-catalog-with-data-highlights-retry-schedule-idempotency-on-executionid-seq) + - 3.1 [Inbound webhook handler — raw JSON ingress with bearer auth, signed callbacks, rate/size limits, SSRF guard](#31-inbound-webhook-handler-raw-json-ingress-with-bearer-auth-signed-callbacks-ratesize-limits-ssrf-guard) + - 3.2 [Webhook delivery — HMAC verification on raw bytes, payload shape, event catalog with data highlights, retry schedule, idempotency on (executionId, seq)](#32-webhook-delivery-hmac-verification-on-raw-bytes-payload-shape-event-catalog-with-data-highlights-retry-schedule-idempotency-on-executionid-seq) --- @@ -58,6 +59,11 @@ agent Runs an agent. Non-blocking by default (completes asynchronously with human Requires reviewer approval. Drives via /steps/recordReviewerDecision. Parks in "waiting" until aggregator resolves. + +webhook Deferred in v1. The `webhook` type passes definition validation (so authors can + draft graphs that will rely on it later), but the runtime handler is NOT enabled — + a `webhook` node will not run today. Treat it as a forward-compatibility hook, + not a runnable surface. ``` **Agent node shape:** @@ -870,9 +876,47 @@ The `action` field (REQUIRED) is the central discriminator. It determines both t **Impact: HIGH** -Inbound webhook delivery — required HMAC-SHA256 signature verification on the raw bytes (not re-serialized JSON), the three `x-velt-*` headers, the full payload field shape, the event catalog with `data` highlights per event type (including new `loop.iteration-started` and `loop.exhausted` events), the retry schedule (immediate → +2s → +8s → +32s → +2min → +8min → DLQ), the at-least-once delivery contract with idempotency on `(executionId, seq)`, the cancellation reason vocabulary, and missed-event recovery via `/executions/getEvents?sinceSeq=N`. +The two webhook surfaces of the Approval Engine. Outbound delivery (`webhooks-delivery`) — required HMAC-SHA256 signature verification on the raw bytes (not re-serialized JSON), the three `x-velt-*` headers, the full payload field shape, the event catalog with `data` highlights per event type (including new `loop.iteration-started` and `loop.exhausted` events), the retry schedule (immediate → +2s → +8s → +32s → +2min → +8min → DLQ), the at-least-once delivery contract with idempotency on `(executionId, seq)`, the cancellation reason vocabulary, and missed-event recovery via `/executions/getEvents?sinceSeq=N`. Inbound ingress (`webhooks-inbound-handler`) — a stable HTTP endpoint external systems POST raw JSON to (no `{data:...}` envelope), with bearer-token auth, signed callback tokens, per-source rate limiting, body-size limits, and an SSRF URL guard. + +### 3.1 Inbound webhook handler — raw JSON ingress with bearer auth, signed callbacks, rate/size limits, SSRF guard + +**Impact: MEDIUM-HIGH (External systems POST raw JSON directly (no {data:...} envelope); skipping the bearer token or assuming the standard REST envelope makes every inbound call fail)** + +In addition to outbound delivery (`webhooks-delivery`), the Approval Engine exposes an **inbound** webhook handler: an HTTP endpoint with a stable URL that external systems (Salesforce, Stripe, GitHub, etc.) POST raw JSON to in order to push events *into* the engine. It is complementary to — not a duplicate of — outbound delivery; both paths are active independently and can coexist on the same workflow. + +The key shape difference from the standard REST API: inbound payloads are **not** wrapped in a `{ data: ... }` envelope. The handler accepts raw JSON bodies directly. + +**Incorrect (wrapping the body in the REST `{data:...}` envelope and omitting the bearer token):** + +```text +{ + "data": { "event": "deal.closed", "dealId": "abc123" } +} +POST +Content-Type: application/json +// no Authorization header → rejected before any processing +``` + +**Correct (raw JSON body + bearer token):** + +```json +POST +Content-Type: application/json +Authorization: Bearer +{ "event": "deal.closed", "dealId": "abc123" } +``` + +The inbound handler enforces, at the boundary: +- **Bearer-token validation** — requests must carry a valid bearer token; unauthenticated requests are rejected before any processing. +- **Signed callback tokens** — callbacks issued by the handler are signed so downstream consumers can verify authenticity end-to-end. +- **Rate limiting** — per-source request rate is capped to prevent abuse. +- **Body-size limits** — oversized payloads are rejected before parsing. +- **SSRF URL guard** — any URL values in the incoming payload are validated against an allowlist to block server-side request forgery. +Keep the two surfaces straight: **inbound** = external systems push events *into* the engine (this rule); **outbound** = the engine pushes state-change events *out* to your receiver via the per-dispatch `webhookUrl` + `webhookSecret` (`webhooks-delivery`). This is also distinct from the deferred `node.type === "webhook"` step type, which validates in a definition but does not run in v1 (`concepts-workflow-model`). + +--- -### 3.1 Webhook delivery — HMAC verification on raw bytes, payload shape, event catalog with data highlights, retry schedule, idempotency on (executionId, seq) +### 3.2 Webhook delivery — HMAC verification on raw bytes, payload shape, event catalog with data highlights, retry schedule, idempotency on (executionId, seq) **Impact: HIGH (Wrong signature verification (hashing re-serialized JSON instead of raw bytes) lets forged requests through; missing seq-based idempotency double-processes every retried event)** @@ -1035,3 +1079,4 @@ The receiver and the reconciler must share the same dedup logic (same `(executio - https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/definitions/create-definition - https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/executions/dispatch-execution - https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/steps/record-reviewer-decision +- https://docs.velt.dev/ai/approval-engine/overview#inbound-webhook-handler diff --git a/skills/velt-approval-engine-best-practices/AGENTS.md b/skills/velt-approval-engine-best-practices/AGENTS.md index f89f28f..9ff955d 100644 --- a/skills/velt-approval-engine-best-practices/AGENTS.md +++ b/skills/velt-approval-engine-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Approval Engine Best Practices -|v1.0.0|Velt|May 2026 +|v1.0.2|Velt|May 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules @@ -10,4 +10,4 @@ |shared/rest:{rest-foundations.md,rest-definitions.md,rest-executions.md,rest-object-views.md,rest-steps.md} ## 3. Webhooks — HIGH -|shared/webhooks:{webhooks-delivery.md} +|shared/webhooks:{webhooks-inbound-handler.md,webhooks-delivery.md} diff --git a/skills/velt-approval-engine-best-practices/SKILL.md b/skills/velt-approval-engine-best-practices/SKILL.md index 0a028b6..9b413c5 100644 --- a/skills/velt-approval-engine-best-practices/SKILL.md +++ b/skills/velt-approval-engine-best-practices/SKILL.md @@ -47,6 +47,7 @@ For general Velt REST API patterns (Comments, Notifications, Activity, etc.), se ### Webhooks (HIGH) - `webhooks-delivery` — HMAC-SHA256 signature verification on raw bytes, delivery headers, retry schedule, event-type catalog (now includes `loop.iteration-started` and `loop.exhausted`), at-least-once idempotency +- `webhooks-inbound-handler` — inbound HTTP endpoint that external systems POST raw JSON to (no `{data: ...}` envelope), bearer-token auth, signed callback tokens, per-source rate limiting, body-size limits, SSRF URL guard; distinct from outbound delivery and from the deferred `node.type === "webhook"` ## How to Use @@ -60,6 +61,7 @@ rules/shared/rest/rest-executions.md rules/shared/rest/rest-steps.md rules/shared/rest/rest-object-views.md rules/shared/webhooks/webhooks-delivery.md +rules/shared/webhooks/webhooks-inbound-handler.md ``` Each rule file contains: diff --git a/skills/velt-approval-engine-best-practices/metadata.json b/skills/velt-approval-engine-best-practices/metadata.json index 5c87c34..8c4a9d8 100644 --- a/skills/velt-approval-engine-best-practices/metadata.json +++ b/skills/velt-approval-engine-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.0.2", "organization": "Velt", "date": "May 2026", "abstract": "Velt Approval Engine implementation guide covering the declarative workflow runtime for multi-step agent + human approval processes — the 14 REST endpoints under /v2/workflow/*, the definition model (nodes + edges + parallel-quorum groups), execution dispatch with idempotency, HMAC-SHA256 webhook signature verification, missed-event recovery via getEvents+sinceSeq, SLA breach edges, and the quorum policies (waitAll / cancelOnQuorum / joinOnQuorum).", @@ -10,6 +10,7 @@ "https://docs.velt.dev/ai/approval-engine/customize-behavior", "https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/definitions/create-definition", "https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/executions/dispatch-execution", - "https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/steps/record-reviewer-decision" + "https://docs.velt.dev/api-reference/rest-apis/v2/approval-engine/steps/record-reviewer-decision", + "https://docs.velt.dev/ai/approval-engine/overview#inbound-webhook-handler" ] } diff --git a/skills/velt-approval-engine-best-practices/rules/shared/_sections.md b/skills/velt-approval-engine-best-practices/rules/shared/_sections.md index f4e4a8a..08c105a 100644 --- a/skills/velt-approval-engine-best-practices/rules/shared/_sections.md +++ b/skills/velt-approval-engine-best-practices/rules/shared/_sections.md @@ -22,4 +22,4 @@ The section prefix (in parentheses) is the filename prefix used to group rules. ## 3. Webhooks (webhooks) **Impact:** HIGH -**Description:** Inbound webhook delivery — required HMAC-SHA256 signature verification on the raw bytes (not re-serialized JSON), the three `x-velt-*` headers, the full payload field shape, the event catalog with `data` highlights per event type (including new `loop.iteration-started` and `loop.exhausted` events), the retry schedule (immediate → +2s → +8s → +32s → +2min → +8min → DLQ), the at-least-once delivery contract with idempotency on `(executionId, seq)`, the cancellation reason vocabulary, and missed-event recovery via `/executions/getEvents?sinceSeq=N`. +**Description:** The two webhook surfaces of the Approval Engine. Outbound delivery (`webhooks-delivery`) — required HMAC-SHA256 signature verification on the raw bytes (not re-serialized JSON), the three `x-velt-*` headers, the full payload field shape, the event catalog with `data` highlights per event type (including new `loop.iteration-started` and `loop.exhausted` events), the retry schedule (immediate → +2s → +8s → +32s → +2min → +8min → DLQ), the at-least-once delivery contract with idempotency on `(executionId, seq)`, the cancellation reason vocabulary, and missed-event recovery via `/executions/getEvents?sinceSeq=N`. Inbound ingress (`webhooks-inbound-handler`) — a stable HTTP endpoint external systems POST raw JSON to (no `{data:...}` envelope), with bearer-token auth, signed callback tokens, per-source rate limiting, body-size limits, and an SSRF URL guard. diff --git a/skills/velt-approval-engine-best-practices/rules/shared/concepts/concepts-workflow-model.md b/skills/velt-approval-engine-best-practices/rules/shared/concepts/concepts-workflow-model.md index a264eee..7d1febe 100644 --- a/skills/velt-approval-engine-best-practices/rules/shared/concepts/concepts-workflow-model.md +++ b/skills/velt-approval-engine-best-practices/rules/shared/concepts/concepts-workflow-model.md @@ -2,7 +2,7 @@ title: Approval Engine workflow model — nodes, edges, groups, quorum policies, loop regions, and step IDs impact: HIGH impactDescription: Every REST payload carries these shapes; misunderstanding them produces either INVALID_ARGUMENT linter failures at create time or stuck-forever executions at runtime -tags: approval-engine, workflow, definition, nodes, edges, groups, quorum, agent, human, reviewers, reviewerIds, slaMs, onQuorumMet, requiredNodeIds, stepId, loops, onReject, reviewerEmails, commentBody +tags: approval-engine, workflow, definition, nodes, edges, groups, quorum, agent, human, webhook, reviewers, reviewerIds, slaMs, onQuorumMet, requiredNodeIds, stepId, loops, onReject, reviewerEmails, commentBody, storeDbId, tenant-partitioning, __mock__, mock-agent --- ## Approval Engine workflow model — nodes, edges, groups, quorum policies, loop regions, and step IDs @@ -20,8 +20,23 @@ agent Runs an agent. Non-blocking by default (completes asynchronously with human Requires reviewer approval. Drives via /steps/recordReviewerDecision. Parks in "waiting" until aggregator resolves. + +webhook Deferred in v1. The `webhook` type passes definition validation (so authors can + draft graphs that will rely on it later), but the runtime handler is NOT enabled — + a `webhook` node will not run today. Treat it as a forward-compatibility hook, + not a runnable surface. ``` +**`webhook` vs. inbound handler vs. outbound delivery — three distinct surfaces:** + +The string `webhook` appears in three unrelated places in the Approval Engine. Do NOT conflate them: + +1. **`node.type === "webhook"`** — a *deferred* node type that validates in a definition but does not run in v1 (above). +2. **Inbound webhook handler** — a *live* HTTP endpoint that external systems POST raw JSON to (covered in `webhooks-inbound-handler`). +3. **Per-execution outbound delivery** — the `webhookUrl` + `webhookSecret` pair set on dispatch that pushes externally-visible events to your receiver (covered in `webhooks-delivery`). + +The inbound handler and outbound delivery are both live in v1 and active independently; the `webhook` node is not. + **Agent node shape:** ```json @@ -42,6 +57,10 @@ human Requires reviewer approval. Drives via /steps/recordReviewerDecision. Agent node config fields: `agentId` (required), `promptOverride` (≤ 8000 chars), `inputMapping` (object), `blocking` (default false), `resolutionPolicy` (**required when `blocking: true`**: `{ kind: "allResolved" | "minResolved", minCount?: integer }`; `minCount` is required when `kind === "minResolved"`), `agentMaxRuntimeMs` (≤ 86_400_000 / 24h), `requireNonEmptyOutput` (boolean). +**Reserved `__mock__` agent id:** + +`agentId: "__mock__"` is a reserved identifier that lets you run a definition end-to-end without registering a real agent. The engine accepts the node, treats the step as terminal, and lets the workflow advance — useful for testing the graph shape, edge expressions, and webhook receiver before any real agent code exists. Swap to a real `agentId` before production; `__mock__` does not invoke any agent runtime. + **Human node shape (new — preferred):** ```json @@ -279,8 +298,20 @@ Step: pending → running → (waiting) → completed | failed | skipped | // `waiting` applies only to human steps and blocking:true agent steps ``` +**Tenant partitioning — `storeDbId`:** + +Approval Engine state is partitioned per tenant: each tenant's executions, definitions, and events are routed to that tenant's dedicated `storeDbId`. Two operational consequences: + +- A definition created under tenant A is not visible to tenant B; an `executionId` from one tenant cannot be fetched, cancelled, or replayed under another tenant's API key. +- Recovery via `/executions/getEvents?sinceSeq=` reads from the partition that owns the execution. There is no cross-tenant event stream. + +Treat the partition boundary as a hard isolation boundary when designing multi-tenant integrations — never assume an ID is portable across tenants. + **Verification Checklist:** -- [ ] Node `type` is one of `agent` / `human` +- [ ] Node `type` is one of `agent` / `human` / `webhook` (the `webhook` type validates but does NOT run in v1 — don't depend on its runtime behavior) +- [ ] Code that switches on `webhook` does not confuse the deferred node type with the live [[webhooks-inbound-handler]] or the live outbound delivery covered in [[webhooks-delivery]] +- [ ] Test runs that use `agentId: "__mock__"` are swapped to a real `agentId` before production +- [ ] Multi-tenant integrations treat `storeDbId` as a hard isolation boundary — no IDs assumed portable across tenants - [ ] Every `human` node provides exactly one of `reviewers[]` or `reviewerIds[]` — never both - [ ] Every `human` node using the new shape has at least one `reviewers[].mandatory: true` - [ ] Every `human` node satisfies strict-mode: has `config.onReject` set OR is a member of a top-level `loops[]` body @@ -297,5 +328,6 @@ Step: pending → running → (waiting) → completed | failed | skipped | - [ ] Every `loops[]` entry: `entryNodeId` is in `bodyNodeIds`, `maxIterations` 1–20, no node appears in more than one loop body, `onExhausted.routeToNodeId` (if set) references a node outside the loop body **Source Pointers:** -- https://docs.velt.dev/ai/approval-engine/overview — concepts overview, step ID formats -- https://docs.velt.dev/ai/approval-engine/customize-behavior — full node configuration, edge expressions, quorum policies, SLAs +- https://docs.velt.dev/ai/approval-engine/overview — concepts overview, step ID formats, deferred `webhook` node type, tenant `storeDbId` partitioning +- https://docs.velt.dev/ai/approval-engine/customize-behavior — full node configuration, edge expressions, quorum policies, SLAs, deferred `webhook` node clarification vs. inbound/outbound surfaces +- https://docs.velt.dev/ai/approval-engine/setup — reserved `__mock__` agent id for end-to-end tests diff --git a/skills/velt-approval-engine-best-practices/rules/shared/webhooks/webhooks-inbound-handler.md b/skills/velt-approval-engine-best-practices/rules/shared/webhooks/webhooks-inbound-handler.md new file mode 100644 index 0000000..515e090 --- /dev/null +++ b/skills/velt-approval-engine-best-practices/rules/shared/webhooks/webhooks-inbound-handler.md @@ -0,0 +1,59 @@ +--- +title: Inbound webhook handler — raw JSON ingress with bearer auth, signed callbacks, rate/size limits, SSRF guard +impact: MEDIUM-HIGH +impactDescription: External systems POST raw JSON directly (no {data:...} envelope); skipping the bearer token or assuming the standard REST envelope makes every inbound call fail +tags: approval-engine, webhooks, inbound, raw-json, bearer-token, signed-callback, rate-limiting, body-size-limit, ssrf, salesforce, stripe, github +--- + +## Inbound webhook handler — raw JSON ingress, distinct from outbound delivery + +In addition to outbound delivery (`webhooks-delivery`), the Approval Engine exposes an **inbound** webhook handler: an HTTP endpoint with a stable URL that external systems (Salesforce, Stripe, GitHub, etc.) POST raw JSON to in order to push events *into* the engine. It is complementary to — not a duplicate of — outbound delivery; both paths are active independently and can coexist on the same workflow. + +The key shape difference from the standard REST API: inbound payloads are **not** wrapped in a `{ data: ... }` envelope. The handler accepts raw JSON bodies directly. + +**Incorrect (wrapping the body in the REST `{data:...}` envelope and omitting the bearer token):** + +```json +{ + "data": { "event": "deal.closed", "dealId": "abc123" } +} +``` + +```text +POST +Content-Type: application/json +// no Authorization header → rejected before any processing +``` + +**Correct (raw JSON body + bearer token):** + +```text +POST +Content-Type: application/json +Authorization: Bearer +``` + +```json +{ "event": "deal.closed", "dealId": "abc123" } +``` + +The inbound handler enforces, at the boundary: + +- **Bearer-token validation** — requests must carry a valid bearer token; unauthenticated requests are rejected before any processing. +- **Signed callback tokens** — callbacks issued by the handler are signed so downstream consumers can verify authenticity end-to-end. +- **Rate limiting** — per-source request rate is capped to prevent abuse. +- **Body-size limits** — oversized payloads are rejected before parsing. +- **SSRF URL guard** — any URL values in the incoming payload are validated against an allowlist to block server-side request forgery. + +Keep the two surfaces straight: **inbound** = external systems push events *into* the engine (this rule); **outbound** = the engine pushes state-change events *out* to your receiver via the per-dispatch `webhookUrl` + `webhookSecret` (`webhooks-delivery`). This is also distinct from the deferred `node.type === "webhook"` step type, which validates in a definition but does not run in v1 (`concepts-workflow-model`). + +**Verification Checklist:** +- [ ] Inbound POST bodies are raw JSON — NOT wrapped in `{ data: ... }` +- [ ] Every inbound request carries a valid `Authorization: Bearer ` — unauthenticated calls are rejected up front +- [ ] Callers verify the signed callback token on callbacks issued by the handler +- [ ] Senders stay within the per-source rate cap and body-size limit (oversized/abusive traffic is rejected at the boundary) +- [ ] Any URLs in the payload resolve to allowlisted hosts (SSRF guard rejects the rest) +- [ ] Inbound and outbound are treated as independent surfaces, not interchangeable + +**Source Pointers:** +- https://docs.velt.dev/ai/approval-engine/overview — "Inbound webhook handler" (raw JSON ingress, bearer auth, signed callbacks, rate/size limits, SSRF guard) diff --git a/skills/velt-comments-best-practices/AGENTS.full.md b/skills/velt-comments-best-practices/AGENTS.full.md index eef0847..2ef3656 100644 --- a/skills/velt-comments-best-practices/AGENTS.full.md +++ b/skills/velt-comments-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Comments Best Practices -**Version 1.1.5** +**Version 1.1.10** Velt January 2026 @@ -34,21 +34,22 @@ Comprehensive Velt Comments implementation guide covering comment modes, setup p - 3.4 [Add Comments to Nivo Charts](#34-add-comments-to-nivo-charts) - 3.5 [Add Data Point Comments to Highcharts](#35-add-data-point-comments-to-highcharts) - 3.6 [Integrate Comments with Ace Editor](#36-integrate-comments-with-ace-editor) - - 3.7 [Integrate Comments with CodeMirror Editor](#37-integrate-comments-with-codemirror-editor) - - 3.8 [Integrate Comments with Lexical Editor](#38-integrate-comments-with-lexical-editor) - - 3.9 [Integrate Comments with Plate Editor](#39-integrate-comments-with-plate-editor) - - 3.10 [Integrate Comments with Quill Editor](#310-integrate-comments-with-quill-editor) - - 3.11 [Integrate Comments with SlateJS Editor](#311-integrate-comments-with-slatejs-editor) - - 3.12 [Integrate Comments with TipTap Editor](#312-integrate-comments-with-tiptap-editor) - - 3.13 [Add Frame-by-Frame Comments to Lottie Animations](#313-add-frame-by-frame-comments-to-lottie-animations) - - 3.14 [Integrate Comments with Custom Video Player](#314-integrate-comments-with-custom-video-player) - - 3.15 [Use Freestyle Mode for Pin-Anywhere Comments](#315-use-freestyle-mode-for-pin-anywhere-comments) - - 3.16 [Use Inline Comments for Traditional Thread Style](#316-use-inline-comments-for-traditional-thread-style) - - 3.17 [Use Page Mode for Page-Level Comments](#317-use-page-mode-for-page-level-comments) - - 3.18 [Use Popover Mode for Table Cell Comments](#318-use-popover-mode-for-table-cell-comments) - - 3.19 [Use Prebuilt Video Player for Quick Setup](#319-use-prebuilt-video-player-for-quick-setup) - - 3.20 [Use Stream Mode for Google Docs-Style Comments](#320-use-stream-mode-for-google-docs-style-comments) - - 3.21 [Use Text Mode for Text Highlight Comments](#321-use-text-mode-for-text-highlight-comments) + - 3.7 [Integrate Comments with Apryse WebViewer](#37-integrate-comments-with-apryse-webviewer) + - 3.8 [Integrate Comments with CodeMirror Editor](#38-integrate-comments-with-codemirror-editor) + - 3.9 [Integrate Comments with Lexical Editor](#39-integrate-comments-with-lexical-editor) + - 3.10 [Integrate Comments with Plate Editor](#310-integrate-comments-with-plate-editor) + - 3.11 [Integrate Comments with Quill Editor](#311-integrate-comments-with-quill-editor) + - 3.12 [Integrate Comments with SlateJS Editor](#312-integrate-comments-with-slatejs-editor) + - 3.13 [Integrate Comments with TipTap Editor](#313-integrate-comments-with-tiptap-editor) + - 3.14 [Add Frame-by-Frame Comments to Lottie Animations](#314-add-frame-by-frame-comments-to-lottie-animations) + - 3.15 [Integrate Comments with Custom Video Player](#315-integrate-comments-with-custom-video-player) + - 3.16 [Use Freestyle Mode for Pin-Anywhere Comments](#316-use-freestyle-mode-for-pin-anywhere-comments) + - 3.17 [Use Inline Comments for Traditional Thread Style](#317-use-inline-comments-for-traditional-thread-style) + - 3.18 [Use Page Mode for Page-Level Comments](#318-use-page-mode-for-page-level-comments) + - 3.19 [Use Popover Mode for Table Cell Comments](#319-use-popover-mode-for-table-cell-comments) + - 3.20 [Use Prebuilt Video Player for Quick Setup](#320-use-prebuilt-video-player-for-quick-setup) + - 3.21 [Use Stream Mode for Google Docs-Style Comments](#321-use-stream-mode-for-google-docs-style-comments) + - 3.22 [Use Text Mode for Text Highlight Comments](#322-use-text-mode-for-text-highlight-comments) 4. [Standalone Components](#4-standalone-components) — **MEDIUM-HIGH** - 4.1 [Use Comment Pin for Manual Position Control](#41-use-comment-pin-for-manual-position-control) @@ -1250,7 +1251,124 @@ velt-comment-text { --- -### 3.7 Integrate Comments with CodeMirror Editor +### 3.7 Integrate Comments with Apryse WebViewer + +**Impact: HIGH (PDF and DOCX text comments in Apryse WebViewer with durable anchors and cleanup)** + +Use `@veltdev/apryse-velt-comments` when adding Velt text comments to Apryse WebViewer documents. The integration attaches to one WebViewer instance, renders existing Velt annotations back into Apryse, and stores durable text anchors that survive document edits and viewer/docxEditor mode switches. + +**Incorrect (using default text comments without the Apryse extension):** + +```jsx +// Default text mode cannot render selections inside the Apryse canvas. + + +
+ +``` + +**Correct (React / Next.js with the Apryse extension):** + +```jsx +npm install @veltdev/apryse-velt-comments @pdftron/webviewer +import { VeltProvider, VeltComments } from '@veltdev/react'; + + + + +import { useEffect, useRef, useState } from 'react'; +import { useCommentAnnotations } from '@veltdev/react'; +import { + ApryseVeltComments, + addComment, + renderComments, +} from '@veltdev/apryse-velt-comments'; + +function ApryseEditor() { + const viewerRef = useRef(null); + const instanceRef = useRef(null); + const extensionRef = useRef(null); + const [instance, setInstance] = useState(null); + const annotations = useCommentAnnotations(); + + useEffect(() => { + if (!viewerRef.current || instanceRef.current) return; + let cancelled = false; + + import('@pdftron/webviewer').then(({ default: WebViewer }) => { + if (cancelled) return; + WebViewer( + { + path: 'lib/webviewer', + licenseKey: 'YOUR_APRYSE_LICENSE_KEY', + initialDoc: '/your-document.docx', + initialMode: 'docxEditor', + }, + viewerRef.current, + ).then((webViewerInstance) => { + if (cancelled) return; + instanceRef.current = webViewerInstance; + extensionRef.current = ApryseVeltComments + .configure({ editorId: 'contract-viewer' }) + .attach(webViewerInstance); + setInstance(webViewerInstance); + }); + }); + + return () => { + cancelled = true; + extensionRef.current?.detach(); + extensionRef.current = null; + instanceRef.current = null; + setInstance(null); + }; + }, []); + + useEffect(() => { + if (instance && annotations) { + renderComments({ instance, commentAnnotations: annotations }); + } + }, [instance, annotations]); + + return ( + <> + +
+ + ); +} +``` + +`@pdftron/webviewer` is a peer dependency. Copy its `public/core` and `public/ui` runtime folders into your app's public assets, then point WebViewer's `path` option at that location. +**Step 2: Mount Velt comments with default text mode disabled** +**Step 3: Dynamically create WebViewer and attach the Velt extension** + +**Style Apryse highlights:** + +```css +velt-comment-text .velt-apryse-highlight { + background-color: rgba(60, 130, 246, 0.30) !important; + border-bottom: 2px solid rgba(60, 130, 246, 0.95) !important; +} + +velt-comment-text:hover .velt-apryse-highlight { + background-color: rgba(60, 130, 246, 0.50) !important; +} +``` + +--- + +### 3.8 Integrate Comments with CodeMirror Editor **Impact: MEDIUM (Text comments in CodeMirror code editor with highlight decorations)** @@ -1393,7 +1511,7 @@ velt-comment-text { --- -### 3.8 Integrate Comments with Lexical Editor +### 3.9 Integrate Comments with Lexical Editor **Impact: HIGH (Text comments in Lexical rich text editor with CommentNode)** @@ -1487,7 +1605,7 @@ velt-comment-text[comment-available="true"] { --- -### 3.9 Integrate Comments with Plate Editor +### 3.10 Integrate Comments with Plate Editor **Impact: MEDIUM (Text comments in Plate.js rich text editor with highlight marks)** @@ -1601,7 +1719,7 @@ velt-comment-text { --- -### 3.10 Integrate Comments with Quill Editor +### 3.11 Integrate Comments with Quill Editor **Impact: MEDIUM (Text comments in Quill rich text editor with highlight marks)** @@ -1733,7 +1851,7 @@ velt-comment-text { --- -### 3.11 Integrate Comments with SlateJS Editor +### 3.12 Integrate Comments with SlateJS Editor **Impact: HIGH (Text comments in SlateJS rich text editor with custom elements)** @@ -1828,7 +1946,7 @@ velt-comment-text[comment-available="true"] { --- -### 3.12 Integrate Comments with TipTap Editor +### 3.13 Integrate Comments with TipTap Editor **Impact: HIGH (Text comments in TipTap rich text editor with highlight marks)** @@ -1951,7 +2069,7 @@ velt-comment-text[comment-available="true"] { --- -### 3.13 Add Frame-by-Frame Comments to Lottie Animations +### 3.14 Add Frame-by-Frame Comments to Lottie Animations **Impact: HIGH (Comments synced to specific frames in Lottie animations)** @@ -2108,7 +2226,7 @@ commentElement.allowedElementIds(['lottiePlayerContainer']); --- -### 3.14 Integrate Comments with Custom Video Player +### 3.15 Integrate Comments with Custom Video Player **Impact: HIGH (Add comments to any video player with timeline and sidebar)** @@ -2252,7 +2370,7 @@ const onCommentClick = async (event) => { --- -### 3.15 Use Freestyle Mode for Pin-Anywhere Comments +### 3.16 Use Freestyle Mode for Pin-Anywhere Comments **Impact: HIGH (Default mode - enables clicking anywhere to pin comments)** @@ -2329,7 +2447,7 @@ export default function App() { --- -### 3.16 Use Inline Comments for Traditional Thread Style +### 3.17 Use Inline Comments for Traditional Thread Style **Impact: HIGH (Traditional comment threads bound to container elements)** @@ -2479,7 +2597,7 @@ export default function App() { --- -### 3.17 Use Page Mode for Page-Level Comments +### 3.18 Use Page Mode for Page-Level Comments **Impact: HIGH (Comments at page level via sidebar, not attached to elements)** @@ -2581,7 +2699,7 @@ function PageModeControls() { --- -### 3.18 Use Popover Mode for Table Cell Comments +### 3.19 Use Popover Mode for Table Cell Comments **Impact: HIGH (Google Sheets-style comments attached to specific elements)** @@ -2679,7 +2797,7 @@ export default function App() { --- -### 3.19 Use Prebuilt Video Player for Quick Setup +### 3.20 Use Prebuilt Video Player for Quick Setup **Impact: HIGH (Velt-provided video player with built-in commenting)** @@ -2724,7 +2842,7 @@ export default function App() { --- -### 3.20 Use Stream Mode for Google Docs-Style Comments +### 3.21 Use Stream Mode for Google Docs-Style Comments **Impact: HIGH (Comments appear in a side column synchronized with scroll position)** @@ -2780,7 +2898,7 @@ export default function App() { --- -### 3.21 Use Text Mode for Text Highlight Comments +### 3.22 Use Text Mode for Text Highlight Comments **Impact: HIGH (Comments attached to selected text, like Google Docs highlighting)** @@ -3383,8 +3501,6 @@ commentElement.toggleCommentSidebar(); import { VeltCommentsSidebarV2 } from '@veltdev/react'; -// or - ``` V2 replaces the per-category filter panel with a unified `FilterDropdown`. For V2 wireframe customization, see the Comment Sidebar V2 Structure docs. @@ -3395,7 +3511,7 @@ V2 replaces the per-category filter panel with a unified `FilterDropdown`. For V **Impact: MEDIUM-HIGH (Central panel for viewing, filtering, and navigating all comments)** -VeltCommentsSidebar provides a panel displaying all comments with search, filter, and navigation capabilities. Essential for any non-trivial commenting implementation. +`VeltCommentsSidebar` provides a panel displaying all comments with search, filter, and navigation capabilities. Essential for any non-trivial commenting implementation. The same `VeltCommentsSidebarProps` shape is reused by `VeltCommentsSidebarV2` — this rule is the prop catalog for both surfaces; for the V2-only declarative filter / sort surface, see `surface/surface-sidebar-v2.md`. **Basic Setup:** @@ -3455,14 +3571,13 @@ export default function App() { /> ``` -**V1 defers to V2 when both are mounted (v5.0.2-beta.13+):** +**V2 Sidebar Entry:** -```jsx -// Before (no longer valid — version prop removed): - +```html +import { VeltCommentsSidebarV2 } from '@veltdev/react'; -// After — use VeltCommentsSidebarV2 directly: + ``` **For HTML:** @@ -3494,6 +3609,8 @@ export default function App() { /> ``` +The React TypeScript interface; HTML attributes use the same names in kebab-case. All props are optional. Defaults reflect the current SDK surface — note in particular: `position` is narrowed from `string` to `'right' | 'left'`, and `forceClose` now defaults to `true` (the sidebar force-closes on outside click unless you explicitly set `forceClose={false}` — embed mode is unaffected). + --- ### 5.3 Use Sidebar Button to Toggle Comments Panel @@ -3577,9 +3694,9 @@ export default function App() { ### 5.4 Use VeltCommentsSidebarV2 for Primitive-Architecture Sidebar Customization -**Impact: MEDIUM-HIGH (Full composability of every sidebar UI section via 27+ independently importable primitives, enabling precise customization without forking the entire component)** +**Impact: MEDIUM-HIGH (Full composability of every sidebar UI section via 56+ independently importable primitives, enabling precise customization without forking the entire component)** -`VeltCommentsSidebarV2` is a complete redesign of the Comments Sidebar built on a flat primitive component architecture. Every section of the UI is an independently importable and composable primitive, so you can replace only the parts you need without reimplementing the whole component. V2 ships with a unified filter model (replacing the legacy `minimalFilter` + `advancedFilters` system), CDK virtual scroll for large comment lists, a focused-thread view, a minimal actions dropdown, and a filter dropdown. +`VeltCommentsSidebarV2` is a complete redesign of the Comments Sidebar built on a flat primitive component architecture. Every section of the UI is an independently importable and composable primitive, so you can replace only the parts you need without reimplementing the whole component. V2 ships with a declarative filter / sort / group model (three filter surfaces — main panel, mini funnel dropdown, multi-dropdown minimal bar), CDK virtual scroll for large comment lists, a focused-thread view, a fullscreen toggle, and a header search. **Incorrect (customizing V1 sidebar by overriding deeply nested internals):** @@ -3610,7 +3727,7 @@ export default function App() { readOnly={false} position="right" variant="sidebar" - forceClose={false} + forceClose={true} onSidebarOpen={(data) => console.log('sidebar opened', data)} onSidebarClose={(data) => console.log('sidebar closed', data)} onCommentClick={(data) => console.log('comment clicked', data)} @@ -3621,7 +3738,7 @@ export default function App() { } ``` -**Correct (HTML / Other Frameworks):** +**Correct (HTML / Other Frameworks — dedicated V2 web-component tag):** ```html ``` +`` / `VeltCommentsSidebarV2` is the only entry point documented by the V2 setup page. The old V1 component prop opt-in is no longer shown in `async-collaboration/comments-sidebar/v2/setup`. Mount the dedicated V2 tag directly; do not pair it with a V1 tag. + +**VeltCommentsSidebarV2 Props (core layout / event surface):** + +```typescript +// React — main filter panel + a multi-dropdown minimal bar + + + + +const commentElement = client.getCommentElement(); +const filtered: CommentAnnotation[] = commentElement.applyCommentSidebarClientFilters( + annotations, + filters, +); +// Filter field definition (panel sections + minimal-filter `filter` dropdowns) +interface FilterField { + field: string; // BuiltInFilterFieldId or custom id + label?: string; + select?: 'single' | 'multi'; + searchable?: boolean; + showCounts?: boolean; + icon?: string; + valuePath?: string; // dot-path for custom fields + includeUnset?: boolean; + placeholder?: string; + groupable?: boolean; + order?: string[]; + options?: SidebarFilterValue[]; +} + +// Single selectable option inside a FilterField — { id, label, count?, icon? } +interface SidebarFilterValue { /* id + display + optional count/icon */ } + +// One dropdown in the minimalFilters bar +interface SidebarMinimalFilterConfig { + type?: SidebarFilterDropdownType; // 'filter' | 'sort' | 'quick' | 'actions' + label?: string; + field?: string; + fields?: FilterField[]; // for type === 'filter' + sorts?: (string | SidebarSortConfig)[]; // for type === 'sort' or 'actions' + actions?: (string | SidebarQuickFilterConfig)[]; // for type === 'quick' or 'actions' +} + +// One sort option +interface SidebarSortConfig { + label?: string; + preset?: string; // 'date' | 'unread' | ... + path?: string; + field?: string; + order?: 'asc' | 'desc'; +} + +// One quick-filter predicate +interface SidebarQuickFilterConfig { + label?: string; + preset?: string; // 'open' | 'resolved' | 'unread' | ... + path?: string; + field?: string; + value?: any; + conditions?: SidebarQuickCondition[]; + operator?: 'and' | 'or'; +} + +interface SidebarQuickCondition { + path?: string; + field?: string; + value: any; +} + +// List grouping + flattened virtual-scroll rows +interface SidebarAnnotationGroup { + id: string; + label: string; + count: number; + isExpanded: boolean; + isCurrentPage?: boolean; + annotations: CommentAnnotation[]; +} + +type SidebarListRow = + | { type: 'group'; group: SidebarAnnotationGroup } + | { type: 'annotation'; annotation: CommentAnnotation; groupId: string }; + +// Operators + dropdown kinds +type FilterFieldOperator = 'and' | 'or'; +type SidebarFilterDropdownType = 'filter' | 'sort' | 'quick' | 'actions'; + +// Built-in field ids — recognized natively by the V2 filter pipeline +const BUILT_IN_FILTER_FIELD_IDS = [ + 'status', 'priority', 'category', 'people', 'assigned', + 'tagged', 'involved', 'location', 'version', 'document', +] as const; +type BuiltInFilterFieldId = typeof BUILT_IN_FILTER_FIELD_IDS[number]; + +// Section header chips + "All" toggle (panel-level controls) +type SectionControlChip = { id: string; label: string; isAll: boolean }; +type SectionAllOption = { show: boolean; label: string }; + +// Helper types for the resolved sort / quick pipelines +type SidebarSortCriterion = unknown; // resolved from SidebarSortConfig +type SidebarQuickPredicate = unknown; // resolved from SidebarQuickFilterConfig + +// Default sort surface (props sortBy / sortOrder) +type SortBy = string; +type SortOrder = 'asc' | 'desc'; + +// Custom-field resolver registration +interface FacetContext { + annotations: CommentAnnotation[]; + field: FilterField; +} + +interface FilterFieldResolver { + id: string; + optionSource: 'catalog' | 'scan'; + buildOptions: (ctx: FacetContext) => SidebarFilterValue[]; + matches: (annotation: CommentAnnotation, selectedValueIds: string[]) => boolean; +} +``` + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `filters` | `string \| FilterField[] \| object` | `[]` | Main Filter panel sections, OR an object of active selections that routes to the V1 `setCommentSidebarFilters` path. | +| `miniFilters` | `string \| FilterField[]` | `[]` | Single header funnel dropdown. | +| `minimalFilters` | `string \| SidebarMinimalFilterConfig[]` | `[]` | Multiple configurable header dropdowns. Replaces the single mini-filter funnel when present. | +| `filterOperator` | `'and' \| 'or'` | `'and'` | Cross-section combination of active filter selections. | +| `filterPanelLayout` | `'bottomSheet' \| 'menu'` | `'bottomSheet'` | Main Filter panel layout. | +| `filterOptionLayout` | `'dropdown' \| 'checkbox'` | `'dropdown'` | How options render within a filter section. | +| `filterCount` | boolean | `true` | Per-option facet counts. Disabling improves performance. | +| `filterGhostCommentsInSidebar` | boolean | `false` | Hide ghost comments from the list. | +| `systemFiltersOperator` | `'and' \| 'or'` | `'and'` | Operator applied to system filters. Mirrored by `applyCommentSidebarClientFilters()`. | +| `defaultMinimalFilter` | `'all' \| 'read' \| 'unread' \| 'resolved' \| 'open' \| 'assignedToMe' \| 'reset'` | — | Default active quick filter applied on load. | +| Prop | Type | Description | +|------|------|-------------| +| `sortBy` | [`SortBy`](#) | Default sort key — built-in preset (`'date'`, `'unread'`) or a dot-path (e.g. `'comments.createdAt'`). Sets the default sort; does not render a sort dropdown on its own. | +| `sortOrder` | [`SortOrder`](#) — `'asc' \| 'desc'` | Default sort direction. | +| `sortData` | string | Custom-field sort path used when sorting by a custom field. | +Apply a `CommentSidebarFilters` payload to an annotation array client-side, honoring the current `systemFiltersOperator`. Backs the V2 declarative filter pipeline; reach for it when filtering annotations outside the sidebar (custom previews, off-screen counts, exports). +- Params: `annotations: CommentAnnotation[]`, `filters: CommentSidebarFilters`. +- Returns: `CommentAnnotation[]`. +- No React hook — call on `commentElement`. +V2-only types that back the declarative pipeline. They are consumed exclusively through V2 props (filter / sort / group / list / facet) — keep them co-located with this surface rule rather than mixing them into the core type reference. + --- ## 6. UI Customization @@ -3741,12 +4029,78 @@ Customize comment dialog appearance using variants, styling, and wireframe compo ``` +**Dark Mode:** + +```jsx + +``` + **Disable Shadow DOM (for CSS access):** ```jsx ``` +**`VeltCommentDialogProps` — full prop surface (v5.0.2-beta.11+):** + +```jsx +interface VeltCommentDialogProps { + annotationId?: string; + multiThreadAnnotationId?: string; + + // Display & mode flags + darkMode?: boolean; + readOnly?: boolean; + sidebarMode?: boolean; + isFocusedThreadEnabled?: boolean; + openAnnotationInFocusMode?: boolean; + expandOnSelection?: boolean; + inlineCommentMode?: boolean; + inboxMode?: boolean; + isInsidePdfViewer?: boolean; + multiThread?: boolean; + commentComposerMode?: boolean; + dialogSelection?: boolean; + dialogMode?: boolean; + focusedThreadMode?: boolean; + pageModeComposer?: boolean; + messageTruncation?: boolean; + initialEditCommentIndex?: number | string | null; + messageTruncationLines?: number | string; + + // Layout & styling + variant?: string; + composerPosition?: string; + sortBy?: string; + sortOrder?: string; + commentPinType?: 'bubble' | 'pin' | 'chart' | 'text'; + + // Target & context binding + containerComponentId?: string; + targetElementId?: string; + targetComposerElementId?: string; // For programmatic submitComment() + locationVersion?: string; + locationDisplayName?: string; + context?: any; + + // Placeholders (paired with config-component-props edit-mode rule) + commentPlaceholder?: string; + replyPlaceholder?: string; + editPlaceholder?: string; + editCommentPlaceholder?: string; + editReplyPlaceholder?: string; +} +// Common combinations + +``` + **Wireframe Customization (full control):** ```jsx @@ -3794,15 +4148,116 @@ velt-comment-dialog { /> ``` +**Thread-Card Primitives (v5.0.2-beta.11+):** + +```html +import { + VeltCommentDialogThreadCardReactionPin, + VeltCommentDialogThreadCardAssignButton, + VeltCommentDialogThreadCardEditComposer, +} from '@veltdev/react'; + +// Reaction pin inside a thread card + + +// Assign-to button inside a thread card + + +// Inline edit composer inside a thread card + + + + + + + + + + +``` + +| Primitive | Extra Props (beyond Common Inputs) | +|-----------|-----------------------------------| +| `VeltCommentDialogThreadCardReactionPin` | `reactionId`, `commentObj`, `commentIndex`, `index` | +| `VeltCommentDialogThreadCardAssignButton` | `commentObj`, `commentId`, `commentIndex` | +| `VeltCommentDialogThreadCardEditComposer` | `commentObj`, `commentId`, `commentIndex` | + +**`VeltCommentDialogOptionsDropdownContent` — show/hide individual options (v5.0.2-beta.11+):** + +```html +// React — show only the edit option + + + + +``` + +| Prop | Type | Description | +|------|------|-------------| +| `commentObj` | `any \| string` | Comment data object (or serialized JSON string for HTML) | +| `commentIndex` | `number \| string` | Index of comment in the array | +| `enableAssignment` | `boolean` | Shows the assign option | +| `enableEdit` | `boolean` | Shows the edit option | +| `enableNotifications` | `boolean` | Shows the notifications option | +| `enablePrivateMode` | `boolean` | Shows the private mode option | +| `enableMarkAsRead` | `boolean` | Shows the mark-as-read option | + +**Collapsed-Replies-Preview Primitives (v5.0.2-beta.37+):** + +```html +import { + VeltCommentDialogMoreReplyCount, + VeltCommentDialogMoreReplyText, +} from '@veltdev/react'; + +// Hidden-reply count: annotation.comments.length - 2, clamped to >= 0 + + +// Pluralized noun: "reply" when one reply is hidden, otherwise "replies" + + + +``` + +Both accept Common Inputs only. In React wireframe mode the public primitive is also exposed as the named sub-properties `VeltCommentDialogMoreReply.Count` and `.Text`; see `wireframe-variables-comment-dialog` for the separate wireframe-tree names. + --- ### 6.3 Set defaultCondition on V2 Primitive Sub-Components to Control Default Rendering **Impact: MEDIUM (Prevents the SDK's default show/hide logic from conflicting with custom wireframe compositions in V2 primitive component families)** -Six comment component families have been migrated to the V2 primitive architecture: Comment Pin (6 primitives), Comment Bubble (3, HTML-only), Text Comment (7), Inline Comments Section (23), Multi-Thread Comment Dialog (24), and Sidebar Button (3). Every primitive in these families accepts a `defaultCondition` / `default-condition` prop. When a wireframe replaces a section of the UI, set `defaultCondition={false}` to bypass the SDK's built-in default show/hide logic and prevent double-rendering or unintended visibility toggles. - - +Seven comment component families use the V2 primitive architecture: Comment Pin (6 primitives), Comment Bubble (3, HTML-only), Text Comment (7), Inline Comments Section (24), Multi-Thread Comment Dialog (25), Sidebar Button (3), and Comments Sidebar V2 (56+ — expanded with the new Search / FilterButton / FilterContainer / FullscreenButton families this release). Every primitive in these families accepts a `defaultCondition` / `default-condition` prop. When a wireframe replaces a section of the UI, set `defaultCondition={false}` to bypass the SDK's built-in default show/hide logic and prevent double-rendering or unintended visibility toggles. **Incorrect (omitting defaultCondition when overriding a primitive section):** @@ -3852,6 +4307,142 @@ import { VeltWireframe } from '@veltdev/react'; ``` +> **Note:** The root container `VeltCommentsSidebarV2` / `` is plural. **Every** standalone sub-primitive — React identifier *and* HTML custom-element tag — uses the singular form `VeltCommentSidebarV2*` / ``. The HTML tag rename (plural → singular for sub-primitives) is the current release; React identifiers were already singular. + +**Incorrect (old plural HTML / React identifiers):** + +```html + + + + + + + + + + + + + + + +``` + +**Correct (singular sub-primitive names for React *and* HTML; root stays plural):** + +```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +| Identifier family (React + HTML) | Identifier | HTML element | +|----------------------------------|-----------|--------------| +| Skeleton | `VeltCommentSidebarV2Skeleton` | `velt-comment-sidebar-skeleton-v2` | +| Panel | `VeltCommentSidebarV2Panel` | `velt-comment-sidebar-panel-v2` | +| Header | `VeltCommentSidebarV2Header` | `velt-comment-sidebar-header-v2` | +| CloseButton | `VeltCommentSidebarV2CloseButton` | `velt-comment-sidebar-close-button-v2` | +| FullscreenButton (new) | `VeltCommentSidebarV2FullscreenButton` | `velt-comment-sidebar-fullscreen-button-v2` | +| Search (new) | `VeltCommentSidebarV2Search` (+ `Icon`, `Input`) | `velt-comment-sidebar-search-v2` (+ `-icon`, `-input`) | +| FilterButton (new) | `VeltCommentSidebarV2FilterButton` (+ `AppliedIcon`) | `velt-comment-sidebar-filter-button-v2` (+ `-applied-icon`) | +| FilterDropdown (+ subtree, incl. new `Content.List.Item.Count` and `Content.List.Category.Label` leaves) | `VeltCommentSidebarV2FilterDropdown*` | `velt-comment-sidebar-filter-dropdown-*-v2` | +| FilterContainer (new — Main Filter bottom-sheet subtree) | `VeltCommentSidebarV2FilterContainer*` | `velt-comment-sidebar-filter-container-*-v2` | +| List / ListItem | `VeltCommentSidebarV2List` / `*ListItem` | `velt-comment-sidebar-list-v2` / `velt-comment-sidebar-list-item-v2` | +| EmptyPlaceholder | `VeltCommentSidebarV2EmptyPlaceholder` | `velt-comment-sidebar-empty-placeholder-v2` | +| ResetFilterButton | `VeltCommentSidebarV2ResetFilterButton` | `velt-comment-sidebar-reset-filter-button-v2` | +| PageModeComposer | `VeltCommentSidebarV2PageModeComposer` | `velt-comment-sidebar-page-mode-composer-v2` | +| FocusedThread (+ subtree) | `VeltCommentSidebarV2FocusedThread*` | `velt-comment-sidebar-focused-thread-*-v2` | +> **Breaking change (Comment Sidebar V2 — current release):** `VeltCommentSidebarV2MinimalActionsDropdown` (and the `Trigger` / `Content` / `MarkAllRead` / `MarkAllResolved` children) plus the corresponding `velt-comments-sidebar-minimal-actions-dropdown-v2` HTML family have been **removed**. The bulk actions are now exposed by the combined `actions` filter-dropdown, configured via the `minimalFilters` input on `VeltCommentsSidebarV2`. Replace any `MinimalActionsDropdown` usage with a `FilterDropdown` configured as `{ type: 'actions', sorts: [...], actions: [...] }` — see `surface/surface-sidebar-v2.md`. +- **Search** — header search container holding the icon + input leaves (`VeltCommentSidebarV2Search`, `*SearchIcon`, `*SearchInput`). +- **FilterButton** — header button that opens the Main Filter container; child `*FilterButtonAppliedIcon` surfaces the active-filter indicator. +- **FullscreenButton** — header leaf that emits `onFullscreenClick` when clicked. +- **FilterContainer** — root container for the Main Filter bottom-sheet / menu, holding: + - `Title`, `GroupBy`, `ResetButton`, `ApplyButton`, `CloseButton` leaves. + - `SectionList` → `Section` → `SectionLabel` (leaf) and `SectionField` → `SectionControl` (+ `SectionControlChevron`, `SectionControlValue`, `SectionControlChipList` → `SectionControlChip`, `SectionControlSearch`) and `SectionOptionList` → `SectionOption` (+ `SectionOptionCheckbox`, `SectionOptionName`, `SectionOptionCount`). +The `Search`, `FilterButton`, `FilterContainer`, and `FullscreenButton` families replace the customization surface previously occupied by `MinimalActionsDropdown`. Use the `actions` dropdown type on `minimalFilters` for bulk mark-all-read / mark-all-resolved — these primitive families are the new shape for that surface. + +**`VeltInlineCommentsSectionFilterDropdownContentApplyButton` — React promotion (v5.0.2-beta.11+):** + +```html + + + +``` + +**`VeltMultiThreadCommentDialog` — new root primitive (v5.0.2-beta.11+):** + +```html + console.log(e)} +/> + + +``` + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `annotationId` | `string` | — | The annotation ID | +| `multiThreadAnnotationId` | `string` | — | The multi-thread annotation ID | +| `annotation` | `any` | — | Annotation data object (serialized JSON in HTML) | +| `readOnly` | `boolean` | `false` | Disables user interaction | +| `defaultCondition` | `boolean` | `true` | When `false`, the component always renders regardless of internal state | +| `variant` | `string` | — | Visual variant for the component | +| `inboxMode` | `boolean` | `false` | Renders the dialog in inbox mode | +| `onSaveComment` | `Function` | — | Callback fired when a comment is saved (HTML: listen via `addEventListener('onSaveComment', ...)`) | + --- ### 6.4 Use Standalone Autocomplete Primitives for Custom Autocomplete UIs @@ -3974,6 +4565,40 @@ import { VeltWireframe, VeltAutocompleteEmptyWireframe } from '@veltdev/react'; +**`VeltAutocompletePanel` — standalone panel (v5.0.2-beta.11+):** + +```html +// React — inline user picker that always renders + + + + +``` + +| React Prop | HTML Attribute | Type | Default | Description | +|------------|---------------|------|---------|-------------| +| `type` | `type` | `'contact' \| 'generic'` | `'contact'` | Type of options the panel renders | +| `hideInput` | `hide-input` | `boolean` | `false` | Hides the search input | +| `placeholder` | `placeholder` | `string` | — | Placeholder text for the search input | +| `multiSelect` | `multi-select` | `boolean` | `false` | Allows selecting multiple contacts | +| `selectedFirstOrdering` | `selected-first-ordering` | `boolean` | `false` | Shows selected items first in the list | +| `readOnly` | `read-only` | `boolean` | `false` | Disables user interaction | +| `inline` | `inline` | `boolean` | `false` | Renders the panel inline | +| `enableOnFocus` | `enable-on-focus` | `boolean` | `false` | Opens the panel when the input receives focus | +| `position` | `position` | `'above' \| 'below' \| 'auto' \| string` | `'auto'` | Position of the panel relative to its anchor | +| `defaultCondition` | `default-condition` | `boolean` | `true` | When `false`, the component always renders regardless of internal state | + --- ### 6.5 Use VeltCommentDialogAgentSuggestion Primitives for Custom AI Suggestion UIs @@ -4221,6 +4846,59 @@ function CustomSidebar() { ``` +**V2 Sidebar Wireframe Subtrees (`VeltCommentsSidebarV2Wireframe.*` / `velt-comments-sidebar-*-v2-wireframe`):** + +```html +// React — V2 sidebar header composed against the new wireframe subtree + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + **For HTML:** ```html @@ -4633,6 +5311,8 @@ interface CommentAnnotation { topPercentage: number; // Defaults to 0 when not set leftPercentage: number; // Defaults to 0 when not set }; + involvedUserIds?: string[]; // All user IDs involved in the annotation (subscribed + unsubscribed). Read-only, server-derived + mentionedUserIds?: string[]; // User IDs @mentioned across the annotation's comments. Read-only, server-derived } ``` @@ -4652,6 +5332,9 @@ interface Comment { lastUpdated?: number; // Last update timestamp isEdited?: boolean; // Whether comment was edited type?: string; // Comment type + sourceType?: string; // Origin of the comment; 'agent' indicates AI-agent-authored. Read-only + agent?: AgentData; // AI agent identity + output for an agent-authored comment. Read-only. See data-agent-fields-query.md + metadata?: any; // Customer-supplied metadata bag, persisted as-is when provided } ``` @@ -4718,6 +5401,24 @@ interface TargetElement { } ``` +**ReactionAnnotation (placed emoji reaction):** + +```typescript +interface ReactionAnnotation { + annotationId?: string; // Reaction-annotation ID + documentId?: string; + organizationId?: string; + location?: Location; + targetElement?: TargetElement; + reactions?: Reaction[]; + createdAt?: any; // Auto-generated + lastUpdated?: any; // Auto-generated + metadata?: ReactionMetadata; + context?: Context; + involvedUserIds?: string[]; // All user IDs involved in the reaction annotation. Read-only, server-derived +} +``` + **TaggedContact:** ```typescript @@ -5093,6 +5794,20 @@ const subscription = commentElement.getCommentAnnotationCount({ }); ``` +**AgentData (set on `Comment.agent`):** + +```typescript +interface AgentData { + agentName?: string; // Agent identifier. Always retained for agent-field querying. + name?: string; // Agent display name. + avatar?: string; // Agent avatar URL. + result?: { title?: string }; // Structured agent output; `title` renders in the agent suggestion card. + agentFields?: string[]; // Agent field tags used by CommentRequestQuery.agentFields. +} +``` + +The annotation-level `CommentAnnotationAgent` (see `data-types-reference.md`) is a sibling shape used on `CommentAnnotation.agent`; `AgentData` is its comment-level counterpart on `Comment.agent`. Both surface `agentFields` for the same query-side filter. + --- ### 7.10 Use CommentActivityActionTypes for Type-Safe Comment Activity Filtering @@ -7322,6 +8037,20 @@ commentElement.enableRecordingTranscription(); commentElement.disableRecordingTranscription(); ``` +**Collapsed Replies Preview (v5.0.2-beta.37+):** + +```tsx +const commentElement = client.getCommentElement(); + +// Show the collapsed teaser in the non-selected/preview state +commentElement.enableCollapsedRepliesPreview(); + +// Revert to showing only the first comment when not selected (default) +commentElement.disableCollapsedRepliesPreview(); +``` + +Also settable declaratively as a prop (``) or HTML attribute (``). The same flag is exposed as the `collapsedRepliesPreview` comment-dialog wireframe variable (boolean, default `false`); see `wireframe-variables-comment-dialog`. Both methods take no params and return `void`. + Reference: https://docs.velt.dev/async-collaboration/comments/customize-behavior - UI/UX --- @@ -10646,7 +11375,7 @@ import { VeltCommentsSidebarWireframe } from '@veltdev/react'; ``` -**List data:** +**FilterDropdown subtree leaves (new this release):** ```typescript // On any in an Angular template @@ -11162,3 +11891,14 @@ Override either with `defaultCondition={false}` (React) / `default-condition="fa - https://docs.velt.dev/ui-customization/features/async/comments/autocomplete-wireframe-variables - https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-button/wireframe-variables - https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-wireframe-variables +- https://docs.velt.dev/async-collaboration/comments/setup/apryse +- https://docs.velt.dev/api-reference/sdk/models/data-models +- https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/primitives +- https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-v2-primitives +- https://docs.velt.dev/ui-customization/features/async/comments/inline-comments-section/primitives +- https://docs.velt.dev/ui-customization/features/async/comments/multithread-comments/primitives +- https://docs.velt.dev/async-collaboration/comments-sidebar/v2/setup +- https://docs.velt.dev/async-collaboration/comments-sidebar/v2/customize-behavior +- https://docs.velt.dev/api-reference/sdk/api/api-methods +- https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-structure-v2 +- https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-v2-wireframes diff --git a/skills/velt-comments-best-practices/AGENTS.md b/skills/velt-comments-best-practices/AGENTS.md index 57b77f1..f2863c7 100644 --- a/skills/velt-comments-best-practices/AGENTS.md +++ b/skills/velt-comments-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Comments Best Practices -|v1.1.5|Velt|January 2026 +|v1.1.10|Velt|January 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules @@ -9,7 +9,7 @@ ## 2. REST API — HIGH ## 3. Comment Modes — HIGH -|react/mode:{mode-canvas.md,mode-chart-chartjs.md,mode-chart-custom.md,mode-chart-nivo.md,mode-chart-highcharts.md,mode-ace.md,mode-codemirror-comments.md,mode-lexical.md,mode-plate.md,mode-quill.md,mode-slatejs.md,mode-tiptap.md} +|react/mode:{mode-canvas.md,mode-chart-chartjs.md,mode-chart-custom.md,mode-chart-nivo.md,mode-chart-highcharts.md,mode-ace.md,mode-apryse.md,mode-codemirror-comments.md,mode-lexical.md,mode-plate.md,mode-quill.md,mode-slatejs.md,mode-tiptap.md} |shared/mode:{mode-lottie-player.md,mode-video-player-custom.md,mode-freestyle.md,mode-inline-comments.md,mode-page.md,mode-popover.md,mode-video-player-prebuilt.md,mode-stream.md,mode-text.md} ## 4. Standalone Components — MEDIUM-HIGH diff --git a/skills/velt-comments-best-practices/SKILL.md b/skills/velt-comments-best-practices/SKILL.md index e62c8c1..0271eed 100644 --- a/skills/velt-comments-best-practices/SKILL.md +++ b/skills/velt-comments-best-practices/SKILL.md @@ -1,10 +1,10 @@ --- name: velt-comments-best-practices -description: Velt Comments implementation patterns and best practices for React, Next.js, and web applications. Use when adding collaborative commenting features, comment modes (Freestyle, Popover, Stream, Text, Page), rich text editor comments (TipTap, SlateJS, Lexical), media player comments, chart comments, comments sidebar setup and customization (embed mode, floating mode, focused thread, V2 sidebar), sidebar filtering with accessModes for privacy, isAnnotationPrivate() visibility routing, CommentDialogActionService.isSubmitInFlight() for duplicate-submit guards, VeltCommentDialogAgentSuggestion primitives for AI suggestion accept/reject UIs, agent comment annotations via REST API (agent block with agentSource/agentId/executionId, agent-specific GET filters, suggestionAccepted/suggestionRejected client events, sourceType "agent" UI rendering), or binding Comment Bubble / Comment Dialog / Comment Tool wireframe slots via template variables (velt-data, velt-if, velt-class). +description: Velt Comments implementation patterns and best practices for React, Next.js, and web applications. Use when adding collaborative commenting features, comment modes (Freestyle, Popover, Stream, Text, Page), rich text editor comments (TipTap, SlateJS, Lexical), Apryse WebViewer (PDF/docx) comments, media player comments, chart comments, comments sidebar setup and customization (embed mode, floating mode, focused thread, V2 sidebar), sidebar filtering with accessModes for privacy, isAnnotationPrivate() visibility routing, CommentDialogActionService.isSubmitInFlight() for duplicate-submit guards, VeltCommentDialogAgentSuggestion primitives for AI suggestion accept/reject UIs, agent comment annotations via REST API (agent block with agentSource/agentId/executionId, agent-specific GET filters, suggestionAccepted/suggestionRejected client events, sourceType "agent" UI rendering), or binding Comment Bubble / Comment Dialog / Comment Tool wireframe slots via template variables (velt-data, velt-if, velt-class). license: MIT metadata: author: velt - version: "1.4.0" + version: "1.4.1" --- # Velt Comments Best Practices @@ -17,6 +17,7 @@ Reference these guidelines when: - Adding collaborative commenting to a React/Next.js application - Implementing any Velt comment mode (Freestyle, Popover, Stream, Text, Page, Inline) - Integrating comments with rich text editors (TipTap, SlateJS, Lexical) +- Integrating comments with the Apryse WebViewer for PDF/docx documents - Adding comments to media players (Video, Lottie animations) - Adding comments to charts (Highcharts, ChartJS, Nivo) - Building custom comment interfaces with standalone components @@ -63,6 +64,7 @@ Reference these guidelines when: - `mode-chart-chartjs` - ChartJS data point comments - `mode-chart-nivo` - Nivo charts data point comments - `mode-chart-custom` - Custom chart integration +- `mode-apryse` - Apryse WebViewer (PDF/docx) integration via `@veltdev/apryse-velt-comments` — `ApryseVeltComments.configure(...).attach(instance)`, `addComment({ instance })`, `renderComments({ instance, commentAnnotations })`, durable `TextEditorConfig` anchors (`text` + `occurrence` + `pageNumber`) ### 3. Standalone Components (MEDIUM-HIGH) diff --git a/skills/velt-comments-best-practices/metadata.json b/skills/velt-comments-best-practices/metadata.json index b65701a..3021582 100644 --- a/skills/velt-comments-best-practices/metadata.json +++ b/skills/velt-comments-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.1.5", + "version": "1.1.10", "organization": "Velt", "date": "January 2026", "abstract": "Comprehensive Velt Comments implementation guide covering comment modes, setup patterns, UI customization, and best practices. This skill provides evidence-backed patterns for integrating Velt's collaborative comments feature into React, Next.js, and other web applications. Covers Freestyle, Popover, Stream, Text, Page Mode, Inline Comments, rich text editor integrations (TipTap, SlateJS, Lexical), media player comments (Video, Lottie), chart comments (Highcharts, ChartJS, Nivo), and standalone component patterns.", @@ -17,6 +17,17 @@ "https://docs.velt.dev/ui-customization/features/async/comments/text-comment-wireframe-variables", "https://docs.velt.dev/ui-customization/features/async/comments/autocomplete-wireframe-variables", "https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-button/wireframe-variables", - "https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-wireframe-variables" + "https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-wireframe-variables", + "https://docs.velt.dev/async-collaboration/comments/setup/apryse", + "https://docs.velt.dev/api-reference/sdk/models/data-models", + "https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/primitives", + "https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-v2-primitives", + "https://docs.velt.dev/ui-customization/features/async/comments/inline-comments-section/primitives", + "https://docs.velt.dev/ui-customization/features/async/comments/multithread-comments/primitives", + "https://docs.velt.dev/async-collaboration/comments-sidebar/v2/setup", + "https://docs.velt.dev/async-collaboration/comments-sidebar/v2/customize-behavior", + "https://docs.velt.dev/api-reference/sdk/api/api-methods", + "https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-structure-v2", + "https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-v2-wireframes" ] } diff --git a/skills/velt-comments-best-practices/rules/react/mode/mode-apryse.md b/skills/velt-comments-best-practices/rules/react/mode/mode-apryse.md new file mode 100644 index 0000000..8d928ec --- /dev/null +++ b/skills/velt-comments-best-practices/rules/react/mode/mode-apryse.md @@ -0,0 +1,160 @@ +--- +title: Integrate Comments with Apryse WebViewer +impact: HIGH +impactDescription: PDF and DOCX text comments in Apryse WebViewer with durable anchors and cleanup +tags: apryse, webviewer, pdf, docx, text-comments, editor +--- + +## Integrate Comments with Apryse WebViewer + +Use `@veltdev/apryse-velt-comments` when adding Velt text comments to Apryse WebViewer documents. The integration attaches to one WebViewer instance, renders existing Velt annotations back into Apryse, and stores durable text anchors that survive document edits and viewer/docxEditor mode switches. + +**Incorrect (using default text comments without the Apryse extension):** + +```jsx +// Default text mode cannot render selections inside the Apryse canvas. + + +
+ +``` + +**Correct (React / Next.js with the Apryse extension):** + +**Step 1: Install both packages** + +```bash +npm install @veltdev/apryse-velt-comments @pdftron/webviewer +``` + +`@pdftron/webviewer` is a peer dependency. Copy its `public/core` and `public/ui` runtime folders into your app's public assets, then point WebViewer's `path` option at that location. + +**Step 2: Mount Velt comments with default text mode disabled** + +```jsx +import { VeltProvider, VeltComments } from '@veltdev/react'; + + + + +``` + +**Step 3: Dynamically create WebViewer and attach the Velt extension** + +```jsx +import { useEffect, useRef, useState } from 'react'; +import { useCommentAnnotations } from '@veltdev/react'; +import { + ApryseVeltComments, + addComment, + renderComments, +} from '@veltdev/apryse-velt-comments'; + +function ApryseEditor() { + const viewerRef = useRef(null); + const instanceRef = useRef(null); + const extensionRef = useRef(null); + const [instance, setInstance] = useState(null); + const annotations = useCommentAnnotations(); + + useEffect(() => { + if (!viewerRef.current || instanceRef.current) return; + let cancelled = false; + + import('@pdftron/webviewer').then(({ default: WebViewer }) => { + if (cancelled) return; + WebViewer( + { + path: 'lib/webviewer', + licenseKey: 'YOUR_APRYSE_LICENSE_KEY', + initialDoc: '/your-document.docx', + initialMode: 'docxEditor', + }, + viewerRef.current, + ).then((webViewerInstance) => { + if (cancelled) return; + instanceRef.current = webViewerInstance; + extensionRef.current = ApryseVeltComments + .configure({ editorId: 'contract-viewer' }) + .attach(webViewerInstance); + setInstance(webViewerInstance); + }); + }); + + return () => { + cancelled = true; + extensionRef.current?.detach(); + extensionRef.current = null; + instanceRef.current = null; + setInstance(null); + }; + }, []); + + useEffect(() => { + if (instance && annotations) { + renderComments({ instance, commentAnnotations: annotations }); + } + }, [instance, annotations]); + + return ( + <> + +
+ + ); +} +``` + +**Key APIs:** + +| API | Purpose | +|-----|---------| +| `ApryseVeltComments.configure({ editorId }).attach(instance)` | Attach Velt comment handling to one WebViewer instance. | +| `addComment({ instance })` | Create a Velt annotation from the current Apryse text selection. Returns `null` when nothing is selected or the SDK is not loaded. | +| `renderComments({ instance, commentAnnotations })` | Re-render Velt annotations as Apryse text highlights. | +| `AttachedExtension.detach()` | Remove Apryse listeners and clear per-instance caches during cleanup. | + +**Important details:** +- Import `@pdftron/webviewer` dynamically in browser-only code; WebViewer touches the DOM. +- Clicking a host-page button does not clear Apryse's canvas selection, so no selection-preservation `mousedown` workaround is needed. +- Set `editorId` when the page hosts multiple WebViewer instances; `renderComments()` only paints annotations whose stored editor id matches. +- The library stores `annotation.context.textEditorConfig` with `editorId`, selected `text`, `pageNumber`, and `occurrence`; physical positions are re-derived at render time. +- If the WebViewer loads a new document in the same instance, keep the extension attached. It listens for Apryse document lifecycle events and re-syncs highlights. + +**Style Apryse highlights:** + +```css +velt-comment-text .velt-apryse-highlight { + background-color: rgba(60, 130, 246, 0.30) !important; + border-bottom: 2px solid rgba(60, 130, 246, 0.95) !important; +} + +velt-comment-text:hover .velt-apryse-highlight { + background-color: rgba(60, 130, 246, 0.50) !important; +} +``` + +**Verification Checklist:** +- [ ] `@veltdev/apryse-velt-comments` and `@pdftron/webviewer` are installed +- [ ] WebViewer runtime assets are copied to a public HTTP path used by `WebViewer({ path })` +- [ ] `VeltComments` is mounted with `textMode={false}` +- [ ] `ApryseVeltComments.configure(...).attach(instance)` runs once per WebViewer instance +- [ ] `renderComments({ instance, commentAnnotations })` runs when annotations change +- [ ] `extension.detach()` runs on unmount +- [ ] Multi-viewer pages set stable `editorId` values + +**Source Pointers:** +- https://docs.velt.dev/async-collaboration/comments/setup/apryse - "Apryse Setup" +- https://docs.velt.dev/api-reference/sdk/models/data-models#apryseveltcommentsconfig - "ApryseVeltCommentsConfig" +- https://docs.velt.dev/api-reference/sdk/models/data-models#addcommentargs - "AddCommentArgs" +- https://docs.velt.dev/api-reference/sdk/models/data-models#attachedextension - "AttachedExtension" diff --git a/skills/velt-comments-best-practices/rules/shared/config/config-ui-behavior.md b/skills/velt-comments-best-practices/rules/shared/config/config-ui-behavior.md index ff758c2..da9bd0d 100644 --- a/skills/velt-comments-best-practices/rules/shared/config/config-ui-behavior.md +++ b/skills/velt-comments-best-practices/rules/shared/config/config-ui-behavior.md @@ -2,7 +2,7 @@ title: UI/UX Toggle Methods — Comment Display, Interaction, and Behavior impact: LOW impactDescription: Fine-tune comment UI appearance and interaction behavior -tags: enableCollapsedComments, enableMobileMode, enableCommentPinHighlighter, enableDialogOnHover, enableFloatingCommentDialog, enableDraftMode, enableGhostComments, enableHotkey, enableEnterKeyToSubmit, enablePersistentCommentMode, enableMinimap, enableCommentIndex, enableDeviceInfo, enableReplyAvatars, composerMode, showCommentsOnDom, showResolvedCommentsOnDom +tags: enableCollapsedComments, enableMobileMode, enableCommentPinHighlighter, enableDialogOnHover, enableFloatingCommentDialog, enableDraftMode, enableGhostComments, enableHotkey, enableEnterKeyToSubmit, enablePersistentCommentMode, enableMinimap, enableCommentIndex, enableDeviceInfo, enableReplyAvatars, composerMode, showCommentsOnDom, showResolvedCommentsOnDom, enableCollapsedRepliesPreview, disableCollapsedRepliesPreview, collapsedRepliesPreview --- ## UI/UX Toggle Methods — Comment Display, Interaction, and Behavior @@ -245,6 +245,22 @@ Drafts are session-only and are cleared on: There is no API surface for this behavior — it is fully automatic. Developers should be aware of the session-only scoping: drafts do not survive a page reload. +**Collapsed Replies Preview (v5.0.2-beta.37+):** + +When enabled, a comment dialog's non-selected/preview state shows the collapsed thread teaser — first comment, a "Show N replies…" divider, and the last comment — instead of only the first comment. Clicking the divider selects and expands the dialog in one step. Defaults to disabled. + +```tsx +const commentElement = client.getCommentElement(); + +// Show the collapsed teaser in the non-selected/preview state +commentElement.enableCollapsedRepliesPreview(); + +// Revert to showing only the first comment when not selected (default) +commentElement.disableCollapsedRepliesPreview(); +``` + +Also settable declaratively as a prop (``) or HTML attribute (``). The same flag is exposed as the `collapsedRepliesPreview` comment-dialog wireframe variable (boolean, default `false`); see `wireframe-variables-comment-dialog`. Both methods take no params and return `void`. + **Key details:** - All toggle methods have corresponding `disable` variants - Most can also be set as props on `` or via HTML attributes diff --git a/skills/velt-comments-best-practices/rules/shared/data/data-agent-fields-query.md b/skills/velt-comments-best-practices/rules/shared/data/data-agent-fields-query.md index 32b535b..611f784 100644 --- a/skills/velt-comments-best-practices/rules/shared/data/data-agent-fields-query.md +++ b/skills/velt-comments-best-practices/rules/shared/data/data-agent-fields-query.md @@ -2,7 +2,7 @@ title: Use agentFields on CommentRequestQuery to Filter Annotation Count by Agent impact: MEDIUM impactDescription: Enables precise comment count queries scoped to agent-tagged annotations, avoiding full-collection scans -tags: agent-fields, comment-request-query, getCommentAnnotationCount, agent, filter +tags: agent-fields, comment-request-query, getCommentAnnotationCount, agent, filter, AgentData --- ## Use agentFields on CommentRequestQuery to Filter Annotation Count by Agent @@ -74,11 +74,28 @@ const subscription = commentElement.getCommentAnnotationCount({ **Behavioral Note:** The unread count query uses a Firestore `array-contains` constraint that cannot be combined with the filter used for unread counting. When `agentFields` is set, Velt skips the separate unread count query and returns `unreadCount === totalCount`. If your UI distinguishes read from unread, do not rely on `unreadCount` when `agentFields` is active. +**AgentData (set on `Comment.agent`):** + +The AI-agent identity + output payload attached to an agent-authored `Comment` (set on `Comment.agent` when `Comment.sourceType === 'agent'`). Read-only from the SDK; populated when the annotation is created via the REST API `agent` block. The `agentFields` array on this payload is the field that `CommentRequestQuery.agentFields` filters against. + +```typescript +interface AgentData { + agentName?: string; // Agent identifier. Always retained for agent-field querying. + name?: string; // Agent display name. + avatar?: string; // Agent avatar URL. + result?: { title?: string }; // Structured agent output; `title` renders in the agent suggestion card. + agentFields?: string[]; // Agent field tags used by CommentRequestQuery.agentFields. +} +``` + +The annotation-level `CommentAnnotationAgent` (see `data-types-reference.md`) is a sibling shape used on `CommentAnnotation.agent`; `AgentData` is its comment-level counterpart on `Comment.agent`. Both surface `agentFields` for the same query-side filter. + **Verification Checklist:** - [ ] `agentFields` values match the strings stored in `agent.agentFields` on the target annotations - [ ] UI does not display a meaningful unread badge when `agentFields` is set (unread equals total) - [ ] Subscription is cleaned up on component unmount - [ ] `organizationId` is always provided alongside `agentFields` +- [ ] When reading `Comment.agent`, use the `AgentData` shape (`agentName`, `name`, `avatar`, `result.title`, `agentFields`); do not mutate it from the client **Source Pointers:** - https://docs.velt.dev/api-reference/sdk/models/data-models#commentrequestquery - CommentRequestQuery model diff --git a/skills/velt-comments-best-practices/rules/shared/data/data-types-reference.md b/skills/velt-comments-best-practices/rules/shared/data/data-types-reference.md index 72e82c2..594075e 100644 --- a/skills/velt-comments-best-practices/rules/shared/data/data-types-reference.md +++ b/skills/velt-comments-best-practices/rules/shared/data/data-types-reference.md @@ -2,7 +2,7 @@ title: Comments Data Type Reference — Core Models impact: MEDIUM impactDescription: Type definitions for comment annotations, comments, status, priority, attachments -tags: CommentAnnotation, Comment, Status, Priority, Attachment, Location, TargetElement, CommentRequestQuery, AddCommentAnnotationRequest, CommentSidebarData, CommentAnnotationAgent, AgentResult, CommentAnnotationSuggestion, basicAnchorData, commentType, sourceType, types, models +tags: CommentAnnotation, Comment, ReactionAnnotation, Status, Priority, Attachment, Location, TargetElement, CommentRequestQuery, AddCommentAnnotationRequest, CommentSidebarData, CommentAnnotationAgent, AgentResult, CommentAnnotationSuggestion, basicAnchorData, commentType, sourceType, involvedUserIds, mentionedUserIds, metadata, types, models --- ## Comments Data Type Reference — Core Models @@ -40,6 +40,8 @@ interface CommentAnnotation { topPercentage: number; // Defaults to 0 when not set leftPercentage: number; // Defaults to 0 when not set }; + involvedUserIds?: string[]; // All user IDs involved in the annotation (subscribed + unsubscribed). Read-only, server-derived + mentionedUserIds?: string[]; // User IDs @mentioned across the annotation's comments. Read-only, server-derived } ``` @@ -59,6 +61,9 @@ interface Comment { lastUpdated?: number; // Last update timestamp isEdited?: boolean; // Whether comment was edited type?: string; // Comment type + sourceType?: string; // Origin of the comment; 'agent' indicates AI-agent-authored. Read-only + agent?: AgentData; // AI agent identity + output for an agent-authored comment. Read-only. See data-agent-fields-query.md + metadata?: any; // Customer-supplied metadata bag, persisted as-is when provided } ``` @@ -125,6 +130,24 @@ interface TargetElement { } ``` +**ReactionAnnotation (placed emoji reaction):** + +```typescript +interface ReactionAnnotation { + annotationId?: string; // Reaction-annotation ID + documentId?: string; + organizationId?: string; + location?: Location; + targetElement?: TargetElement; + reactions?: Reaction[]; + createdAt?: any; // Auto-generated + lastUpdated?: any; // Auto-generated + metadata?: ReactionMetadata; + context?: Context; + involvedUserIds?: string[]; // All user IDs involved in the reaction annotation. Read-only, server-derived +} +``` + **TaggedContact:** ```typescript @@ -183,5 +206,8 @@ Suggestion state is mutated by `acceptSuggestion()` / `rejectSuggestion()` API m - [ ] Location.id is number, not string - [ ] Status.type is one of 'default', 'ongoing', 'terminal' - [ ] Agent-authored annotations check `annotation.agent` for identity, not custom fields +- [ ] `CommentAnnotation.involvedUserIds` / `mentionedUserIds` and `ReactionAnnotation.involvedUserIds` are treated as read-only server-derived fields (never written from the client) +- [ ] `Comment.sourceType === 'agent'` is the discriminator for the agent-identity header; `Comment.agent` carries the AI payload (`AgentData` — see `data-agent-fields-query.md` for the shape) +- [ ] `Comment.metadata` is opaque to Velt — application code owns its schema **Source Pointer:** https://docs.velt.dev/api-reference/sdk/models/data-models - Comments diff --git a/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-setup.md b/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-setup.md index 7c0a01f..67dde2b 100644 --- a/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-setup.md +++ b/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-setup.md @@ -141,8 +141,6 @@ commentElement.toggleCommentSidebar(); import { VeltCommentsSidebarV2 } from '@veltdev/react'; -// or - ``` V2 replaces the per-category filter panel with a unified `FilterDropdown`. For V2 wireframe customization, see the Comment Sidebar V2 Structure docs. diff --git a/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-v2.md b/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-v2.md index e004b7c..a0a2fab 100644 --- a/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-v2.md +++ b/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar-v2.md @@ -1,13 +1,13 @@ --- title: Use VeltCommentsSidebarV2 for Primitive-Architecture Sidebar Customization impact: MEDIUM-HIGH -impactDescription: Full composability of every sidebar UI section via 27+ independently importable primitives, enabling precise customization without forking the entire component -tags: sidebar, veltcommentssidebarv2, primitives, wireframe, filter, virtual-scroll, focused-thread +impactDescription: Full composability of every sidebar UI section via 56+ independently importable primitives, enabling precise customization without forking the entire component +tags: sidebar, veltcommentssidebarv2, primitives, wireframe, filter, virtual-scroll, focused-thread, minimal-filters, sort, declarative-filters, applyCommentSidebarClientFilters --- ## Use VeltCommentsSidebarV2 for Primitive-Architecture Sidebar Customization -`VeltCommentsSidebarV2` is a complete redesign of the Comments Sidebar built on a flat primitive component architecture. Every section of the UI is an independently importable and composable primitive, so you can replace only the parts you need without reimplementing the whole component. V2 ships with a unified filter model (replacing the legacy `minimalFilter` + `advancedFilters` system), CDK virtual scroll for large comment lists, a focused-thread view, a minimal actions dropdown, and a filter dropdown. +`VeltCommentsSidebarV2` is a complete redesign of the Comments Sidebar built on a flat primitive component architecture. Every section of the UI is an independently importable and composable primitive, so you can replace only the parts you need without reimplementing the whole component. V2 ships with a declarative filter / sort / group model (three filter surfaces — main panel, mini funnel dropdown, multi-dropdown minimal bar), CDK virtual scroll for large comment lists, a focused-thread view, a fullscreen toggle, and a header search. **Incorrect (customizing V1 sidebar by overriding deeply nested internals):** @@ -38,7 +38,7 @@ export default function App() { readOnly={false} position="right" variant="sidebar" - forceClose={false} + forceClose={true} onSidebarOpen={(data) => console.log('sidebar opened', data)} onSidebarClose={(data) => console.log('sidebar closed', data)} onCommentClick={(data) => console.log('comment clicked', data)} @@ -49,7 +49,7 @@ export default function App() { } ``` -**Correct (HTML / Other Frameworks):** +**Correct (HTML / Other Frameworks — dedicated V2 web-component tag):** ```html ``` -**VeltCommentsSidebarV2 Props:** +`` / `VeltCommentsSidebarV2` is the only entry point documented by the V2 setup page. The old V1 component prop opt-in is no longer shown in `async-collaboration/comments-sidebar/v2/setup`. Mount the dedicated V2 tag directly; do not pair it with a V1 tag. + +**VeltCommentsSidebarV2 Props (core layout / event surface):** | Prop | Type | Optional | Description | |------|------|----------|-------------| @@ -71,28 +73,236 @@ export default function App() { | `readOnly` | boolean | Yes | Render the sidebar in read-only mode. | | `embedMode` | string \| null | Yes | Embed the sidebar inside a custom container. | | `floatingMode` | boolean | Yes | Render the sidebar in floating mode. | -| `position` | string | Yes | Anchor position of the sidebar panel (e.g. `"right"`). | +| `position` | `'right' \| 'left'` | Yes | Anchor position of the sidebar panel. Narrowed from `string`. | | `variant` | string | Yes | Display variant (e.g. `"sidebar"`). | -| `forceClose` | boolean | Yes | Force the sidebar closed programmatically. | +| `forceClose` | boolean | Yes | Force the sidebar to close on outside click, even when opened via API. Default `true`. | | `onSidebarOpen` | (data: any) => void | Yes | Callback fired when the sidebar opens. | | `onSidebarClose` | (data: any) => void | Yes | Callback fired when the sidebar closes. | | `onCommentClick` | (data: any) => void | Yes | Callback fired when a comment item is clicked. | | `onCommentNavigationButtonClick` | (data: any) => void | Yes | Callback fired when the comment navigation button is clicked. | +| `fullScreen` | boolean | Yes | Add a fullscreen toggle to the header. Default `false`. | +| `onFullscreenClick` | (data: any) => void | Yes | Fires when the fullscreen toggle is clicked. | + +For the complete prop catalog (placeholders, virtual-scroll tuning, URL navigation, deprecated V1 aliases such as `openSidebar` / `sidebarCommentClick` / `onSidebarCommentClick`), see `surface/surface-sidebar.md` — `VeltCommentsSidebarV2` reuses `VeltCommentsSidebarProps`. + +### Declarative filter surfaces (V2) + +V2 exposes filter / sort / group / search as data. The sidebar renders the matching UI and applies the selections client-side via the new `applyCommentSidebarClientFilters()` API method. Three filter surface props drive three distinct surfaces — they only make sense together, so configure them as one unit: + +| Prop | Surface | Shape | +|------|---------|-------| +| `filters` | Main Filter bottom-sheet / menu panel | `FilterField[]` defines sections; an object keyed by field (e.g. `{ status: ['open'] }`) instead applies active selections directly. Default `[]`. | +| `miniFilters` | Single header funnel dropdown | `FilterField[]` — one section per field. Default `[]`. | +| `minimalFilters` | Multiple header dropdowns (replaces the single funnel) | `SidebarMinimalFilterConfig[]` — one dropdown per entry. The entry's `type` (`filter` / `sort` / `quick` / `actions`) decides what the dropdown contains; matching input (`fields` / `sorts` / `actions`) provides its content. Default `[]`. | + +```jsx +// React — main filter panel + a multi-dropdown minimal bar + +``` + +```html + + +``` + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `filters` | `string \| FilterField[] \| object` | `[]` | Main Filter panel sections, OR an object of active selections that routes to the V1 `setCommentSidebarFilters` path. | +| `miniFilters` | `string \| FilterField[]` | `[]` | Single header funnel dropdown. | +| `minimalFilters` | `string \| SidebarMinimalFilterConfig[]` | `[]` | Multiple configurable header dropdowns. Replaces the single mini-filter funnel when present. | +| `filterOperator` | `'and' \| 'or'` | `'and'` | Cross-section combination of active filter selections. | +| `filterPanelLayout` | `'bottomSheet' \| 'menu'` | `'bottomSheet'` | Main Filter panel layout. | +| `filterOptionLayout` | `'dropdown' \| 'checkbox'` | `'dropdown'` | How options render within a filter section. | +| `filterCount` | boolean | `true` | Per-option facet counts. Disabling improves performance. | +| `filterGhostCommentsInSidebar` | boolean | `false` | Hide ghost comments from the list. | +| `systemFiltersOperator` | `'and' \| 'or'` | `'and'` | Operator applied to system filters. Mirrored by `applyCommentSidebarClientFilters()`. | +| `defaultMinimalFilter` | `'all' \| 'read' \| 'unread' \| 'resolved' \| 'open' \| 'assignedToMe' \| 'reset'` | — | Default active quick filter applied on load. | + +### Default sort and quick-filter (V2) + +| Prop | Type | Description | +|------|------|-------------| +| `sortBy` | [`SortBy`](#) | Default sort key — built-in preset (`'date'`, `'unread'`) or a dot-path (e.g. `'comments.createdAt'`). Sets the default sort; does not render a sort dropdown on its own. | +| `sortOrder` | [`SortOrder`](#) — `'asc' \| 'desc'` | Default sort direction. | +| `sortData` | string | Custom-field sort path used when sorting by a custom field. | + +```jsx + +``` + +### `applyCommentSidebarClientFilters()` — programmatic filter pipeline + +Apply a `CommentSidebarFilters` payload to an annotation array client-side, honoring the current `systemFiltersOperator`. Backs the V2 declarative filter pipeline; reach for it when filtering annotations outside the sidebar (custom previews, off-screen counts, exports). + +```typescript +const commentElement = client.getCommentElement(); +const filtered: CommentAnnotation[] = commentElement.applyCommentSidebarClientFilters( + annotations, + filters, +); +``` + +- Params: `annotations: CommentAnnotation[]`, `filters: CommentSidebarFilters`. +- Returns: `CommentAnnotation[]`. +- No React hook — call on `commentElement`. + +### V2 type vocabulary + +V2-only types that back the declarative pipeline. They are consumed exclusively through V2 props (filter / sort / group / list / facet) — keep them co-located with this surface rule rather than mixing them into the core type reference. + +```typescript +// Filter field definition (panel sections + minimal-filter `filter` dropdowns) +interface FilterField { + field: string; // BuiltInFilterFieldId or custom id + label?: string; + select?: 'single' | 'multi'; + searchable?: boolean; + showCounts?: boolean; + icon?: string; + valuePath?: string; // dot-path for custom fields + includeUnset?: boolean; + placeholder?: string; + groupable?: boolean; + order?: string[]; + options?: SidebarFilterValue[]; +} + +// Single selectable option inside a FilterField — { id, label, count?, icon? } +interface SidebarFilterValue { /* id + display + optional count/icon */ } + +// One dropdown in the minimalFilters bar +interface SidebarMinimalFilterConfig { + type?: SidebarFilterDropdownType; // 'filter' | 'sort' | 'quick' | 'actions' + label?: string; + field?: string; + fields?: FilterField[]; // for type === 'filter' + sorts?: (string | SidebarSortConfig)[]; // for type === 'sort' or 'actions' + actions?: (string | SidebarQuickFilterConfig)[]; // for type === 'quick' or 'actions' +} + +// One sort option +interface SidebarSortConfig { + label?: string; + preset?: string; // 'date' | 'unread' | ... + path?: string; + field?: string; + order?: 'asc' | 'desc'; +} + +// One quick-filter predicate +interface SidebarQuickFilterConfig { + label?: string; + preset?: string; // 'open' | 'resolved' | 'unread' | ... + path?: string; + field?: string; + value?: any; + conditions?: SidebarQuickCondition[]; + operator?: 'and' | 'or'; +} + +interface SidebarQuickCondition { + path?: string; + field?: string; + value: any; +} + +// List grouping + flattened virtual-scroll rows +interface SidebarAnnotationGroup { + id: string; + label: string; + count: number; + isExpanded: boolean; + isCurrentPage?: boolean; + annotations: CommentAnnotation[]; +} + +type SidebarListRow = + | { type: 'group'; group: SidebarAnnotationGroup } + | { type: 'annotation'; annotation: CommentAnnotation; groupId: string }; + +// Operators + dropdown kinds +type FilterFieldOperator = 'and' | 'or'; +type SidebarFilterDropdownType = 'filter' | 'sort' | 'quick' | 'actions'; + +// Built-in field ids — recognized natively by the V2 filter pipeline +const BUILT_IN_FILTER_FIELD_IDS = [ + 'status', 'priority', 'category', 'people', 'assigned', + 'tagged', 'involved', 'location', 'version', 'document', +] as const; +type BuiltInFilterFieldId = typeof BUILT_IN_FILTER_FIELD_IDS[number]; + +// Section header chips + "All" toggle (panel-level controls) +type SectionControlChip = { id: string; label: string; isAll: boolean }; +type SectionAllOption = { show: boolean; label: string }; + +// Helper types for the resolved sort / quick pipelines +type SidebarSortCriterion = unknown; // resolved from SidebarSortConfig +type SidebarQuickPredicate = unknown; // resolved from SidebarQuickFilterConfig + +// Default sort surface (props sortBy / sortOrder) +type SortBy = string; +type SortOrder = 'asc' | 'desc'; + +// Custom-field resolver registration +interface FacetContext { + annotations: CommentAnnotation[]; + field: FilterField; +} + +interface FilterFieldResolver { + id: string; + optionSource: 'catalog' | 'scan'; + buildOptions: (ctx: FacetContext) => SidebarFilterValue[]; + matches: (annotation: CommentAnnotation, selectedValueIds: string[]) => boolean; +} +``` **Key V2 Differences from V1:** -- **Unified filter model** — replaces the legacy `minimalFilter` + `advancedFilters` system with a single consistent filter dropdown primitive. -- **CDK virtual scroll** — built-in for large comment lists; no manual configuration required. -- **Focused-thread view** — when `focusedThreadMode={true}`, clicking a comment opens the thread inline inside the sidebar instead of navigating away. -- **Primitive tree** — every section of the sidebar UI (header, filter bar, comment list item, thread view, actions dropdown) is an independently importable primitive that accepts `parentLocalUIState` and supports `velt-class` conditional styling. +- **Declarative filter / sort model** — `filters` / `miniFilters` / `minimalFilters` (+ `sortBy` / `sortOrder` / `sortData` / `defaultMinimalFilter`) replace the legacy `minimalFilter` + `advancedFilters` system. +- **CDK virtual scroll** — built-in for large comment lists; tune via `measuredSize` / `minBufferPx` / `maxBufferPx`. +- **Focused-thread view** — when `focusedThreadMode={true}`, clicking a comment opens the thread inline inside the sidebar. +- **Primitive tree** — every section (header, search, filter button, filter container, list group header, fullscreen button, list, thread view, page-mode composer) is an independently importable primitive that accepts `parentLocalUIState` and supports `velt-class` conditional styling. See `ui/ui-v2-primitives.md`. +- **`MinimalActionsDropdown` removed** — replaced by the combined `actions` filter-dropdown configured via `minimalFilters`. **Verification Checklist:** -- [ ] `VeltCommentsSidebarV2` is used when per-section customization is required (the removed `version="2"` opt-in on `VeltCommentsSidebar` is no longer valid as of v5.0.2-beta.13) +- [ ] `VeltCommentsSidebarV2` (or ``) is mounted directly for per-section customization — the V2 setup docs no longer cover the legacy V1 component prop opt-in - [ ] `focusedThreadMode` is set explicitly when inline thread expansion is needed -- [ ] `forceClose` is driven by state, not hardcoded to `true` -- [ ] Event callbacks (`onSidebarOpen`, `onSidebarClose`, `onCommentClick`) clean up any side effects on unmount +- [ ] `forceClose` is driven by state when not using the new default of `true` (V2 default flipped from `false` to `true`) +- [ ] Filter / sort props are configured together (`filters` + `minimalFilters` for visible UI, `sortBy` / `sortOrder` for default ordering) +- [ ] `applyCommentSidebarClientFilters()` is used for off-sidebar filtering instead of reimplementing the predicate pipeline +- [ ] Built-in filter fields are referenced via `BuiltInFilterFieldId` ids; custom fields supply `valuePath` (and a `FilterFieldResolver` when option sourcing is non-trivial) +- [ ] Event callbacks (`onSidebarOpen`, `onSidebarClose`, `onCommentClick`, `onFullscreenClick`) clean up any side effects on unmount **Source Pointers:** -- https://docs.velt.dev/async-collaboration/comments-sidebar/overview - Comments Sidebar overview -- https://docs.velt.dev/async-collaboration/comments-sidebar/setup - Setup guide -- https://docs.velt.dev/api-reference/sdk/models/data-models - Velt data models +- https://docs.velt.dev/async-collaboration/comments-sidebar/v2/setup — "V2 Setup" +- https://docs.velt.dev/async-collaboration/comments-sidebar/v2/customize-behavior — "V2 Customize Behavior" (declarative filters / sort / `applyCommentSidebarClientFilters`) +- https://docs.velt.dev/api-reference/sdk/api/api-methods#applycommentsidebarclientfilters — `applyCommentSidebarClientFilters()` +- https://docs.velt.dev/api-reference/sdk/models/data-models#veltcommentssidebarv2props — V2 props reference (incl. `FilterField`, `SidebarMinimalFilterConfig`, `SortBy` / `SortOrder`) diff --git a/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar.md b/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar.md index 85529c6..a745900 100644 --- a/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar.md +++ b/skills/velt-comments-best-practices/rules/shared/surface/surface-sidebar.md @@ -2,12 +2,12 @@ title: Use Comments Sidebar for Comment Navigation impact: MEDIUM-HIGH impactDescription: Central panel for viewing, filtering, and navigating all comments -tags: sidebar, veltcommentssidebar, navigation, filter, embed-mode +tags: sidebar, veltcommentssidebar, navigation, filter, embed-mode, page-mode, floating-mode, fullscreen, virtual-scroll, placeholders --- ## Use Comments Sidebar for Comment Navigation -VeltCommentsSidebar provides a panel displaying all comments with search, filter, and navigation capabilities. Essential for any non-trivial commenting implementation. +`VeltCommentsSidebar` provides a panel displaying all comments with search, filter, and navigation capabilities. Essential for any non-trivial commenting implementation. The same `VeltCommentsSidebarProps` shape is reused by `VeltCommentsSidebarV2` — this rule is the prop catalog for both surfaces; for the V2-only declarative filter / sort surface, see `surface/surface-sidebar-v2.md`. **Basic Setup:** @@ -67,31 +67,20 @@ export default function App() { /> ``` -**VeltCommentsSidebar Props:** +**V2 Sidebar Entry:** -| Prop | Type | Description | -|------|------|-------------| -| `embedMode` | boolean | Embed in custom container | -| `pageMode` | boolean | Enable page-level comments | -| `groupConfig` | object | Configure comment grouping | -| `onCommentClick` | function | Handle comment selection | - -> **Breaking change (v5.0.2-beta.13):** The `version` prop has been removed from `VeltCommentsSidebar`. The `version="2"` opt-in pattern is no longer valid. Use `VeltCommentsSidebarV2` / `velt-comments-sidebar-v2` directly instead. - -**V1 defers to V2 when both are mounted (v5.0.2-beta.13+):** - - - -When `velt-comments-sidebar` (V1) and `velt-comments-sidebar-v2` (V2) are both present in the DOM simultaneously, V1 defers to V2 and will not open. `setSidebarVisibility` and `toggleSidebarVisibility` APIs also detect the V2 element. Do not mount both components at the same time; use only V2 when V2 behavior is required. +For the primitive-based V2 sidebar, import `VeltCommentsSidebarV2` directly in React or mount `` in other frameworks. The current V2 setup docs no longer document the V1 component prop opt-in as a setup path. ```jsx -// Before (no longer valid — version prop removed): - +import { VeltCommentsSidebarV2 } from '@veltdev/react'; -// After — use VeltCommentsSidebarV2 directly: ``` +```html + +``` + **For HTML:** ```html @@ -121,12 +110,92 @@ When `velt-comments-sidebar` (V1) and `velt-comments-sidebar-v2` (V2) are both p /> ``` +### `VeltCommentsSidebarProps` (shared with `VeltCommentsSidebarV2`) + +The React TypeScript interface; HTML attributes use the same names in kebab-case. All props are optional. Defaults reflect the current SDK surface — note in particular: `position` is narrowed from `string` to `'right' | 'left'`, and `forceClose` now defaults to `true` (the sidebar force-closes on outside click unless you explicitly set `forceClose={false}` — embed mode is unaffected). + +**Layout / mode:** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `pageMode` | boolean | `false` | Page-level comments mode (composer in the sidebar, no element attachment). | +| `focusedThreadMode` | boolean | `false` | Open individual threads in a focused view inside the sidebar. | +| `readOnly` | boolean | `false` | Render the sidebar in read-only mode. | +| `embedMode` | `string \| null` | `null` | Embed the sidebar inline within a host container. | +| `floatingMode` | boolean | `false` | Floating overlay layout. | +| `position` | `'right' \| 'left'` | `'right'` | Side of the viewport the sidebar opens from. Narrowed from `string`. | +| `variant` | string | `'sidebar'` | Layout variant id. | +| `forceClose` | boolean | `true` | Force-close on outside click, even when opened via API. Does not affect embed mode. (V2 default flipped from `false` → `true`.) | +| `fullScreen` | boolean | `false` | Add a fullscreen toggle button to the header. | +| `fullExpanded` | boolean | `false` | Render the sidebar fully expanded. | +| `shadowDom` | boolean | input `false`; shadow-DOM isolation is on by default | Render the sidebar body inside a shadow root for style isolation. Opt out via `shadow-dom="false"` or `disableSidebarShadowDOM()`. | +| `groupConfig` | `{ enable?: boolean; name?: string; groupBy?: string }` | — | Grouping config; defaults to grouping by location when enabled. | +| `currentLocationSuffix` | boolean | `false` | Append a "(this page)" suffix when a group matches the current location. | +| `dialogVariant` | string | `'sidebar'` | Variant for the embedded comment dialog rendered in the list. | +| `focusedThreadDialogVariant` | string | `'sidebar'` | Variant for the focused-thread dialog. | +| `pageModeComposerVariant` | string | `'sidebar'` | Variant for the page-mode composer. | +| `dialogSelection` | boolean | `true` | Clicking a comment opens its dialog inline; set `false` to fall back to a click event without inline expansion. | +| `expandOnSelection` | boolean | `true` | Expand the dialog automatically on selection. | +| `openAnnotationInFocusMode` | boolean | `false` | Open annotations in focus mode when `focusedThreadMode={true}` and a reply / `selectCommentByAnnotationId()` is used. | +| `excludeLocationIds` | `string[]` | `[]` | Hide comments from these locations. | +| `customActions` | boolean | `false` | Enable host-driven wireframe actions in the sidebar. | +| `sidebarButtonCountType` | `'default' \| 'filter'` | — | What the sidebar-button badge tracks — total open/in-progress (default) vs filtered count. | +| `context` | object | `null` | Context attached to comments added via the page-mode composer (serialized JSON on the HTML attribute). | + +**Placeholders (V2 surface; also accepted by V1):** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `searchPlaceholder` | string | `'Search comments'` | Placeholder in the search input. | +| `pageModePlaceholder` | string | `''` | Placeholder for the page-mode composer. | +| `commentPlaceholder` | string | `''` | Placeholder for the dialog composer (new comment input). | +| `replyPlaceholder` | string | `''` | Placeholder for reply input fields. | +| `editPlaceholder` | string | `''` | Fallback edit placeholder. | +| `editCommentPlaceholder` | string | `''` | Placeholder when editing the first comment (takes precedence over `editPlaceholder`). | +| `editReplyPlaceholder` | string | `''` | Placeholder when editing a reply (takes precedence over `editPlaceholder`). | + +**Virtual scrolling:** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `measuredSize` | number | `220` | Estimated row size (px). | +| `minBufferPx` | number | `1000` | Minimum virtual-scroll buffer (px). | +| `maxBufferPx` | number | `2000` | Maximum virtual-scroll buffer (px). | + +**URL navigation:** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `urlNavigation` | boolean | `false` | Automatically update the URL when navigating between comments. | +| `enableUrlNavigation` | boolean | `false` | Deprecated alias for `urlNavigation`. Prefer `urlNavigation`. | +| `queryParamsComments` | boolean | `false` | Sync the selected comment to URL query params. | + +**Events / callbacks:** + +| Prop | Type | Description | +|------|------|-------------| +| `onSidebarOpen` | (data: any) => void | Fired when the sidebar opens. | +| `onSidebarClose` | (data: any) => void | Fired when the sidebar closes. | +| `onCommentClick` | (data: any) => void | Fired when a comment is clicked. | +| `onCommentNavigationButtonClick` | (data: any) => void | Fired when the navigation button is clicked. | +| `onFullscreenClick` | (data: any) => void | Fires when the fullscreen toggle is clicked. | +| `openSidebar` | (data: any) => void | Deprecated V1 alias; prefer `onSidebarOpen`. | +| `sidebarCommentClick` | (data: any) => void | Deprecated V1 alias; prefer `onCommentClick`. | +| `onSidebarCommentClick` | (data: any) => void | Deprecated V1 alias; prefer `onCommentClick`. | + +For the V2-only declarative filter / sort surface (`filters`, `miniFilters`, `minimalFilters`, `filterOperator`, `filterPanelLayout`, `filterOptionLayout`, `filterCount`, `filterGhostCommentsInSidebar`, `systemFiltersOperator`, `sortBy`, `sortOrder`, `sortData`, `defaultMinimalFilter`) and the `applyCommentSidebarClientFilters()` API, see `surface/surface-sidebar-v2.md`. + **Verification Checklist:** -- [ ] VeltCommentsSidebar added to app -- [ ] VeltSidebarButton provides toggle -- [ ] embedMode set if using custom container -- [ ] onCommentClick handles navigation +- [ ] `VeltCommentsSidebar` mounted for V1/sidebar-prop usage, or `VeltCommentsSidebarV2` / `` mounted directly for V2 setup +- [ ] `VeltSidebarButton` provides toggle +- [ ] `embedMode` set if using a custom container +- [ ] `position` is `'right'` or `'left'` (no other strings — the union is narrowed) +- [ ] `forceClose` is explicitly set when the default (`true`) is not desired — do not assume the old default of `false` +- [ ] `onCommentClick` handles navigation +- [ ] Deprecated event aliases (`openSidebar`, `sidebarCommentClick`, `onSidebarCommentClick`, `enableUrlNavigation`) are replaced with the canonical names in new code **Source Pointers:** - https://docs.velt.dev/async-collaboration/comments-sidebar/overview - Overview -- https://docs.velt.dev/async-collaboration/comments-sidebar/setup - Setup +- https://docs.velt.dev/async-collaboration/comments-sidebar/v1/customize-behavior - V1 setup + customize-behavior (`/customize-behavior` paths re-rooted to `/v1/customize-behavior`) +- https://docs.velt.dev/async-collaboration/comments-sidebar/v2/setup - V2 entry (direct `VeltCommentsSidebarV2` / `` setup) +- https://docs.velt.dev/api-reference/sdk/models/data-models#veltcommentssidebarprops - `VeltCommentsSidebarProps` diff --git a/skills/velt-comments-best-practices/rules/shared/ui/ui-autocomplete-primitives.md b/skills/velt-comments-best-practices/rules/shared/ui/ui-autocomplete-primitives.md index fea9256..151e265 100644 --- a/skills/velt-comments-best-practices/rules/shared/ui/ui-autocomplete-primitives.md +++ b/skills/velt-comments-best-practices/rules/shared/ui/ui-autocomplete-primitives.md @@ -2,7 +2,7 @@ title: Use Standalone Autocomplete Primitives for Custom Autocomplete UIs impact: MEDIUM impactDescription: Build fully custom autocomplete UIs without requiring the full VeltAutocomplete panel, using independently importable primitive components -tags: autocomplete, primitives, VeltAutocompleteOption, VeltAutocompleteChip, VeltAutocompleteEmpty, VeltAutocompleteEmptyWireframe, customization, ui, multiSelect, selectedFirstOrdering, readOnly, inline, contacts +tags: autocomplete, primitives, VeltAutocompleteOption, VeltAutocompleteChip, VeltAutocompleteEmpty, VeltAutocompleteEmptyWireframe, VeltAutocompletePanel, customization, ui, multiSelect, selectedFirstOrdering, readOnly, inline, contacts, defaultCondition, hideInput, enableOnFocus, position --- ## Use Standalone Autocomplete Primitives for Custom Autocomplete UIs @@ -154,12 +154,53 @@ These props are added to the parent `` / `` +**`VeltAutocompletePanel` — standalone panel (v5.0.2-beta.11+):** + +Use `VeltAutocompletePanel` when you need an autocomplete panel that is not tied to a text-input @-mention (e.g. an inline user picker, assignee selector, or standalone contact chooser). It accepts all of the `VeltAutocomplete` panel props plus `type`, `hideInput`, `placeholder`, `enableOnFocus`, `position`, and `defaultCondition`. + +```jsx +// React — inline user picker that always renders + +``` + +```html + + + +``` + +| React Prop | HTML Attribute | Type | Default | Description | +|------------|---------------|------|---------|-------------| +| `type` | `type` | `'contact' \| 'generic'` | `'contact'` | Type of options the panel renders | +| `hideInput` | `hide-input` | `boolean` | `false` | Hides the search input | +| `placeholder` | `placeholder` | `string` | — | Placeholder text for the search input | +| `multiSelect` | `multi-select` | `boolean` | `false` | Allows selecting multiple contacts | +| `selectedFirstOrdering` | `selected-first-ordering` | `boolean` | `false` | Shows selected items first in the list | +| `readOnly` | `read-only` | `boolean` | `false` | Disables user interaction | +| `inline` | `inline` | `boolean` | `false` | Renders the panel inline | +| `enableOnFocus` | `enable-on-focus` | `boolean` | `false` | Opens the panel when the input receives focus | +| `position` | `position` | `'above' \| 'below' \| 'auto' \| string` | `'auto'` | Position of the panel relative to its anchor | +| `defaultCondition` | `default-condition` | `boolean` | `true` | When `false`, the component always renders regardless of internal state | + **Verification Checklist:** - [ ] Primitives imported from `'@veltdev/react'` individually (not from the full panel import path) - [ ] `VeltAutocompleteEmptyWireframe` is wrapped inside `` when customizing the empty state - [ ] `VeltAutocompleteOptionDescription` uses the `field` prop to specify which user field to display - [ ] HTML custom elements use separate opening and closing tags (not self-closing) +- [ ] When using `VeltAutocompletePanel` standalone, pick `VeltAutocompletePanel` (not `VeltAutocomplete`) for inline pickers that are not tied to a text-input @-mention **Source Pointers:** - https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-structure - Autocomplete and dialog customization - https://docs.velt.dev/api-reference/sdk/models/data-models - User model reference for `userObject` and `contacts` types +- https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/primitives - VeltAutocompletePanel standalone primitive reference diff --git a/skills/velt-comments-best-practices/rules/shared/ui/ui-comment-dialog.md b/skills/velt-comments-best-practices/rules/shared/ui/ui-comment-dialog.md index 28410d1..b2081d3 100644 --- a/skills/velt-comments-best-practices/rules/shared/ui/ui-comment-dialog.md +++ b/skills/velt-comments-best-practices/rules/shared/ui/ui-comment-dialog.md @@ -2,7 +2,7 @@ title: Customize Comment Dialog Appearance impact: MEDIUM impactDescription: Match comment dialogs to your application design system -tags: dialog, customization, styling, wireframe, ui +tags: dialog, customization, styling, wireframe, ui, darkMode, readOnly, sidebarMode, isFocusedThreadEnabled, openAnnotationInFocusMode, expandOnSelection, inlineCommentMode, inboxMode, isInsidePdfViewer, multiThread, commentComposerMode, dialogSelection, dialogMode, focusedThreadMode, pageModeComposer, messageTruncation, initialEditCommentIndex, messageTruncationLines, variant, composerPosition, sortBy, sortOrder, commentPinType, containerComponentId, targetElementId, targetComposerElementId, locationVersion, locationDisplayName, context, commentPlaceholder, replyPlaceholder, VeltCommentDialogThreadCardReactionPin, VeltCommentDialogThreadCardAssignButton, VeltCommentDialogThreadCardEditComposer, VeltCommentDialogOptionsDropdownContent, enableAssignment, enableEdit, enableNotifications, enablePrivateMode, enableMarkAsRead, VeltCommentDialogMoreReplyCount, VeltCommentDialogMoreReplyText, collapsedRepliesPreview --- ## Customize Comment Dialog Appearance @@ -18,6 +18,7 @@ Customize comment dialog appearance using variants, styling, and wireframe compo **Dark Mode:** ```jsx + ``` **Disable Shadow DOM (for CSS access):** @@ -26,6 +27,71 @@ Customize comment dialog appearance using variants, styling, and wireframe compo ``` +**`VeltCommentDialogProps` — full prop surface (v5.0.2-beta.11+):** + +The React interface mirrors the underlying `` HTML element's full attribute set. Use these props to control rendering modes, layout, sort, edit-mode placeholders, target binding for programmatic submission, and visual styling. + +```typescript +interface VeltCommentDialogProps { + annotationId?: string; + multiThreadAnnotationId?: string; + + // Display & mode flags + darkMode?: boolean; + readOnly?: boolean; + sidebarMode?: boolean; + isFocusedThreadEnabled?: boolean; + openAnnotationInFocusMode?: boolean; + expandOnSelection?: boolean; + inlineCommentMode?: boolean; + inboxMode?: boolean; + isInsidePdfViewer?: boolean; + multiThread?: boolean; + commentComposerMode?: boolean; + dialogSelection?: boolean; + dialogMode?: boolean; + focusedThreadMode?: boolean; + pageModeComposer?: boolean; + messageTruncation?: boolean; + initialEditCommentIndex?: number | string | null; + messageTruncationLines?: number | string; + + // Layout & styling + variant?: string; + composerPosition?: string; + sortBy?: string; + sortOrder?: string; + commentPinType?: 'bubble' | 'pin' | 'chart' | 'text'; + + // Target & context binding + containerComponentId?: string; + targetElementId?: string; + targetComposerElementId?: string; // For programmatic submitComment() + locationVersion?: string; + locationDisplayName?: string; + context?: any; + + // Placeholders (paired with config-component-props edit-mode rule) + commentPlaceholder?: string; + replyPlaceholder?: string; + editPlaceholder?: string; + editCommentPlaceholder?: string; + editReplyPlaceholder?: string; +} +``` + +```jsx +// Common combinations + +``` + **Wireframe Customization (full control):** Velt provides wireframe components for complete UI customization: @@ -87,12 +153,136 @@ velt-comment-dialog { /> ``` +**Thread-Card Primitives (v5.0.2-beta.11+):** + +Three new primitives let you place reaction pins, the assign-to button, and an inline edit composer directly inside a custom thread-card composition. + +```jsx +import { + VeltCommentDialogThreadCardReactionPin, + VeltCommentDialogThreadCardAssignButton, + VeltCommentDialogThreadCardEditComposer, +} from '@veltdev/react'; + +// Reaction pin inside a thread card + + +// Assign-to button inside a thread card + + +// Inline edit composer inside a thread card + +``` + +```html + + + + + + + + + +``` + +| Primitive | Extra Props (beyond Common Inputs) | +|-----------|-----------------------------------| +| `VeltCommentDialogThreadCardReactionPin` | `reactionId`, `commentObj`, `commentIndex`, `index` | +| `VeltCommentDialogThreadCardAssignButton` | `commentObj`, `commentId`, `commentIndex` | +| `VeltCommentDialogThreadCardEditComposer` | `commentObj`, `commentId`, `commentIndex` | + +**`VeltCommentDialogOptionsDropdownContent` — show/hide individual options (v5.0.2-beta.11+):** + +Previously documented as common-inputs-only; now exposes per-option enable flags so you can selectively render the assign / edit / notifications / private-mode / mark-as-read items inside the options dropdown. + +```jsx +// React — show only the edit option + +``` + +```html + + + +``` + +| Prop | Type | Description | +|------|------|-------------| +| `commentObj` | `any \| string` | Comment data object (or serialized JSON string for HTML) | +| `commentIndex` | `number \| string` | Index of comment in the array | +| `enableAssignment` | `boolean` | Shows the assign option | +| `enableEdit` | `boolean` | Shows the edit option | +| `enableNotifications` | `boolean` | Shows the notifications option | +| `enablePrivateMode` | `boolean` | Shows the private mode option | +| `enableMarkAsRead` | `boolean` | Shows the mark-as-read option | + +**Collapsed-Replies-Preview Primitives (v5.0.2-beta.37+):** + +Two primitives render the "Show N replies…" divider in a comment dialog's collapsed teaser (shown in the non-selected/preview state when `collapsedRepliesPreview` is enabled). They appear only when a thread has more than two comments. + +```jsx +import { + VeltCommentDialogMoreReplyCount, + VeltCommentDialogMoreReplyText, +} from '@veltdev/react'; + +// Hidden-reply count: annotation.comments.length - 2, clamped to >= 0 + + +// Pluralized noun: "reply" when one reply is hidden, otherwise "replies" + +``` + +```html + + +``` + +Both accept Common Inputs only. In React wireframe mode the public primitive is also exposed as the named sub-properties `VeltCommentDialogMoreReply.Count` and `.Text`; see `wireframe-variables-comment-dialog` for the separate wireframe-tree names. + **Verification Checklist:** - [ ] Variant applied if using pre-defined styles - [ ] shadowDom={false} if using custom CSS - [ ] Wireframes used for complex customization +- [ ] `VeltCommentDialogProps` flags use React camelCase (e.g. `darkMode`, `readOnly`, `pageModeComposer`); HTML attributes use kebab-case +- [ ] Thread-card primitives (`VeltCommentDialogThreadCard{ReactionPin,AssignButton,EditComposer}`) receive `annotationId` (+ `commentId` or `commentIndex` where relevant) +- [ ] `VeltCommentDialogOptionsDropdownContent` sets `enable*` flags for any individual options it should show; omitted flags default to the SDK's built-in behavior +- [ ] `VeltCommentDialogMoreReply{Count,Text}` used only inside the collapsed-replies-preview divider (threads with more than two comments); both take Common Inputs only **Source Pointers:** - https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-structure - Structure - https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/styling - Styling - https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/pre-defined-variants - Variants +- https://docs.velt.dev/api-reference/sdk/models/data-models#veltcommentdialogprops - VeltCommentDialogProps full attribute set +- https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog/primitives - Thread-card primitives and options-dropdown enable flags diff --git a/skills/velt-comments-best-practices/rules/shared/ui/ui-v2-primitives.md b/skills/velt-comments-best-practices/rules/shared/ui/ui-v2-primitives.md index 7b1d0d1..705dbac 100644 --- a/skills/velt-comments-best-practices/rules/shared/ui/ui-v2-primitives.md +++ b/skills/velt-comments-best-practices/rules/shared/ui/ui-v2-primitives.md @@ -2,14 +2,12 @@ title: Set defaultCondition on V2 Primitive Sub-Components to Control Default Rendering impact: MEDIUM impactDescription: Prevents the SDK's default show/hide logic from conflicting with custom wireframe compositions in V2 primitive component families -tags: v2-primitives, defaultCondition, wireframe, comment-pin, comment-bubble, text-comment, inline-comments-section, multi-thread-comment-dialog, sidebar-button, customization, ui +tags: v2-primitives, defaultCondition, wireframe, comment-pin, comment-bubble, text-comment, inline-comments-section, multi-thread-comment-dialog, sidebar-button, comments-sidebar-v2, VeltCommentSidebarV2, VeltMultiThreadCommentDialog, VeltInlineCommentsSectionFilterDropdownContentApplyButton, search, filter-button, filter-container, fullscreen-button, customization, ui --- ## Set defaultCondition on V2 Primitive Sub-Components to Control Default Rendering -Six comment component families have been migrated to the V2 primitive architecture: Comment Pin (6 primitives), Comment Bubble (3, HTML-only), Text Comment (7), Inline Comments Section (23), Multi-Thread Comment Dialog (24), and Sidebar Button (3). Every primitive in these families accepts a `defaultCondition` / `default-condition` prop. When a wireframe replaces a section of the UI, set `defaultCondition={false}` to bypass the SDK's built-in default show/hide logic and prevent double-rendering or unintended visibility toggles. - - +Seven comment component families use the V2 primitive architecture: Comment Pin (6 primitives), Comment Bubble (3, HTML-only), Text Comment (7), Inline Comments Section (24), Multi-Thread Comment Dialog (25), Sidebar Button (3), and Comments Sidebar V2 (56+ — expanded with the new Search / FilterButton / FilterContainer / FullscreenButton families this release). Every primitive in these families accepts a `defaultCondition` / `default-condition` prop. When a wireframe replaces a section of the UI, set `defaultCondition={false}` to bypass the SDK's built-in default show/hide logic and prevent double-rendering or unintended visibility toggles. **Incorrect (omitting defaultCondition when overriding a primitive section):** @@ -48,16 +46,17 @@ import { VeltWireframe } from '@veltdev/react'; ``` -**V2-Migrated Component Families (v5.0.2-beta.11+):** +**V2-Migrated Component Families:** | Family | Primitive Count | Notes | |--------|----------------|-------| | Comment Pin | 6 | React + HTML | | Comment Bubble | 3 | HTML-only primitives | | Text Comment | 7 | React + HTML | -| Inline Comments Section | 23 | React + HTML | -| Multi-Thread Comment Dialog | 24 | React + HTML | +| Inline Comments Section | 24 | React + HTML (ApplyButton promoted to React in v5.0.2-beta.11) | +| Multi-Thread Comment Dialog | 25 | React + HTML (`VeltMultiThreadCommentDialog` root added in v5.0.2-beta.11) | | Sidebar Button | 3 | React + HTML | +| Comments Sidebar V2 | 56+ | React + HTML; standalone HTML sub-primitive tags use the singular `velt-comment-sidebar-*-v2` form (root stays plural `velt-comments-sidebar-v2`); React identifiers are also singular `VeltCommentSidebarV2*` | | Comment Dialog Composer — Attachment Downloads | 2 | React + HTML; edit-mode only | **Attachment Download Primitives (edit-mode composer):** @@ -81,12 +80,178 @@ Both accept an `annotationId` prop (required, `string`) providing the attachment ``` +### Comments Sidebar V2 — naming convention + +> **Note:** The root container `VeltCommentsSidebarV2` / `` is plural. **Every** standalone sub-primitive — React identifier *and* HTML custom-element tag — uses the singular form `VeltCommentSidebarV2*` / ``. The HTML tag rename (plural → singular for sub-primitives) is the current release; React identifiers were already singular. + +**Incorrect (old plural HTML / React identifiers):** + +```jsx + + + + + + + +``` + +```html + + + + + + + + +``` + +**Correct (singular sub-primitive names for React *and* HTML; root stays plural):** + +```jsx + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +```html + + + + + + + + + + + + + + + + + + +``` + +| Identifier family (React + HTML) | Identifier | HTML element | +|----------------------------------|-----------|--------------| +| Skeleton | `VeltCommentSidebarV2Skeleton` | `velt-comment-sidebar-skeleton-v2` | +| Panel | `VeltCommentSidebarV2Panel` | `velt-comment-sidebar-panel-v2` | +| Header | `VeltCommentSidebarV2Header` | `velt-comment-sidebar-header-v2` | +| CloseButton | `VeltCommentSidebarV2CloseButton` | `velt-comment-sidebar-close-button-v2` | +| FullscreenButton (new) | `VeltCommentSidebarV2FullscreenButton` | `velt-comment-sidebar-fullscreen-button-v2` | +| Search (new) | `VeltCommentSidebarV2Search` (+ `Icon`, `Input`) | `velt-comment-sidebar-search-v2` (+ `-icon`, `-input`) | +| FilterButton (new) | `VeltCommentSidebarV2FilterButton` (+ `AppliedIcon`) | `velt-comment-sidebar-filter-button-v2` (+ `-applied-icon`) | +| FilterDropdown (+ subtree, incl. new `Content.List.Item.Count` and `Content.List.Category.Label` leaves) | `VeltCommentSidebarV2FilterDropdown*` | `velt-comment-sidebar-filter-dropdown-*-v2` | +| FilterContainer (new — Main Filter bottom-sheet subtree) | `VeltCommentSidebarV2FilterContainer*` | `velt-comment-sidebar-filter-container-*-v2` | +| List / ListItem | `VeltCommentSidebarV2List` / `*ListItem` | `velt-comment-sidebar-list-v2` / `velt-comment-sidebar-list-item-v2` | +| EmptyPlaceholder | `VeltCommentSidebarV2EmptyPlaceholder` | `velt-comment-sidebar-empty-placeholder-v2` | +| ResetFilterButton | `VeltCommentSidebarV2ResetFilterButton` | `velt-comment-sidebar-reset-filter-button-v2` | +| PageModeComposer | `VeltCommentSidebarV2PageModeComposer` | `velt-comment-sidebar-page-mode-composer-v2` | +| FocusedThread (+ subtree) | `VeltCommentSidebarV2FocusedThread*` | `velt-comment-sidebar-focused-thread-*-v2` | + +> **Breaking change (Comment Sidebar V2 — current release):** `VeltCommentSidebarV2MinimalActionsDropdown` (and the `Trigger` / `Content` / `MarkAllRead` / `MarkAllResolved` children) plus the corresponding `velt-comments-sidebar-minimal-actions-dropdown-v2` HTML family have been **removed**. The bulk actions are now exposed by the combined `actions` filter-dropdown, configured via the `minimalFilters` input on `VeltCommentsSidebarV2`. Replace any `MinimalActionsDropdown` usage with a `FilterDropdown` configured as `{ type: 'actions', sorts: [...], actions: [...] }` — see `surface/surface-sidebar-v2.md`. + +### New V2 primitive families (current release) + +- **Search** — header search container holding the icon + input leaves (`VeltCommentSidebarV2Search`, `*SearchIcon`, `*SearchInput`). +- **FilterButton** — header button that opens the Main Filter container; child `*FilterButtonAppliedIcon` surfaces the active-filter indicator. +- **FullscreenButton** — header leaf that emits `onFullscreenClick` when clicked. +- **FilterContainer** — root container for the Main Filter bottom-sheet / menu, holding: + - `Title`, `GroupBy`, `ResetButton`, `ApplyButton`, `CloseButton` leaves. + - `SectionList` → `Section` → `SectionLabel` (leaf) and `SectionField` → `SectionControl` (+ `SectionControlChevron`, `SectionControlValue`, `SectionControlChipList` → `SectionControlChip`, `SectionControlSearch`) and `SectionOptionList` → `SectionOption` (+ `SectionOptionCheckbox`, `SectionOptionName`, `SectionOptionCount`). + +The `Search`, `FilterButton`, `FilterContainer`, and `FullscreenButton` families replace the customization surface previously occupied by `MinimalActionsDropdown`. Use the `actions` dropdown type on `minimalFilters` for bulk mark-all-read / mark-all-resolved — these primitive families are the new shape for that surface. + +**`VeltInlineCommentsSectionFilterDropdownContentApplyButton` — React promotion (v5.0.2-beta.11+):** + +Previously HTML-only; now exposed as a React component with `targetElementId` and `defaultCondition` props. This brings the Inline Comments Section primitive family count to 24. + +```jsx + +``` + +```html + + +``` + +**`VeltMultiThreadCommentDialog` — new root primitive (v5.0.2-beta.11+):** + +A new root component for the multi-thread comment dialog family, with a matching standalone `` custom element. Multi-thread primitives can now also be used standalone by passing `multiThreadAnnotationId` to render with real annotation data without a parent root. + +```jsx + console.log(e)} +/> +``` + +```html + + +``` + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `annotationId` | `string` | — | The annotation ID | +| `multiThreadAnnotationId` | `string` | — | The multi-thread annotation ID | +| `annotation` | `any` | — | Annotation data object (serialized JSON in HTML) | +| `readOnly` | `boolean` | `false` | Disables user interaction | +| `defaultCondition` | `boolean` | `true` | When `false`, the component always renders regardless of internal state | +| `variant` | `string` | — | Visual variant for the component | +| `inboxMode` | `boolean` | `false` | Renders the dialog in inbox mode | +| `onSaveComment` | `Function` | — | Callback fired when a comment is saved (HTML: listen via `addEventListener('onSaveComment', ...)`) | + **Verification Checklist:** - [ ] `defaultCondition={false}` is set on any V2 primitive whose section is fully replaced by a custom wireframe - [ ] Primitive components are wrapped inside a `` block (React) or `` wrapper (HTML) - [ ] HTML attributes use kebab-case: `default-condition="false"` -- [ ] Only primitives from the six V2-migrated families are targeted (Comment Pin, Comment Bubble, Text Comment, Inline Comments Section, Multi-Thread Comment Dialog, Sidebar Button) +- [ ] Only primitives from the V2-migrated families are targeted (Comment Pin, Comment Bubble, Text Comment, Inline Comments Section, Multi-Thread Comment Dialog, Sidebar Button, Comments Sidebar V2) +- [ ] V2 sidebar sub-primitives use the singular `VeltCommentSidebarV2*` React identifiers **and** singular `velt-comment-sidebar-*-v2` HTML tags; the root component stays `VeltCommentsSidebarV2` / `velt-comments-sidebar-v2` +- [ ] Any `MinimalActionsDropdown` usage is migrated to a `FilterDropdown` configured via `minimalFilters: [{ type: 'actions', sorts: [...], actions: [...] }]` +- [ ] New families (`Search`, `FilterButton`, `FilterContainer`, `FullscreenButton`) are composed inside `Header` for the modern V2 sidebar header layout +- [ ] Multi-thread primitives used standalone pass `multiThreadAnnotationId` to bind real annotation data without the parent `VeltMultiThreadCommentDialog` root **Source Pointers:** - https://docs.velt.dev/ui-customization/overview - Wireframe and primitive architecture overview - https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-structure - Comment dialog primitives reference +- https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-v2-primitives - V2 sidebar primitive catalog (56+ primitives, singular HTML tag rename, MinimalActionsDropdown removal) +- https://docs.velt.dev/ui-customization/features/async/comments/inline-comments-section/primitives - Inline Comments Section primitives (incl. ApplyButton React promotion) +- https://docs.velt.dev/ui-customization/features/async/comments/multithread-comments/primitives - Multi-Thread Comment Dialog primitives (incl. new root) diff --git a/skills/velt-comments-best-practices/rules/shared/ui/ui-wireframes.md b/skills/velt-comments-best-practices/rules/shared/ui/ui-wireframes.md index d928261..5ba22eb 100644 --- a/skills/velt-comments-best-practices/rules/shared/ui/ui-wireframes.md +++ b/skills/velt-comments-best-practices/rules/shared/ui/ui-wireframes.md @@ -216,6 +216,76 @@ As of v5.0.1-beta.2, the wireframe template for the resolve and unresolve button - `List` - Comment list - `EmptyPlaceholder` - Empty state +**V2 Sidebar Wireframe Subtrees (`VeltCommentsSidebarV2Wireframe.*` / `velt-comments-sidebar-*-v2-wireframe`):** + +The V2 sidebar wireframe catalog gained five new subtrees and lost the MinimalActionsDropdown family. Compose them inside `VeltWireframe` / ``. + +- `Search` — header search row (and its `Icon` + `Input` leaves). +- `FilterButton` — opens the Main Filter container; child `AppliedIcon` leaf surfaces the active-filter indicator. +- `FilterContainer` — Main Filter bottom-sheet / menu subtree: + - `Title`, `GroupBy`, `ResetButton`, `ApplyButton`, `CloseButton` leaves. + - `SectionList` → `Section` → `SectionLabel` (leaf) and `SectionField` → `SectionControl` (+ `SectionControlChevron`, `SectionControlValue`, `SectionControlChipList` → `SectionControlChip`, `SectionControlSearch`) and `SectionOptionList` → `SectionOption` (+ `SectionOptionCheckbox`, `SectionOptionName`, `SectionOptionCount`). +- `FullscreenButton` — leaf header toggle that emits the new `onFullscreenClick` event. +- `ListGroupHeader` — renders once per group when grouping is enabled; child leaves `Label`, `Count`, `Chevron`, `Separator`. +- `FilterDropdown.Content.List.Item.Count` — new leaf under the existing `FilterDropdown` subtree. +- `FilterDropdown.Content.List.Category.Label` — new leaf alongside the existing `Category.Content`. + +> **Breaking change (Comment Sidebar V2 — current release):** `VeltCommentsSidebarV2Wireframe.MinimalActionsDropdown` (Trigger / Content / MarkAllRead / MarkAllResolved) and the `velt-comments-sidebar-minimal-actions-dropdown-v2-wireframe` family are removed from the wireframe catalog. The actions are now exposed by the combined `actions` filter-dropdown, configured via the `minimalFilters` input on `VeltCommentsSidebarV2`. Migrate any custom wireframes to a `FilterDropdown` (or `FilterContainer`) composition. + +```jsx +// React — V2 sidebar header composed against the new wireframe subtree + + + + + + + + + + + + + + + + + + + + + + + +``` + +```html + + + + + + + + + + + + + + + + + + + + + + + + +``` + **For HTML:** ```html @@ -305,7 +375,10 @@ These controls only render when a message exceeds the `messageTruncationLines` t - [ ] Framework naming convention followed - [ ] Required subcomponents included - [ ] When accessing annotation data in wireframe templates, use `annotations` or `allAnnotations` shorthand variables (v5.0.2-beta.11+) instead of long-form signal paths +- [ ] V2 sidebar header compositions use `Search` / `FilterButton` / `FilterContainer` / `FullscreenButton` / `FilterDropdown` — `MinimalActionsDropdown` and its descendants are no longer in the catalog +- [ ] V2 sidebar list compositions place `ListGroupHeader` (+ `Label`, `Count`, `Chevron`, `Separator`) inside `List` **Source Pointers:** - https://docs.velt.dev/ui-customization/features/async/comments/comment-dialog-structure - Dialog wireframe - https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-structure - Sidebar wireframe +- https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar-structure-v2 - V2 Sidebar wireframe structure (Search / FilterButton / FilterContainer / FullscreenButton / ListGroupHeader) diff --git a/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-dialog.md b/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-dialog.md index b9e65f1..4878b65 100644 --- a/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-dialog.md +++ b/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-dialog.md @@ -2,7 +2,7 @@ title: Bind Comment Dialog Wireframe Slots Using Template Variables impact: MEDIUM impactDescription: Drives layout-mode styling, capability gating, composer state, thread-card iteration, and banner visibility inside the Comment Dialog wireframe family without re-subscribing to annotation state -tags: wireframe, template-variables, velt-data, velt-if, velt-class, defaultCondition, comment-dialog, composer, thread-card, banners +tags: wireframe, template-variables, velt-data, velt-if, velt-class, defaultCondition, comment-dialog, composer, thread-card, banners, collapsedRepliesPreview, more-reply --- ## Bind Comment Dialog Wireframe Slots Using Template Variables @@ -221,6 +221,7 @@ The dialog injects four root namespaces plus context-specific (loop-scoped) vari | `enableSignInButton` / `enableUpgradeButton` | `boolean` | Sign-in / upgrade buttons rendered. | | `enableGhostCommentsMessage` | `boolean` | Ghost-comment banner enabled. | | `replyAvatars` | `boolean` | Reply-avatars strip enabled. | +| `collapsedRepliesPreview` | `boolean` | Surface the collapsed teaser (first comment + "Show N replies…" divider + last comment) even while the dialog is non-selected/preview. Mirrors the `collapsedRepliesPreview` prop / `collapsed-replies-preview` attribute. Default `false`. | | `userMentions` | `boolean` | @-mention autocomplete enabled. | | `recordingSummaryEnabled` | `boolean` | Recording AI-summary feature enabled. | | `enableAttachment` | `boolean` | File attachments enabled. | @@ -395,7 +396,7 @@ The **visibility-banner dropdown subtree** mirrors the status / priority dropdow | `` (+ `-list-item-wireframe` child) | Strip of reply-author avatars. `shouldShow` requires `{replyAvatars}`. | | `` (+ `-count`, `-icon`, `-text` children) | "View N replies" toggle. | | `` | "Hide replies" toggle. | -| `` | "+N more" indicator. | +| `` (+ `-count-wireframe` / `-text-wireframe` children) | "Show N replies…" expander between the first comment and the rest — label composed as `Show` + `Count` + `Text`. `shouldShow` = (`isDialogSelected` **or** `collapsedRepliesPreview`) **and** `!showAllComments` **and** `annotation.comments.length > 2`. The `-count` child renders the hidden-reply count (`annotation.comments.length - 2`, clamped ≥ 0); the `-text` child renders the pluralized noun (`reply` / `replies`). Exposed in React as `VeltCommentDialogWireframe.MoreReply.Count` / `.Text`. | | `` | Inter-thread navigation. | **Auxiliary** (3 tags): diff --git a/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-sidebar.md b/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-sidebar.md index b9e0741..b32a325 100644 --- a/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-sidebar.md +++ b/skills/velt-comments-best-practices/rules/shared/wireframe-variables/wireframe-variables-comment-sidebar.md @@ -280,6 +280,56 @@ The full tag set runs to ~80 wireframe tags. The structural tree lives in `ui/ui | `` | Reset-filters button (used in empty placeholder). Gate with `{appliedFiltersCount} > 0`. | | `` / `` | Generic action / reset primitives used in placeholders. | +### V2 wireframe slots — Search, FilterButton, FilterContainer, FullscreenButton, ListGroupHeader + +The V2 sidebar wireframe family (`VeltCommentsSidebarV2Wireframe.*` / ``) introduces five new bindable slot subtrees on top of the V1 surface above. Each leaf wireframe exposes its own per-slot variables — **bind dynamic data on the leaf, not its container** (signals only update live when bound to the leaf signal). + +**Header — Search / FilterButton / FullscreenButton:** + +| Wireframe tag | Exposed variables / `shouldShow` | Notes | +|---|---|---| +| `` (+ `-icon-`, `-input-` leaves) | `placeholder`, `searchable` | Header search row. Bind `placeholder` on `-input-` to customize the search placeholder live. | +| `` (+ `-applied-icon-` leaf) | `isFilterActive`, `appliedCount` | Opens the Main Filter container. Drive the badge with `appliedCount`; gate the `-applied-icon-` leaf with `velt-if="{isFilterActive}"`. | +| `` | — | Header fullscreen toggle; emits the `onFullscreenClick` event upstream. | + +**FilterContainer (Main Filter bottom-sheet/menu subtree):** + +The `FilterContainer` wireframe is the new bottom-sheet/menu surface — distinct from the existing `FilterDropdown` header dropdown. Available variables across these leaves: `value`, `label`, `count`, `mode`, `selected`, `group`, `groupingEnabled`, `groupByOptions`, `chips`, `searchable`, `placeholder`, `isFilterActive`, `appliedCount`. + +| Wireframe tag | Exposed variables / `shouldShow` | Notes | +|---|---|---| +| `` | `isFilterActive`, `appliedCount` | Root container — holds title, group-by, section list, reset/apply/close. | +| `` | `label` | Panel title. | +| `` | `groupByOptions`, `groupingEnabled` | Renders only when grouping is enabled. Bind `groupByOptions` for its option list. | +| `` → `…-section-wireframe>` (loop) | inherits `section` (one per filter section) | Per-section iteration. | +| `` | `label`, `count` | Section header label + count. | +| `` | `searchable`, `mode` | Field container; `searchable` toggles the per-section search box. | +| `` (+ `-chevron-`, `-value-`, `-chip-list-` → `-chip-`, `-search-` leaves) | `value`, `chips`, `searchable`, `placeholder` | Section control row — chips list and inline search. | +| `` → `-section-option-wireframe>` (loop) | inherits `option` per row | Per-option iteration. | +| `` | `selected` | Per-option checkbox state. Gate with `velt-if="{selected}"` (or the unchecked sibling). | +| `` | `label` | Option label. | +| `` | `count` | Option facet count. Gate with `velt-if="{componentConfig.filterCount}"` if you want to honor the `filterCount` prop. | +| `` / `…-apply-button-…` / `…-close-button-…` | `appliedCount`, `isFilterActive` | Footer actions. The reset button is meaningful only when `{appliedCount} > 0`. | + +**List groups — ListGroupHeader:** + +| Wireframe tag | Exposed variables | Notes | +|---|---|---| +| `` | injects `group` (one per group when grouping is enabled) | Renders once per group inside ``. | +| `` | `group.label` | Group display label. | +| `` | `group.count` | Group annotation count. | +| `` | inherits `group.isExpanded` | Expand / collapse chevron — drive direction with `velt-class="'collapsed': !{group.isExpanded}"`. | +| `` | — | Inter-group separator. | + +**FilterDropdown subtree leaves (new this release):** + +| Wireframe tag | Exposed variables | +|---|---| +| `` | per-item `count` (new — surfaces per-option facet count alongside the existing indicator + label leaves). | +| `` | category `label` (new — sibling to the existing `Category.Content` leaf). | + +> **Breaking change (V2 — current release):** the `velt-comments-sidebar-minimal-actions-dropdown-v2-wireframe` family (Trigger / Content / MarkAllRead / MarkAllResolved) is removed. Mark-all-read and mark-all-resolved are now exposed by the combined `actions` filter-dropdown configured via the `minimalFilters` input on `VeltCommentsSidebarV2` — bind those rows inside the existing `FilterDropdown` subtree (`` + `…-item-count-v2-wireframe`). + ### `defaultCondition` and Common Props | React Prop | HTML Attribute | Type | Default | Behavior | @@ -317,14 +367,19 @@ The full tag set runs to ~80 wireframe tags. The structural tree lives in `ui/ui **Verification:** - [ ] Mapped names (`focusedAnnotation`, `appliedFiltersCount`, `filteredCommentAnnotationsCount`, `unreadCommentAnnotationCount`, `selectedMinimalFilterDropdownOption`, `annotation`, `annotations`, `user`, `darkMode`, `variant`) are referenced as bare short names - [ ] Every other property uses the full `componentConfig.` path (skeleton / empty / filter / virtual-scroll / mode state) -- [ ] Loop-scope (`focusedAnnotation` inside focused-thread, `filter` / `item` / `group` / `tag` inside their owning iteration) is used only inside the owning slot +- [ ] Loop-scope (`focusedAnnotation` inside focused-thread, `filter` / `item` / `group` / `tag` inside their owning iteration, `group` inside `list-group-header-v2`) is used only inside the owning slot - [ ] Empty-state copy + reset-filter button branch on `noCommentsFoundForAppliedFilters` (not `noCommentsFound`) when filters are applied - [ ] Skeleton vs. list mutual exclusion uses `{componentConfig.skeletonLoading}` to gate the skeleton — the list does not need an explicit `velt-if` (the wireframe handles it) - [ ] Nested comment-dialog wireframes (list-item, focused-thread, page-mode composer) use the full Comment Dialog variable surface — see `wireframe-variables-comment-dialog.md` - [ ] Tag names are copied verbatim — both `velt-comments-sidebar-…` and `velt-comment-sidebar-…` prefixes are valid depending on the subtree - [ ] Minimal-filter row gating compares `selectedMinimalFilterDropdownOption.filter` / `.sort` with `===`, not boolean coercion +- [ ] V2 FilterContainer leaves bind `value` / `label` / `count` / `selected` / `chips` / `placeholder` on the leaf wireframe (not its container) so signals update live +- [ ] V2 `FilterButton.AppliedIcon` is gated on `{isFilterActive}` and the badge text is driven by `{appliedCount}` +- [ ] V2 `ListGroupHeader.Chevron` is class-toggled on `{group.isExpanded}` (not unmounted) so collapse-state is reversible +- [ ] No references remain to the removed `velt-comments-sidebar-minimal-actions-dropdown-v2-wireframe` family — migrate to the `actions` filter-dropdown configured via `minimalFilters` **Source Pointers:** - https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-wireframe-variables — "Comment Sidebar Wireframe Variables" (full per-slot reference) +- https://docs.velt.dev/ui-customization/features/async/comments/comment-sidebar/comment-sidebar-v2-wireframes — "V2 Sidebar Wireframes" (Search / FilterButton / FilterContainer / FullscreenButton / ListGroupHeader new-slot bindings) - https://docs.velt.dev/ui-customization/template-variables — "Template Variables overview" -- Cross-reference: `ui/ui-wireframes.md` (structural catalog), `surface/surface-sidebar.md` (sidebar surface), `surface/surface-sidebar-v2.md` (V2 primitives), `wireframe-variables-comment-dialog.md` (variables that resolve inside nested dialog tags rendered by the list / focused-thread / page-mode composer), `wireframe-variables-comment-sidebar-button.md` (the button that opens this sidebar) +- Cross-reference: `ui/ui-wireframes.md` (structural catalog), `surface/surface-sidebar.md` (sidebar surface), `surface/surface-sidebar-v2.md` (V2 primitives + declarative filter model), `wireframe-variables-comment-dialog.md` (variables that resolve inside nested dialog tags rendered by the list / focused-thread / page-mode composer), `wireframe-variables-comment-sidebar-button.md` (the button that opens this sidebar) diff --git a/skills/velt-node-sdk-best-practices/AGENTS.full.md b/skills/velt-node-sdk-best-practices/AGENTS.full.md index be6a356..9e123d5 100644 --- a/skills/velt-node-sdk-best-practices/AGENTS.full.md +++ b/skills/velt-node-sdk-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Node Sdk Best Practices -**Version 0.2.0** +**Version 0.2.3** Velt June 2026 @@ -14,7 +14,7 @@ June 2026 ## Abstract -Implementation guide for the Velt Node SDK (@veltdev/node) covering its two backends — sdk.api.* (REST API, 17 services) and sdk.selfHosting.* (MongoDB + S3 self-hosted, 7 services + token) — with emphasis on response envelopes, lazy-load pattern for self-hosting services, positional-arg surprise on getToken/getAttachment/saveAttachment, typed error class hierarchy, and data models (PartialCommentAnnotation, BaseMetadata, resolvedByUserId three-state semantics, round-trip dict helpers). +Implementation guide for the Velt Node SDK (@veltdev/node) covering its two backends — sdk.api.* (REST API, 18 services) and sdk.selfHosting.* (MongoDB + S3 self-hosted, 7 services + token) — with emphasis on response envelopes, lazy-load pattern for self-hosting services, positional-arg surprise on getToken/getAttachment/saveAttachment, typed error class hierarchy, and data models (PartialCommentAnnotation, BaseMetadata, resolvedByUserId three-state semantics, round-trip dict helpers). --- @@ -23,6 +23,14 @@ Implementation guide for the Velt Node SDK (@veltdev/node) covering its two back 1. [Initialization & lifecycle](#1-initialization-lifecycle) — **CRITICAL** - 1.1 [Initialize VeltSDK in the right mode and wire shutdown](#11-initialize-veltsdk-in-the-right-mode-and-wire-shutdown) +2. [sdk.api.* REST backend](#2-sdkapi-rest-backend) — **HIGH** + - 2.1 [Drop unknown fields from REST writes with the FieldFilterOptions allowlist](#21-drop-unknown-fields-from-rest-writes-with-the-fieldfilteroptions-allowlist) + - 2.2 [Read the sdk.api.* envelope correctly and use the right service namespace](#22-read-the-sdkapi-envelope-correctly-and-use-the-right-service-namespace) + +3. [sdk.selfHosting.* MongoDB + S3](#3-sdkselfhosting-mongodb-s3) — **HIGH** + - 3.1 [Lazy-load self-hosting services and check the flat envelope](#31-lazy-load-self-hosting-services-and-check-the-flat-envelope) + - 3.2 [Pass file bytes positionally to saveAttachment; getAttachment is purely positional](#32-pass-file-bytes-positionally-to-saveattachment-getattachment-is-purely-positional) + 4. [Data models](#4-data-models) — **HIGH** - 4.1 [Use Correct PartialCommentAnnotation and BaseMetadata Shapes for Updates](#41-use-correct-partialcommentannotation-and-basemetadata-shapes-for-updates) @@ -49,6 +57,174 @@ Reference: `backend-sdks/node.mdx` (Installation; Quick Start → Initialize/Shu --- +## 2. sdk.api.* REST backend + +**Impact: HIGH** + +18 typed services that wrap the Velt REST API v2. Response envelope is `{ result: { status, message, data, ... } }`. Every method requires `organizationId` (write) or `organizationIds` (read). Service instances are available immediately — no async lazy-load. The `activities` / `commentAnnotations` / `notifications` add/update methods accept an optional `FieldFilterOptions` second argument (`{ filterUnknownFields: true }`) to drop unknown keys before the write. + +### 2.1 Drop unknown fields from REST writes with the FieldFilterOptions allowlist + +**Impact: MEDIUM (Opt-in payload narrowing keeps custom/unknown keys out of Velt REST writes; fail-open so a write is never blocked)** + +The `sdk.api.*` add/update methods on `activities`, `commentAnnotations`, and `notifications` accept an optional **second** argument, `FieldFilterOptions`. Pass `{ filterUnknownFields: true }` to narrow the request to exactly the fields the target Velt backend endpoint accepts, dropping unknown/custom keys before the request is sent. It is **opt-in** (defaults to `false`) and **fail-open**: if filtering throws, the original payload is sent, so enabling it never blocks a write. + +The eight methods that accept the option: `addActivities`, `updateActivities`, `addCommentAnnotations`, `updateCommentAnnotations`, `addComments`, `updateComments`, `addNotifications`, `updateNotifications`. + +**Incorrect (passing custom/unknown keys and assuming the backend strips them — they are forwarded as-is, and `isRead`/`isArchived` silently do nothing on update):** + +```ts +// Unknown `internalTag` is sent verbatim; nothing narrows it. +await sdk.api.notifications.addNotifications({ + organizationId: 'org-123', + documentId: 'doc-1', + notifications: [{ /* ... */ internalTag: 'debug' }], +}); + +// isRead/isArchived are NOT part of /v2/notifications/update — they are ignored. +await sdk.api.notifications.updateNotifications({ + organizationId: 'org-123', + notifications: [{ id: 'n-1', isRead: true }], +}); +``` + +**Correct (opt in with the second argument to drop unknown keys before sending):** + +```ts +await sdk.api.notifications.addNotifications( + { + organizationId: 'org-123', + documentId: 'doc-1', + notifications: [{ /* ... */ internalTag: 'debug' }], // internalTag dropped + }, + { filterUnknownFields: true }, +); +pickKnownFields(data: T, keys: readonly string[]): Partial; +filterRequest(request: T, spec: FilterSpec): T; + +interface FilterSpec { + keys: readonly string[]; // allowed top-level keys (everything else dropped) + arrays?: Record; // array-of-object fields, filtered per item + objects?: Record; // single-object fields, filtered recursively +} +``` + +Open-typed objects (`actionUser`, `context`, `metadata`, `from`, `entityData`, and user objects) pass through whole — their nested contents are never filtered. +**Exported utilities** — the field-allowlist module is exported from `@veltdev/node` so advanced callers can reuse the same logic: +- `pickKnownFields(data, keys)` — keeps only own-enumerable keys present in `keys`; values kept by reference (no recursion); non-object/array/null inputs returned unchanged. +- `filterRequest(request, spec)` — applies a `FilterSpec` recursively; never mutates input; fail-open (returns the original request on any error). +- Eight per-method specs are exported: `ADD_ACTIVITIES_SPEC`, `UPDATE_ACTIVITIES_SPEC`, `ADD_COMMENT_ANNOTATIONS_SPEC`, `UPDATE_COMMENT_ANNOTATIONS_SPEC`, `ADD_COMMENTS_SPEC`, `UPDATE_COMMENTS_SPEC`, `ADD_NOTIFICATIONS_SPEC`, `UPDATE_NOTIFICATIONS_SPEC`. See the docs for the full per-endpoint allowlisted-key tables. +**Note:** `UPDATE_NOTIFICATIONS_SPEC` intentionally excludes `isRead`/`isArchived` — they are absent from the backend `UpdateNotificationsSchemaV2` and unsupported by `/v2/notifications/update`, so they are dropped when filtering is on. + +Reference: `backend-sdks/node.mdx` — "Field Allowlist" (FieldFilterOptions, exported `pickKnownFields`/`filterRequest`/`FilterSpec`, per-endpoint specs) + +--- + +### 2.2 Read the sdk.api.* envelope correctly and use the right service namespace + +**Impact: HIGH (Wrong envelope check silently mis-reads every response; wrong service/method name is a runtime "is not a function")** + +Every `sdk.api.*` method returns the REST envelope and requires `organizationId`. Service instances are available immediately — there is no `await sdk.api.getXxx()` lazy-load (that pattern is `sdk.selfHosting.*` only). + +**Envelope** — success returns: + +**High-risk additions to remember:** + +```ts +const result = await sdk.api.documents.addDocuments({ + organizationId: 'org-123', + documents: [{ documentId: 'doc-1', documentName: 'My Document' }], +}); +if (result.result.status !== 'success') throw new Error(result.result.message); +``` + +Workspace and Token methods are workspace-scoped — they use `VELT_WORKSPACE_AUTH_TOKEN` and `VELT_WORKSPACE_ID` when set. + +Reference: `backend-sdks/node.mdx` (REST API Backend → all 18 service subsections); `api-reference/sdk/models/data-models.mdx` (Node SDK Types → `VeltApiResponse`, per-service request/response types) + +--- + +## 3. sdk.selfHosting.* MongoDB + S3 + +**Impact: HIGH** + +7 services backed by your own MongoDB (and optionally AWS S3 for attachments). Loader pattern: `const svc = await sdk.selfHosting.getXxx()` — instances cached after first call. Flat response envelope: `{ success, statusCode, data }` on success, `{ success: false, statusCode, error, errorCode }` on failure. Attachment uploads use a hybrid call shape — request object plus optional positional file args. + +### 3.1 Lazy-load self-hosting services and check the flat envelope + +**Impact: HIGH (Forgetting `await` returns a Promise (next call throws "is not a function"); reading the wrong envelope key silently mis-judges every result)** + +Self-hosting services are lazy-loaded with `await sdk.selfHosting.getXxx()` — the service instance is cached after the first call. Skip the `await` and you get a Promise object back, then every method on it throws "is not a function". + +**Envelope** — flat shape, NOT the nested `{ result: { status, ... } }` of `sdk.api.*`: + +**Incorrect — pre-v1.0.5 `user` field is silently ignored:** + +```ts +const svc = await sdk.selfHosting.getReactions(); +await svc.saveReactions({ + metadata: { organizationId: 'org-123', documentId: 'doc-1' }, + reactionAnnotation: { + 'reaction-1': { annotationId: 'reaction-1', icon: 'thumbsup', user: { userId: 'u-1' } }, // `user` is not a recognized key on v1.0.5+ + }, +}); +``` + +**Correct — use `from`:** + +```ts +const svc = await sdk.selfHosting.getReactions(); +await svc.saveReactions({ + metadata: { organizationId: 'org-123', documentId: 'doc-1' }, + reactionAnnotation: { + 'reaction-1': { annotationId: 'reaction-1', icon: 'thumbsup', from: { userId: 'u-1' }, metadata: {} }, + }, +}); +``` + +**Canonical write:** + +```ts +const svc = await sdk.selfHosting.getComments(); +const r = await svc.saveComments({ + metadata: { organizationId: 'org-123', documentId: 'doc-1' }, + commentAnnotation: { + 'annotation-1': { + annotationId: 'annotation-1', + comments: { '123456': { commentId: '123456', commentText: 'Hello' } }, + metadata: {}, + }, + }, +}); +// → { success: true, statusCode: 200, data: { saved: true } } +``` + +**Canonical delete:** + +```ts +const svc = await sdk.selfHosting.getComments(); +await svc.deleteComment({ + commentAnnotationId: 'annotation-1', + metadata: { organizationId: 'org-123' }, +}); +``` + +Reference: `backend-sdks/node.mdx` (Self-Hosting Backend opening + all 7 service subsections); `api-reference/sdk/models/data-models.mdx` (Node SDK Types → `VeltSelfHostingResponse`, per-method request types) + +--- + +### 3.2 Pass file bytes positionally to saveAttachment; getAttachment is purely positional + +**Impact: HIGH (Putting fileData inside the request object silently no-ops the S3 upload; wrapping getAttachment's args in an object returns nothing useful)** + +Attachments is the one self-hosting service that mixes a request object with positional file arguments. This trips up everyone the first time. + +**`saveAttachment(request, fileBuffer?, fileName?, mimeType?)`** — the request object goes first; the next three are optional positional args. Supply all three when you want the SDK to upload the body to S3; omit them when you're storing only metadata. + +Reference: `backend-sdks/node.mdx` (Self-Hosting Backend → Attachments) + +--- + ## 4. Data models **Impact: HIGH** diff --git a/skills/velt-node-sdk-best-practices/AGENTS.md b/skills/velt-node-sdk-best-practices/AGENTS.md index c156b27..4034854 100644 --- a/skills/velt-node-sdk-best-practices/AGENTS.md +++ b/skills/velt-node-sdk-best-practices/AGENTS.md @@ -1,11 +1,17 @@ # Velt Node Sdk Best Practices -|v0.2.0|Velt|June 2026 +|v0.2.3|Velt|June 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules ## 1. Initialization & lifecycle — CRITICAL |shared/init:{init-dual-mode.md} +## 2. sdk.api.* REST backend — HIGH +|shared/api:{api-field-allowlist.md,api-envelope-and-services.md} + +## 3. sdk.selfHosting.* MongoDB + S3 — HIGH +|shared/selfhost:{selfhost-lazy-load-and-services.md,selfhost-attachments-positional.md} + ## 4. Data models — HIGH |shared/models:{models-comment-annotation.md} diff --git a/skills/velt-node-sdk-best-practices/SKILL.md b/skills/velt-node-sdk-best-practices/SKILL.md index 46d8935..43a42ed 100644 --- a/skills/velt-node-sdk-best-practices/SKILL.md +++ b/skills/velt-node-sdk-best-practices/SKILL.md @@ -4,7 +4,7 @@ description: Velt Node SDK (`@veltdev/node`) implementation patterns for Node.js license: MIT metadata: author: velt - version: "0.2.0" + version: "0.2.3" --- # Velt Node SDK Best Practices @@ -48,7 +48,8 @@ The two return **different response envelopes** — that's the single highest-im - `init-dual-mode` — Pick the right `VeltSDK.initialize` shape (REST-only vs self-hosting) and wire `await sdk.close()` on shutdown ### 2. sdk.api.* (REST backend) -- `api-envelope-and-services` — Use the `{ result: { status, data } }` envelope; service-by-service method index for all 17 namespaces; `organizationId` is required on every method +- `api-envelope-and-services` — Use the `{ result: { status, data } }` envelope; service-by-service method index for all 18 namespaces; `organizationId` is required on every method +- `api-field-allowlist` — Pass `{ filterUnknownFields: true }` as the second arg to the activities/commentAnnotations/notifications add/update methods to drop unknown keys before the REST write (opt-in, fail-open); reuse via exported `pickKnownFields` / `filterRequest` / `FilterSpec` ### 3. sdk.selfHosting.* (MongoDB + S3) - `selfhost-lazy-load-and-services` — Lazy-load with `await sdk.selfHosting.getXxx()`; flat envelope with `success` + `errorCode`; per-service method index for all 7 services diff --git a/skills/velt-node-sdk-best-practices/metadata.json b/skills/velt-node-sdk-best-practices/metadata.json index 01a0128..752f1be 100644 --- a/skills/velt-node-sdk-best-practices/metadata.json +++ b/skills/velt-node-sdk-best-practices/metadata.json @@ -1,8 +1,8 @@ { - "version": "0.2.0", + "version": "0.2.3", "organization": "Velt", "date": "June 2026", - "abstract": "Implementation guide for the Velt Node SDK (@veltdev/node) covering its two backends — sdk.api.* (REST API, 17 services) and sdk.selfHosting.* (MongoDB + S3 self-hosted, 7 services + token) — with emphasis on response envelopes, lazy-load pattern for self-hosting services, positional-arg surprise on getToken/getAttachment/saveAttachment, typed error class hierarchy, and data models (PartialCommentAnnotation, BaseMetadata, resolvedByUserId three-state semantics, round-trip dict helpers).", + "abstract": "Implementation guide for the Velt Node SDK (@veltdev/node) covering its two backends — sdk.api.* (REST API, 18 services) and sdk.selfHosting.* (MongoDB + S3 self-hosted, 7 services + token) — with emphasis on response envelopes, lazy-load pattern for self-hosting services, positional-arg surprise on getToken/getAttachment/saveAttachment, typed error class hierarchy, and data models (PartialCommentAnnotation, BaseMetadata, resolvedByUserId three-state semantics, round-trip dict helpers).", "references": [ "https://docs.velt.dev", "https://docs.velt.dev/backend-sdks/node", diff --git a/skills/velt-node-sdk-best-practices/rules/shared/_sections.md b/skills/velt-node-sdk-best-practices/rules/shared/_sections.md index 2a30266..0c30989 100644 --- a/skills/velt-node-sdk-best-practices/rules/shared/_sections.md +++ b/skills/velt-node-sdk-best-practices/rules/shared/_sections.md @@ -7,14 +7,14 @@ --- -## 2. sdk.api.* (REST backend) (api) +## 2. sdk.api.* REST backend (api) **Impact:** HIGH -**Description:** 17 typed services that wrap the Velt REST API v2. Response envelope is `{ result: { status, message, data, ... } }`. Every method requires `organizationId` (write) or `organizationIds` (read). Service instances are available immediately — no async lazy-load. +**Description:** 18 typed services that wrap the Velt REST API v2. Response envelope is `{ result: { status, message, data, ... } }`. Every method requires `organizationId` (write) or `organizationIds` (read). Service instances are available immediately — no async lazy-load. The `activities` / `commentAnnotations` / `notifications` add/update methods accept an optional `FieldFilterOptions` second argument (`{ filterUnknownFields: true }`) to drop unknown keys before the write. --- -## 3. sdk.selfHosting.* (MongoDB + S3) (selfhost) +## 3. sdk.selfHosting.* MongoDB + S3 (selfhost) **Impact:** HIGH **Description:** 7 services backed by your own MongoDB (and optionally AWS S3 for attachments). Loader pattern: `const svc = await sdk.selfHosting.getXxx()` — instances cached after first call. Flat response envelope: `{ success, statusCode, data }` on success, `{ success: false, statusCode, error, errorCode }` on failure. Attachment uploads use a hybrid call shape — request object plus optional positional file args. diff --git a/skills/velt-node-sdk-best-practices/rules/shared/api/api-envelope-and-services.md b/skills/velt-node-sdk-best-practices/rules/shared/api/api-envelope-and-services.md index 3b4ee36..2aae10d 100644 --- a/skills/velt-node-sdk-best-practices/rules/shared/api/api-envelope-and-services.md +++ b/skills/velt-node-sdk-best-practices/rules/shared/api/api-envelope-and-services.md @@ -28,27 +28,35 @@ if (result.success) { /* never runs */ } **Organization ID** — read methods take `organizationIds` (plural array); write/single-org methods take `organizationId` (singular string) at the root of the request. -**Service-by-service method index** (17 namespaces): +**Service-by-service method index** (18 namespaces): | # | Namespace | Methods | |---|---|---| | 1 | `sdk.api.organizations` | `addOrganizations`, `getOrganizations`, `updateOrganizations`, `deleteOrganizations`, `updateOrganizationDisableState` | | 2 | `sdk.api.folders` | `addFolder`, `getFolders`, `updateFolder`, `deleteFolder`, `updateFolderAccess` | -| 3 | `sdk.api.documents` | `addDocuments`, `getDocuments`, `updateDocuments`, `deleteDocuments`, `moveDocuments`, `updateDocumentAccess`, `updateDocumentDisableState`, `migrateDocuments`, `migrateDocumentsStatus` | -| 4 | `sdk.api.users` | `addUsers`, `getUsers`, `updateUsers`, `deleteUsers` | +| 3 | `sdk.api.documents` | `addDocuments`, `getDocuments`, `updateDocuments`, `deleteDocuments`, `moveDocuments`, `updateDocumentAccess`, `updateDocumentDisableState`, `migrateDocuments`, `migrateDocumentsStatus`, `getDocumentsCount` | +| 4 | `sdk.api.users` | `addUsers`, `getUsers`, `updateUsers`, `deleteUsers`, `getUsersCount`, `getDocUsers`, `addUserInvite`, `respondToUserInvite`, `getUserInvites`, `getUserInvitations`, `getInvitedPendingUsersCount` | | 5 | `sdk.api.userGroups` | `addUserGroups`, `addUsersToGroup`, `deleteUsersFromGroup` | | 6 | `sdk.api.notifications` | `addNotifications`, `getNotifications`, `updateNotifications`, `deleteNotifications`, `getNotificationConfig`, `setNotificationConfig` | | 7 | `sdk.api.commentAnnotations` | `addCommentAnnotations`, `getCommentAnnotations`, `getCommentAnnotationsCount`, `updateCommentAnnotations`, `deleteCommentAnnotations`, `addComments`, `getComments`, `updateComments`, `deleteComments` | | 8 | `sdk.api.activities` | `addActivities`, `getActivities`, `updateActivities`, `deleteActivities` | | 9 | `sdk.api.accessControl` | `addPermissions`, `getPermissions`, `removePermissions`, `generateSignature`, `generateToken` | -| 10 | `sdk.api.crdt` | `addCrdtData`, `getCrdtData`, `updateCrdtData` | +| 10 | `sdk.api.crdt` | `addCrdtData`, `getCrdtData`, `updateCrdtData`, `deleteCrdtData` | | 11 | `sdk.api.presence` | `addPresence`, `updatePresence`, `deletePresence` | | 12 | `sdk.api.livestate` | `broadcastEvent` | | 13 | `sdk.api.recordings` | `getRecordings` | | 14 | `sdk.api.rewriter` | `askAi` | | 15 | `sdk.api.gdpr` | `deleteAllUserData`, `getAllUserData`, `getDeleteUserDataStatus` | -| 16 | `sdk.api.workspace` | `createWorkspace`, `getWorkspace`, `createApiKey`, `updateApiKey`, `getApiKeys`, `getApiKeyMetadata`, `resetAuthToken`, `getAuthTokens`, `addDomains`, `deleteDomains`, `getDomains`, `getEmailStatus`, `sendLoginLink`, `getEmailConfig`, `updateEmailConfig`, `getWebhookConfig`, `updateWebhookConfig` | +| 16 | `sdk.api.workspace` | `createWorkspace`, `getWorkspace`, `createApiKey`, `updateApiKey`, `getApiKeys`, `getApiKeyMetadata`, `resetAuthToken`, `getAuthTokens`, `addDomains`, `deleteDomains`, `getDomains`, `getRequestedDomains`, `acceptRejectAdditionalUrlRequest`, `createDomainRequest`, `getEmailStatus`, `sendLoginLink`, `getEmailConfig`, `updateEmailConfig`, `copyApiKey`, `updateApiKeyConfig`, `getNotificationConfig`, `updateNotificationConfig`, `getPermissionProviderConfig`, `updatePermissionProviderConfig`, `getActivityConfig`, `updateActivityConfig`, `ensureWorkspaceAuthToken`, `getWebhookConfig`, `updateWebhookConfig`, `getAdvancedWebhookConfig`, `updateAdvancedWebhookConfig`, `getAdvancedWebhookEndpoints`, `createAdvancedWebhookEndpoint`, `updateAdvancedWebhookEndpoint`, `deleteAdvancedWebhookEndpoint`, `getAdvancedWebhookEndpointSecret` | | 17 | `sdk.api.token` | `getToken` (positional args — see `pitfalls-token-and-envelopes`) | +| 18 | `sdk.api.approval` | `createDefinition`, `updateDefinition`, `deleteDefinition`, `getDefinition`, `listDefinitions`, `dispatchExecution`, `cancelExecution`, `getExecution`, `getExecutionEvents`, `listExecutions`, `cancelStep`, `resolveStep`, `recordAgentResolution`, `recordReviewerDecision` | + +The docs intentionally hide the Node SDK `sdk.api.agents` and `sdk.api.memory` sections for now. Do not add those namespaces from draft or commented MDX until they are visible in the published Node SDK reference. + +**High-risk additions to remember:** +- `sdk.api.documents.getDocumentsCount()` returns organization document counts, optionally scoped by `folderId` and `excludeFolderDocs`. +- `sdk.api.crdt.deleteCrdtData()` deletes CRDT editor data; omitting `editorIds` deletes CRDT data for all editors in the document. +- `sdk.api.approval.*` maps to the `/v2/workflow/*` Approval Engine workflow surface. Use it for definitions, executions, step cancellation/resolution, and reviewer/agent decisions. **Canonical call**: @@ -68,4 +76,4 @@ Workspace and Token methods are workspace-scoped — they use `VELT_WORKSPACE_AU - [ ] No `await sdk.api.getXxx()` calls — those are invented - [ ] Method name + namespace match the table above -**Source Pointer:** `backend-sdks/node.mdx` (REST API Backend → all 17 service subsections); `api-reference/sdk/models/data-models.mdx` (Node SDK Types → `VeltApiResponse`, per-service request/response types) +**Source Pointer:** `backend-sdks/node.mdx` (REST API Backend → all 18 service subsections); `api-reference/sdk/models/data-models.mdx` (Node SDK Types → `VeltApiResponse`, per-service request/response types) diff --git a/skills/velt-node-sdk-best-practices/rules/shared/api/api-field-allowlist.md b/skills/velt-node-sdk-best-practices/rules/shared/api/api-field-allowlist.md new file mode 100644 index 0000000..66dccf5 --- /dev/null +++ b/skills/velt-node-sdk-best-practices/rules/shared/api/api-field-allowlist.md @@ -0,0 +1,79 @@ +--- +title: Drop unknown fields from REST writes with the FieldFilterOptions allowlist +impact: MEDIUM +impactDescription: Opt-in payload narrowing keeps custom/unknown keys out of Velt REST writes; fail-open so a write is never blocked +tags: FieldFilterOptions, filterUnknownFields, pickKnownFields, filterRequest, FilterSpec, allowlist, ADD_NOTIFICATIONS_SPEC, UPDATE_NOTIFICATIONS_SPEC +--- + +## Drop unknown fields from REST writes with the FieldFilterOptions allowlist + +The `sdk.api.*` add/update methods on `activities`, `commentAnnotations`, and `notifications` accept an optional **second** argument, `FieldFilterOptions`. Pass `{ filterUnknownFields: true }` to narrow the request to exactly the fields the target Velt backend endpoint accepts, dropping unknown/custom keys before the request is sent. It is **opt-in** (defaults to `false`) and **fail-open**: if filtering throws, the original payload is sent, so enabling it never blocks a write. + +The eight methods that accept the option: `addActivities`, `updateActivities`, `addCommentAnnotations`, `updateCommentAnnotations`, `addComments`, `updateComments`, `addNotifications`, `updateNotifications`. + +```ts +interface FieldFilterOptions { + // When true, narrow the request to only the fields the Velt backend endpoint + // accepts, silently dropping unknown keys. Fail-open. Defaults to false. + filterUnknownFields?: boolean; +} +``` + +**Incorrect (passing custom/unknown keys and assuming the backend strips them — they are forwarded as-is, and `isRead`/`isArchived` silently do nothing on update):** + +```ts +// Unknown `internalTag` is sent verbatim; nothing narrows it. +await sdk.api.notifications.addNotifications({ + organizationId: 'org-123', + documentId: 'doc-1', + notifications: [{ /* ... */ internalTag: 'debug' }], +}); + +// isRead/isArchived are NOT part of /v2/notifications/update — they are ignored. +await sdk.api.notifications.updateNotifications({ + organizationId: 'org-123', + notifications: [{ id: 'n-1', isRead: true }], +}); +``` + +**Correct (opt in with the second argument to drop unknown keys before sending):** + +```ts +await sdk.api.notifications.addNotifications( + { + organizationId: 'org-123', + documentId: 'doc-1', + notifications: [{ /* ... */ internalTag: 'debug' }], // internalTag dropped + }, + { filterUnknownFields: true }, +); +``` + +Open-typed objects (`actionUser`, `context`, `metadata`, `from`, `entityData`, and user objects) pass through whole — their nested contents are never filtered. + +**Exported utilities** — the field-allowlist module is exported from `@veltdev/node` so advanced callers can reuse the same logic: + +```ts +pickKnownFields(data: T, keys: readonly string[]): Partial; +filterRequest(request: T, spec: FilterSpec): T; + +interface FilterSpec { + keys: readonly string[]; // allowed top-level keys (everything else dropped) + arrays?: Record; // array-of-object fields, filtered per item + objects?: Record; // single-object fields, filtered recursively +} +``` + +- `pickKnownFields(data, keys)` — keeps only own-enumerable keys present in `keys`; values kept by reference (no recursion); non-object/array/null inputs returned unchanged. +- `filterRequest(request, spec)` — applies a `FilterSpec` recursively; never mutates input; fail-open (returns the original request on any error). +- Eight per-method specs are exported: `ADD_ACTIVITIES_SPEC`, `UPDATE_ACTIVITIES_SPEC`, `ADD_COMMENT_ANNOTATIONS_SPEC`, `UPDATE_COMMENT_ANNOTATIONS_SPEC`, `ADD_COMMENTS_SPEC`, `UPDATE_COMMENTS_SPEC`, `ADD_NOTIFICATIONS_SPEC`, `UPDATE_NOTIFICATIONS_SPEC`. See the docs for the full per-endpoint allowlisted-key tables. + +**Note:** `UPDATE_NOTIFICATIONS_SPEC` intentionally excludes `isRead`/`isArchived` — they are absent from the backend `UpdateNotificationsSchemaV2` and unsupported by `/v2/notifications/update`, so they are dropped when filtering is on. + +**Verification:** +- [ ] `filterUnknownFields: true` passed as the **second** argument (not nested in the request object) +- [ ] Only used on the 8 add/update methods of `activities` / `commentAnnotations` / `notifications` +- [ ] Not relied on to apply `isRead`/`isArchived` via `updateNotifications` — those are unsupported by the endpoint +- [ ] Aware filtering is fail-open: a malformed spec does not block the write, it sends the original payload + +**Source Pointer:** `backend-sdks/node.mdx` — "Field Allowlist" (FieldFilterOptions, exported `pickKnownFields`/`filterRequest`/`FilterSpec`, per-endpoint specs) diff --git a/skills/velt-node-sdk-best-practices/rules/shared/selfhost/selfhost-lazy-load-and-services.md b/skills/velt-node-sdk-best-practices/rules/shared/selfhost/selfhost-lazy-load-and-services.md index ce94a37..33cee80 100644 --- a/skills/velt-node-sdk-best-practices/rules/shared/selfhost/selfhost-lazy-load-and-services.md +++ b/skills/velt-node-sdk-best-practices/rules/shared/selfhost/selfhost-lazy-load-and-services.md @@ -53,6 +53,31 @@ Token is separate — it's a synchronous property `sdk.selfHosting.token`, not a - Activities has no `deleteActivity` method - Self-hosting Users only exposes `getUsers` — use `sdk.api.users.*` for write paths - Recorder's loader is `getRecorder` (singular), not `getRecorders` +- `saveReactions` reactionAnnotation entries: only `annotationId` is required; `icon`, `from`, and `metadata` are all optional. The reacting user goes in `from` (a `PartialUser`) — **renamed from `user` in `@veltdev/node` v1.0.5**, matching the frontend `PartialReactionAnnotation.from`. Pre-v1.0.5 code using `user` is silently dropped. + +**Incorrect — pre-v1.0.5 `user` field is silently ignored:** + +```ts +const svc = await sdk.selfHosting.getReactions(); +await svc.saveReactions({ + metadata: { organizationId: 'org-123', documentId: 'doc-1' }, + reactionAnnotation: { + 'reaction-1': { annotationId: 'reaction-1', icon: 'thumbsup', user: { userId: 'u-1' } }, // `user` is not a recognized key on v1.0.5+ + }, +}); +``` + +**Correct — use `from`:** + +```ts +const svc = await sdk.selfHosting.getReactions(); +await svc.saveReactions({ + metadata: { organizationId: 'org-123', documentId: 'doc-1' }, + reactionAnnotation: { + 'reaction-1': { annotationId: 'reaction-1', icon: 'thumbsup', from: { userId: 'u-1' }, metadata: {} }, + }, +}); +``` **Canonical write:** @@ -87,5 +112,6 @@ await svc.deleteComment({ - [ ] Loader names match the table (plural for most, `getRecorder` is the exception) - [ ] `database` was supplied to `VeltSDK.initialize()` — else these methods throw - [ ] No `deleteActivity` or self-hosting `saveUsers`/`deleteUsers` — those don't exist +- [ ] `saveReactions` entries use `from` (not `user`) for the reacting user on `@veltdev/node` v1.0.5+ **Source Pointer:** `backend-sdks/node.mdx` (Self-Hosting Backend opening + all 7 service subsections); `api-reference/sdk/models/data-models.mdx` (Node SDK Types → `VeltSelfHostingResponse`, per-method request types) diff --git a/skills/velt-notifications-best-practices/AGENTS.full.md b/skills/velt-notifications-best-practices/AGENTS.full.md index eb8c4d1..a255541 100644 --- a/skills/velt-notifications-best-practices/AGENTS.full.md +++ b/skills/velt-notifications-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Notifications Best Practices -**Version 1.1.0** +**Version 1.1.3** Velt January 2026 @@ -189,7 +189,27 @@ function ConfigureNotifications() { } ``` -Reference: https://docs.velt.dev/async-collaboration/notifications/customize-behavior - Tab Configuration +**Primitive-Level Feed Selection (`listType`):** + +```jsx +import { + VeltNotificationsPanelContentList, + VeltNotificationsPanelContentLoadMore, +} from '@veltdev/react'; + +// Render the "For You" feed in a custom panel + + +``` + +**For HTML:** + +```html + + +``` + +`listType` is ignored when `notifications` is bound directly to the list primitive. --- @@ -291,10 +311,14 @@ function NotificationButton() { **Controlling Initial Load Count:** -```html +```jsx // Control how many notifications load initially (v4.7.1+) - +``` + +**For HTML:** + +```html ``` @@ -364,6 +388,27 @@ notificationElement.enableCurrentDocumentOnly(); notificationElement.disableCurrentDocumentOnly(); ``` +**Primitive-Level Document Scoping (`documentId`):** + +```jsx +import { + VeltNotificationsPanelContentList, + VeltNotificationsPanelContentLoadMore, +} from '@veltdev/react'; + + + +``` + +**For HTML:** + +```html + + +``` + +`documentId` is ignored when `notifications` is bound directly to the list primitive. + --- ## 3. Data Access @@ -2046,3 +2091,4 @@ Reference: https://docs.velt.dev/async-collaboration/notifications/setup - Setup - https://console.velt.dev - https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel/wireframe-variables - https://docs.velt.dev/ui-customization/features/async/notifications/notifications-tool/wireframe-variables +- https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel/primitives diff --git a/skills/velt-notifications-best-practices/AGENTS.md b/skills/velt-notifications-best-practices/AGENTS.md index 1e81cc4..4fb3c6c 100644 --- a/skills/velt-notifications-best-practices/AGENTS.md +++ b/skills/velt-notifications-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Notifications Best Practices -|v1.1.0|Velt|January 2026 +|v1.1.3|Velt|January 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules diff --git a/skills/velt-notifications-best-practices/SKILL.md b/skills/velt-notifications-best-practices/SKILL.md index dbee71f..83289d5 100644 --- a/skills/velt-notifications-best-practices/SKILL.md +++ b/skills/velt-notifications-best-practices/SKILL.md @@ -64,7 +64,7 @@ Reference these guidelines when: ### 5. Configuration (MEDIUM) -- `config-cross-organization` — Cross-organization notifications: enableCrossOrganization/disableCrossOrganization, CrossOrganizationConfig (organizationIds, excludeOrganizationIds, maxOrganizations), "For You" feed merging, getCrossOrganizationConfig$() subscription +- `config-cross-organization` — Cross-organization notifications: enableCrossOrganization/disableCrossOrganization, CrossOrganizationConfig (organizationIds, excludeOrganizationIds), "For You" feed merging, getCrossOrganizationConfig$() subscription ### 6. Notification Triggers (MEDIUM) diff --git a/skills/velt-notifications-best-practices/metadata.json b/skills/velt-notifications-best-practices/metadata.json index 9744cca..2945302 100644 --- a/skills/velt-notifications-best-practices/metadata.json +++ b/skills/velt-notifications-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.1.3", "organization": "Velt", "date": "January 2026", "abstract": "Comprehensive Velt Notifications implementation guide covering in-app notifications, email delivery, webhook integrations, and user preference management. This skill provides evidence-backed patterns for integrating Velt's notification system into React, Next.js, and other web applications. Covers notification panel setup, tab configuration, data access hooks and APIs, settings management, custom notification triggers, and delivery channel routing.", @@ -11,6 +11,7 @@ "https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel", "https://console.velt.dev", "https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel/wireframe-variables", - "https://docs.velt.dev/ui-customization/features/async/notifications/notifications-tool/wireframe-variables" + "https://docs.velt.dev/ui-customization/features/async/notifications/notifications-tool/wireframe-variables", + "https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel/primitives" ] } diff --git a/skills/velt-notifications-best-practices/rules/shared/config/config-cross-organization.md b/skills/velt-notifications-best-practices/rules/shared/config/config-cross-organization.md index 1e5c4b5..c854346 100644 --- a/skills/velt-notifications-best-practices/rules/shared/config/config-cross-organization.md +++ b/skills/velt-notifications-best-practices/rules/shared/config/config-cross-organization.md @@ -13,7 +13,6 @@ When users belong to multiple organizations, the notification panel's "For You" - This is opt-in — default behavior is unchanged unless explicitly enabled - Only the "For You" feed is supported. The `'all'` feed value in `CrossOrganizationConfig.feeds` is silently ignored with a warning - The current organization is always excluded from cross-org results (it's already shown by default) -- Default `maxOrganizations` is `20` ### React: Enable via Props @@ -24,14 +23,12 @@ The `enableCrossOrganization` prop works on both `VeltNotificationsTool` and `Ve -{/* Enable with specific org allowlist and limit */} +{/* Enable with specific org allowlist */} ``` @@ -46,7 +43,6 @@ notificationElement.enableCrossOrganization(); // Enable with config notificationElement.enableCrossOrganization({ organizationIds: ['org-1', 'org-2'], - maxOrganizations: 10, }); // Disable (preferred pattern) @@ -72,7 +68,7 @@ const subscription = notificationElement.getCrossOrganizationConfig$().subscribe - + ``` @@ -83,7 +79,6 @@ const subscription = notificationElement.getCrossOrganizationConfig$().subscribe | `enabled` | `boolean` | `true` | Set to `false` to disable (equivalent to `disableCrossOrganization()`) | | `organizationIds` | `string[]` | — | Allowlist; when omitted, all indexed orgs are eligible | | `excludeOrganizationIds` | `string[]` | — | Additional orgs to exclude. Current org always excluded | -| `maxOrganizations` | `number` | `20` | Upper bound on orgs queried | | `feeds` | `('forYou' \| 'all')[]` | — | Only `'forYou'` is supported; `'all'` is ignored with a warning | **Equivalences:** Passing `{ enabled: false }` to `enableCrossOrganization()` is the same as calling `disableCrossOrganization()`. Passing `null` or calling without arguments opts in with all defaults. diff --git a/skills/velt-notifications-best-practices/rules/shared/panel/panel-current-document-only.md b/skills/velt-notifications-best-practices/rules/shared/panel/panel-current-document-only.md index 29be620..bd8854f 100644 --- a/skills/velt-notifications-best-practices/rules/shared/panel/panel-current-document-only.md +++ b/skills/velt-notifications-best-practices/rules/shared/panel/panel-current-document-only.md @@ -2,7 +2,7 @@ title: Filter Notifications to Current Document Only impact: MEDIUM impactDescription: Reduces notification noise by showing only current document notifications -tags: notifications, panel, document, filtering +tags: notifications, panel, document, filtering, documentId, primitives --- ## Filter Notifications to Current Document Only @@ -65,10 +65,35 @@ notificationElement.enableCurrentDocumentOnly(); notificationElement.disableCurrentDocumentOnly(); ``` +**Primitive-Level Document Scoping (`documentId`):** + +When building a custom panel out of primitives instead of `VeltNotificationsTool`, pass `documentId` to `VeltNotificationsPanelContentList` and `VeltNotificationsPanelContentLoadMore` to render and paginate notifications for a single document from `notificationsByDocumentId`. This scopes the list/load-more to that document without flipping the global `enableCurrentDocumentOnly()` switch. + +```jsx +import { + VeltNotificationsPanelContentList, + VeltNotificationsPanelContentLoadMore, +} from '@veltdev/react'; + + + +``` + +**For HTML:** + +```html + + +``` + +`documentId` is ignored when `notifications` is bound directly to the list primitive. + **Verification Checklist:** - [ ] `enableCurrentDocumentOnly()` called after Velt client is initialized - [ ] Document ID is set via `setDocument()` before enabling - [ ] `disableCurrentDocumentOnly()` used when switching back to multi-document view +- [ ] When using primitives, the same `documentId` is passed to both the list and the matching load-more so pagination stays scoped to that document **Source Pointers:** - https://docs.velt.dev/async-collaboration/notifications/customize-behavior - enableCurrentDocumentOnly +- https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel/primitives - VeltNotificationsPanelContentList, VeltNotificationsPanelContentLoadMore diff --git a/skills/velt-notifications-best-practices/rules/shared/panel/panel-display.md b/skills/velt-notifications-best-practices/rules/shared/panel/panel-display.md index 903efcc..da2c6db 100644 --- a/skills/velt-notifications-best-practices/rules/shared/panel/panel-display.md +++ b/skills/velt-notifications-best-practices/rules/shared/panel/panel-display.md @@ -106,8 +106,9 @@ function NotificationButton() { ``` +**For HTML:** + ```html - ``` diff --git a/skills/velt-notifications-best-practices/rules/shared/panel/panel-tabs.md b/skills/velt-notifications-best-practices/rules/shared/panel/panel-tabs.md index 5a1ac62..6d8e04c 100644 --- a/skills/velt-notifications-best-practices/rules/shared/panel/panel-tabs.md +++ b/skills/velt-notifications-best-practices/rules/shared/panel/panel-tabs.md @@ -2,7 +2,7 @@ title: Configure Notification Panel Tabs impact: HIGH impactDescription: Customize which notification categories users see -tags: panel, tabs, forYou, all, documents, tabConfig +tags: panel, tabs, forYou, all, documents, tabConfig, listType, primitives --- ## Configure Notification Panel Tabs @@ -83,9 +83,36 @@ function ConfigureNotifications() { } ``` +**Primitive-Level Feed Selection (`listType`):** + +When building a custom panel out of primitives instead of `VeltNotificationsTool`, use the `listType` prop on `VeltNotificationsPanelContentList` and `VeltNotificationsPanelContentLoadMore` to choose which feed each primitive renders or paginates from the shared context. Valid values: `'all'` (default) and `'for-you'`. + +```jsx +import { + VeltNotificationsPanelContentList, + VeltNotificationsPanelContentLoadMore, +} from '@veltdev/react'; + +// Render the "For You" feed in a custom panel + + +``` + +**For HTML:** + +```html + + +``` + +`listType` is ignored when `notifications` is bound directly to the list primitive. + **Verification:** - [ ] tabConfig object uses correct tab keys - [ ] Each tab has name and enable properties - [ ] Disabled tabs do not appear in panel +- [ ] When using primitives, `listType` matches the intended feed (`'all'` vs `'for-you'`) on both the list and the load-more button -**Source Pointer:** https://docs.velt.dev/async-collaboration/notifications/customize-behavior - Tab Configuration +**Source Pointers:** +- https://docs.velt.dev/async-collaboration/notifications/customize-behavior - Tab Configuration +- https://docs.velt.dev/ui-customization/features/async/notifications/notifications-panel/primitives - VeltNotificationsPanelContentList, VeltNotificationsPanelContentLoadMore diff --git a/skills/velt-rest-apis-best-practices/AGENTS.full.md b/skills/velt-rest-apis-best-practices/AGENTS.full.md index 1d6559b..ac784a7 100644 --- a/skills/velt-rest-apis-best-practices/AGENTS.full.md +++ b/skills/velt-rest-apis-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Rest Apis Best Practices -**Version 1.0.4** +**Version 1.0.5** Velt May 2026 @@ -29,9 +29,11 @@ Comprehensive guide for integrating Velt's server-side surface: the Velt REST AP - 2.2 [Approval Engine REST API — moved to its own skill](#22-approval-engine-rest-api-moved-to-its-own-skill) - 2.3 [Comment Annotations and Comments CRUD via REST API](#23-comment-annotations-and-comments-crud-via-rest-api) - 2.4 [Document, Organization, and Folder Management via REST API](#24-document-organization-and-folder-management-via-rest-api) - - 2.5 [Manage Advanced Webhooks via REST API](#25-manage-advanced-webhooks-via-rest-api) - - 2.6 [Notification Management via REST API](#26-notification-management-via-rest-api) - - 2.7 [User Management via REST API](#27-user-management-via-rest-api) + - 2.5 [List agent executions through the Agents REST API](#25-list-agent-executions-through-the-agents-rest-api) + - 2.6 [Manage Advanced Webhooks via REST API](#26-manage-advanced-webhooks-via-rest-api) + - 2.7 [Notification Management via REST API](#27-notification-management-via-rest-api) + - 2.8 [Use Memory REST APIs for judgments, knowledge, alerts, and suggestions](#28-use-memory-rest-apis-for-judgments-knowledge-alerts-and-suggestions) + - 2.9 [User Management via REST API](#29-user-management-via-rest-api) 3. [Webhooks](#3-webhooks) — **MEDIUM** - 3.1 [Webhook v1 Setup and Event Handling](#31-webhook-v1-setup-and-event-handling) @@ -298,7 +300,7 @@ References: **Impact: HIGH** -CRUD patterns for the Velt REST API v2 surface — comment annotations and comments, notifications and notification config, users (add / get / update / delete plus GDPR data operations), documents / organizations / folders, activity logs / CRDT documents, and the Approval Engine (14 `/v2/workflow/` endpoints covering definitions, executions, and steps). All endpoints are POST and use the `https://api.velt.dev/v2` base URL; endpoint identity is verbatim (path and version prefix matter). Includes request and response shape guidance, including the GET response envelope (annotation-level fields, expanded `reactionAnnotations` objects vs. `reactionAnnotationIds`, timestamp formats), idempotency guidance for execution dispatch, and webhook signature verification patterns. +CRUD patterns for the Velt REST API v2 surface — comment annotations and comments, notifications and notification config, users (add / get / update / delete plus GDPR data operations), documents / organizations / folders, activity logs / CRDT documents, agent execution listing, Memory judgments / knowledge / alerts, and the Approval Engine pointer. All endpoints are POST and use the `https://api.velt.dev/v2` base URL; endpoint identity is verbatim (path and version prefix matter). Includes request and response shape guidance, including the GET response envelope (annotation-level fields, expanded `reactionAnnotations` objects vs. `reactionAnnotationIds`, timestamp formats), idempotency guidance, and webhook signature verification patterns. ### 2.1 Activity Logs and CRDT Data Endpoints @@ -755,7 +757,64 @@ References: --- -### 2.5 Manage Advanced Webhooks via REST API +### 2.5 List agent executions through the Agents REST API + +**Impact: MEDIUM (Server-side pagination of agent execution history without fetching executions one by one)** + +Use `POST /v2/agents/execution/list` to paginate through an agent's execution history. This endpoint is for reading execution summaries by agent, document, organization, or status; it is not the workflow/Approval Engine API. + +**Incorrect (using workflow endpoints or fetching executions one at a time):** + +```bash +# Wrong family: workflow executions are Approval Engine executions, +# not generic agent execution history. +POST https://api.velt.dev/v2/workflow/executions/list +{ "data": { "agentId": "agent_123" } } +``` + +**Correct (list agent execution summaries):** + +```json +POST https://api.velt.dev/v2/agents/execution/list +x-velt-api-key: YOUR_API_KEY +x-velt-auth-token: YOUR_AUTH_TOKEN + +{ + "data": { + "agentId": "agent_123", + "documentId": "doc_001", + "status": "failed", + "pageSize": 50, + "orderDirection": "desc" + } +} +{ + "result": { + "items": [ + { + "id": "exec_1711900000000_abc123", + "agentId": "agent_123", + "agentName": "Brand Consistency Checker", + "agentVersion": 3, + "status": "passed", + "message": "Found 7 issues across 12 pages.", + "startedAt": 1711900000000, + "completedAt": 1711900150000, + "durationMs": 150000, + "trigger": "standalone" + } + ], + "nextCursor": "eyJvZmZzZXQiOjUwfQ==", + "hasMore": true + } +} +``` + +The response returns `result.items`, plus `nextCursor` and `hasMore` for pagination. + +--- + +### 2.6 Manage Advanced Webhooks via REST API **Impact: MEDIUM (Programmatically enable advanced webhooks and manage delivery endpoints, signing secrets, and per-endpoint event/channel filters)** @@ -844,7 +903,7 @@ POST https://api.velt.dev/v2/workspace/advancedwebhook/endpoints/secret/get --- -### 2.6 Notification Management via REST API +### 2.7 Notification Management via REST API **Impact: MEDIUM (Notifications keep users informed of collaboration events — misconfigured templates produce broken messages)** @@ -1001,7 +1060,85 @@ Reference: `https://docs.velt.dev/api-reference/rest-api/notifications` (## REST --- -### 2.7 User Management via REST API +### 2.8 Use Memory REST APIs for judgments, knowledge, alerts, and suggestions + +**Impact: HIGH (Memory endpoints provide grounded search and knowledge workflows; wrong retrieval mode or ingest path creates misleading AI results)** + +Use the `/v2/memory/*` REST API family for grounded review memory: semantic search over judgments, Q&A and decision suggestions, knowledge ingestion/search, profile/pattern/stat insights, and proactive alerts. These endpoints use the standard REST envelope and the same `x-velt-api-key` / `x-velt-auth-token` headers as the rest of v2. + +**Incorrect (treating Memory as a chat completion endpoint):** + +```bash +# Do not invent an answer when Memory has no grounding context. +POST https://api.velt.dev/v2/memory/ask +{ "data": { "question": "What policy do we follow for medical claims?" } } +``` + +**Correct (handle empty grounded results explicitly):** + +```bash +POST https://api.velt.dev/v2/memory/ask +x-velt-api-key: YOUR_API_KEY +x-velt-auth-token: YOUR_AUTH_TOKEN + +{ + "data": { + "question": "What policy do we follow for medical claims?", + "organizationId": "org_123" + } +} +``` + +If retrieval finds no relevant context, `answer` is an empty string with `confidence: 0`. Treat that as "Memory has nothing to say yet", not as a model failure to patch over. +| Group | Endpoints | Notes | +|-------|-----------|-------| +| Judgments | `/v2/memory/search`, `/v2/memory/judgments/query` | `search` is semantic; `judgments/query` is structured listing. `filters.annotationId` requires `organizationId`. | +| Q&A and decisions | `/v2/memory/ask`, `/v2/memory/suggest` | `ask` returns grounded answers with citations; `suggest` returns `primary` and optional `conflict` recommendations. | +| Knowledge | `/v2/memory/knowledge/ingest`, `upload-url`, `ingest-status`, `update`, `delete`, `search`, `list`, `download`, `rules` | Ingestion is async. Inline files are up to 5 MB decoded; by-reference files use `upload-url` and support up to 30 MB. | +| Insights | `/v2/memory/profiles/get`, `/v2/memory/patterns/get`, `/v2/memory/stats/get` | Derived reviewer/profile/pattern/stat views over remembered judgments. | +| Alerts | `/v2/memory/alerts/list`, `dismiss`, `action`, `config/get`, `config/update` | Alerts are proactive signals; list is capped at 50 active alerts. | + +**Knowledge ingest pattern:** + +```bash +# Inline file, up to 5 MB decoded. +POST https://api.velt.dev/v2/memory/knowledge/ingest +{ + "data": { + "source": "inline", + "file": { + "base64": "JVBERi0xLjQK...", + "mimeType": "application/pdf", + "fileName": "brand-guidelines.pdf", + "fileSize": 184320 + }, + "organizationId": "org_123" + } +} + +# Poll until terminal. +POST https://api.velt.dev/v2/memory/knowledge/ingest-status +{ "data": { "sourceId": "source_123" } } +``` + +**Search pattern:** + +```bash +POST https://api.velt.dev/v2/memory/search +{ + "data": { + "query": "marketing copy with unsupported medical claims", + "scope": "organization", + "organizationId": "org_123", + "limit": 5, + "filters": { "decision": "reject" } + } +} +``` + +--- + +### 2.9 User Management via REST API **Impact: HIGH (User provisioning and GDPR compliance are critical for production deployments)** @@ -1359,3 +1496,25 @@ Reference: `https://docs.velt.dev/api-reference/rest-api/overview` (## REST API - https://docs.velt.dev/api-reference/rest-apis/v2/notifications/add-notifications - https://docs.velt.dev/api-reference/rest-apis/v2/workspace/create - https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-update +- https://docs.velt.dev/api-reference/rest-apis/v2/agents/list-agent-executions +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/action +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/config/get +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/config/update +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/dismiss +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/list +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/ask +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/judgments/query +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/delete +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/download +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/ingest-status +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/ingest +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/list +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/rules +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/search +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/update +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/upload-url +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/patterns/get +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/profiles/get +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/search +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/stats/get +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/suggest diff --git a/skills/velt-rest-apis-best-practices/AGENTS.md b/skills/velt-rest-apis-best-practices/AGENTS.md index b6fc681..ba87006 100644 --- a/skills/velt-rest-apis-best-practices/AGENTS.md +++ b/skills/velt-rest-apis-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Rest Apis Best Practices -|v1.0.4|Velt|May 2026 +|v1.0.5|Velt|May 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules @@ -7,7 +7,7 @@ |shared/core:{core-rest-api-auth.md,core-jwt-tokens.md} ## 2. REST API Endpoints — HIGH -|shared/rest-api:{rest-activities-crdt.md,rest-approval-engine.md,rest-comments.md,rest-documents-orgs.md,rest-advanced-webhooks.md,rest-notifications.md,rest-users.md} +|shared/rest-api:{rest-activities-crdt.md,rest-approval-engine.md,rest-comments.md,rest-documents-orgs.md,rest-agents.md,rest-advanced-webhooks.md,rest-notifications.md,rest-memory.md,rest-users.md} ## 3. Webhooks — MEDIUM |shared/webhooks:{webhooks-basic.md,webhooks-advanced.md} diff --git a/skills/velt-rest-apis-best-practices/SKILL.md b/skills/velt-rest-apis-best-practices/SKILL.md index 05668b6..9185926 100644 --- a/skills/velt-rest-apis-best-practices/SKILL.md +++ b/skills/velt-rest-apis-best-practices/SKILL.md @@ -9,7 +9,7 @@ metadata: # Velt REST APIs Best Practices -Comprehensive guide for Velt REST API v2, JWT authentication, and webhooks. Contains 10 rules across 4 categories covering core setup, REST API endpoints, webhook handling, and debugging. +Comprehensive guide for Velt REST API v2, JWT authentication, and webhooks. Contains 12 rules across 4 categories covering core setup, REST API endpoints, webhook handling, and debugging. ## When to Apply @@ -42,6 +42,8 @@ Reference these guidelines when: - `rest-notifications` — Notification add/get/update/delete + config - `rest-activities-crdt` — Activity logs + CRDT data endpoints - `rest-advanced-webhooks` — Manage advanced webhooks: config enable + endpoint CRUD + signing-secret retrieval +- `rest-agents` — List agent execution history with pagination and status filters +- `rest-memory` — Memory judgments, knowledge ingestion/search, Q&A, suggestions, insights, and alerts - `rest-approval-engine` — pointer (Approval Engine is now its own skill: see `velt-approval-engine-best-practices`) ### Webhooks (MEDIUM) diff --git a/skills/velt-rest-apis-best-practices/metadata.json b/skills/velt-rest-apis-best-practices/metadata.json index 2c9ff27..68de556 100644 --- a/skills/velt-rest-apis-best-practices/metadata.json +++ b/skills/velt-rest-apis-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.0.4", + "version": "1.0.5", "organization": "Velt", "date": "May 2026", "abstract": "Comprehensive guide for integrating Velt's server-side surface: the Velt REST API v2, JWT-based authentication for the frontend SDK, and webhook event handling. Covers the required `x-velt-api-key` and `x-velt-auth-token` header contract, JWT token generation and refresh flows (48h expiry), full CRUD over comment annotations, comments, notifications, users (including GDPR data export/delete), documents, organizations, folders, activity logs and CRDT documents via REST. Also covers v1 webhook setup plus v2 / Svix enterprise webhooks with retries and transformations, payload shapes for comment, huddle and CRDT events, and signature verification. All guidance is evidence-backed from official Velt documentation. For the self-hosted Python SDK (`velt-py`) used to store data on your own infrastructure, see `velt-self-hosting-data-best-practices`.", @@ -14,6 +14,28 @@ "https://console.velt.dev", "https://docs.velt.dev/api-reference/rest-apis/v2/notifications/add-notifications", "https://docs.velt.dev/api-reference/rest-apis/v2/workspace/create", - "https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-update" + "https://docs.velt.dev/api-reference/rest-apis/v2/workspace/advancedwebhookconfig-update", + "https://docs.velt.dev/api-reference/rest-apis/v2/agents/list-agent-executions", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/action", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/config/get", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/config/update", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/dismiss", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/list", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/ask", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/judgments/query", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/delete", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/download", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/ingest-status", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/ingest", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/list", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/rules", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/search", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/update", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/upload-url", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/patterns/get", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/profiles/get", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/search", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/stats/get", + "https://docs.velt.dev/api-reference/rest-apis/v2/memory/suggest" ] } diff --git a/skills/velt-rest-apis-best-practices/rules/shared/_sections.md b/skills/velt-rest-apis-best-practices/rules/shared/_sections.md index b894f99..1f81f58 100644 --- a/skills/velt-rest-apis-best-practices/rules/shared/_sections.md +++ b/skills/velt-rest-apis-best-practices/rules/shared/_sections.md @@ -15,7 +15,7 @@ The section prefix (in parentheses) is the filename prefix used to group rules. ## 2. REST API Endpoints (rest-api) **Impact:** HIGH -**Description:** CRUD patterns for the Velt REST API v2 surface — comment annotations and comments, notifications and notification config, users (add / get / update / delete plus GDPR data operations), documents / organizations / folders, activity logs / CRDT documents, and the Approval Engine (14 `/v2/workflow/` endpoints covering definitions, executions, and steps). All endpoints are POST and use the `https://api.velt.dev/v2` base URL; endpoint identity is verbatim (path and version prefix matter). Includes request and response shape guidance, including the GET response envelope (annotation-level fields, expanded `reactionAnnotations` objects vs. `reactionAnnotationIds`, timestamp formats), idempotency guidance for execution dispatch, and webhook signature verification patterns. +**Description:** CRUD patterns for the Velt REST API v2 surface — comment annotations and comments, notifications and notification config, users (add / get / update / delete plus GDPR data operations), documents / organizations / folders, activity logs / CRDT documents, agent execution listing, Memory judgments / knowledge / alerts, and the Approval Engine pointer. All endpoints are POST and use the `https://api.velt.dev/v2` base URL; endpoint identity is verbatim (path and version prefix matter). Includes request and response shape guidance, including the GET response envelope (annotation-level fields, expanded `reactionAnnotations` objects vs. `reactionAnnotationIds`, timestamp formats), idempotency guidance, and webhook signature verification patterns. --- diff --git a/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-agents.md b/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-agents.md new file mode 100644 index 0000000..77e97ba --- /dev/null +++ b/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-agents.md @@ -0,0 +1,72 @@ +--- +title: List agent executions through the Agents REST API +impact: MEDIUM +impactDescription: Server-side pagination of agent execution history without fetching executions one by one +tags: rest, api, agents, executions, pagination +--- + +## List agent executions through the Agents REST API + +Use `POST /v2/agents/execution/list` to paginate through an agent's execution history. This endpoint is for reading execution summaries by agent, document, organization, or status; it is not the workflow/Approval Engine API. + +**Incorrect (using workflow endpoints or fetching executions one at a time):** + +```bash +# Wrong family: workflow executions are Approval Engine executions, +# not generic agent execution history. +POST https://api.velt.dev/v2/workflow/executions/list +{ "data": { "agentId": "agent_123" } } +``` + +**Correct (list agent execution summaries):** + +```bash +POST https://api.velt.dev/v2/agents/execution/list +x-velt-api-key: YOUR_API_KEY +x-velt-auth-token: YOUR_AUTH_TOKEN + +{ + "data": { + "agentId": "agent_123", + "documentId": "doc_001", + "status": "failed", + "pageSize": 50, + "orderDirection": "desc" + } +} +``` + +The response returns `result.items`, plus `nextCursor` and `hasMore` for pagination. + +```json +{ + "result": { + "items": [ + { + "id": "exec_1711900000000_abc123", + "agentId": "agent_123", + "agentName": "Brand Consistency Checker", + "agentVersion": 3, + "status": "passed", + "message": "Found 7 issues across 12 pages.", + "startedAt": 1711900000000, + "completedAt": 1711900150000, + "durationMs": 150000, + "trigger": "standalone" + } + ], + "nextCursor": "eyJvZmZzZXQiOjUwfQ==", + "hasMore": true + } +} +``` + +**Verification Checklist:** +- [ ] Endpoint path is exactly `/v2/agents/execution/list` +- [ ] Both `x-velt-api-key` and `x-velt-auth-token` headers are sent +- [ ] `pageSize` is between 1 and 500 when provided +- [ ] Pagination loops with `cursor = result.nextCursor` while `hasMore` is true +- [ ] `status` filters only use `running`, `passed`, `failed`, `error`, or `skipped` + +**Source Pointers:** +- https://docs.velt.dev/api-reference/rest-apis/v2/agents/list-agent-executions - "List Agent Executions" diff --git a/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-memory.md b/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-memory.md new file mode 100644 index 0000000..a7df780 --- /dev/null +++ b/skills/velt-rest-apis-best-practices/rules/shared/rest-api/rest-memory.md @@ -0,0 +1,99 @@ +--- +title: Use Memory REST APIs for judgments, knowledge, alerts, and suggestions +impact: HIGH +impactDescription: Memory endpoints provide grounded search and knowledge workflows; wrong retrieval mode or ingest path creates misleading AI results +tags: rest, api, memory, judgments, knowledge, alerts, suggestions +--- + +## Use Memory REST APIs for judgments, knowledge, alerts, and suggestions + +Use the `/v2/memory/*` REST API family for grounded review memory: semantic search over judgments, Q&A and decision suggestions, knowledge ingestion/search, profile/pattern/stat insights, and proactive alerts. These endpoints use the standard REST envelope and the same `x-velt-api-key` / `x-velt-auth-token` headers as the rest of v2. + +**Incorrect (treating Memory as a chat completion endpoint):** + +```bash +# Do not invent an answer when Memory has no grounding context. +POST https://api.velt.dev/v2/memory/ask +{ "data": { "question": "What policy do we follow for medical claims?" } } +``` + +**Correct (handle empty grounded results explicitly):** + +```bash +POST https://api.velt.dev/v2/memory/ask +x-velt-api-key: YOUR_API_KEY +x-velt-auth-token: YOUR_AUTH_TOKEN + +{ + "data": { + "question": "What policy do we follow for medical claims?", + "organizationId": "org_123" + } +} +``` + +If retrieval finds no relevant context, `answer` is an empty string with `confidence: 0`. Treat that as "Memory has nothing to say yet", not as a model failure to patch over. + +### Endpoint groups + +| Group | Endpoints | Notes | +|-------|-----------|-------| +| Judgments | `/v2/memory/search`, `/v2/memory/judgments/query` | `search` is semantic; `judgments/query` is structured listing. `filters.annotationId` requires `organizationId`. | +| Q&A and decisions | `/v2/memory/ask`, `/v2/memory/suggest` | `ask` returns grounded answers with citations; `suggest` returns `primary` and optional `conflict` recommendations. | +| Knowledge | `/v2/memory/knowledge/ingest`, `upload-url`, `ingest-status`, `update`, `delete`, `search`, `list`, `download`, `rules` | Ingestion is async. Inline files are up to 5 MB decoded; by-reference files use `upload-url` and support up to 30 MB. | +| Insights | `/v2/memory/profiles/get`, `/v2/memory/patterns/get`, `/v2/memory/stats/get` | Derived reviewer/profile/pattern/stat views over remembered judgments. | +| Alerts | `/v2/memory/alerts/list`, `dismiss`, `action`, `config/get`, `config/update` | Alerts are proactive signals; list is capped at 50 active alerts. | + +**Knowledge ingest pattern:** + +```bash +# Inline file, up to 5 MB decoded. +POST https://api.velt.dev/v2/memory/knowledge/ingest +{ + "data": { + "source": "inline", + "file": { + "base64": "JVBERi0xLjQK...", + "mimeType": "application/pdf", + "fileName": "brand-guidelines.pdf", + "fileSize": 184320 + }, + "organizationId": "org_123" + } +} + +# Poll until terminal. +POST https://api.velt.dev/v2/memory/knowledge/ingest-status +{ "data": { "sourceId": "source_123" } } +``` + +**Search pattern:** + +```bash +POST https://api.velt.dev/v2/memory/search +{ + "data": { + "query": "marketing copy with unsupported medical claims", + "scope": "organization", + "organizationId": "org_123", + "limit": 5, + "filters": { "decision": "reject" } + } +} +``` + +**Verification Checklist:** +- [ ] Every request wraps payload fields in `{ "data": { ... } }` +- [ ] Both REST auth headers are sent +- [ ] `ask` callers handle `answer: ""` and `confidence: 0` as no grounded context +- [ ] `filters.annotationId` is only used with `organizationId` +- [ ] Knowledge ingest polls `ingest-status` before depending on search/rules +- [ ] Inline files stay under 5 MB decoded; larger files use `knowledge/upload-url` + `source: "fileRef"` +- [ ] Memory search and knowledge search are not conflated: `/memory/search` searches judgments; `/memory/knowledge/search` searches ingested files + +**Source Pointers:** +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/search - "Search Judgments" +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/ask - "Ask Memory" +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/suggest - "Suggest Decision" +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/knowledge/ingest - "Ingest Knowledge" +- https://docs.velt.dev/api-reference/rest-apis/v2/memory/alerts/list - "List Alerts" diff --git a/skills/velt-self-hosting-data-best-practices/AGENTS.full.md b/skills/velt-self-hosting-data-best-practices/AGENTS.full.md index 8cbb306..f2702ea 100644 --- a/skills/velt-self-hosting-data-best-practices/AGENTS.full.md +++ b/skills/velt-self-hosting-data-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Self Hosting Data Best Practices -**Version 1.0.8** +**Version 1.0.12** Velt March 2026 @@ -1314,8 +1314,10 @@ interface DataProviderConfig { } interface RetryConfig { - retryCount: number; // Max retry attempts - retryDelay: number; // Delay between retries (ms) + retryCount?: number; // Max retry attempts + retryDelay?: number; // Delay between retries (ms) + revertOnFailure?: boolean; // Activity `saveRetryConfig` only — revert the optimistic cache + // update when the save ultimately fails after all retries } ``` @@ -1455,6 +1457,8 @@ Reference: https://docs.velt.dev/self-host-data/users The activity data provider handles PII for activity log records — comment text embedded in change history, feature-specific entity snapshots (e.g., PR titles, deployment metadata), and arbitrary custom fields. The SDK strips configured fields before writing to Velt and re-hydrates them on read via your `get` handler. +Both `get` and `save` can be supplied as either a **callback function** (`get` / `save`) **or** a **config endpoint URL** (`getConfig` / `saveConfig`). Each method is valid as long as one of the two forms is set; the modes can be mixed (e.g., function `get` with endpoint `saveConfig`). See [[provider-retry-timeout]] for the retry/timeout knobs shared across all providers. + **ActivityAnnotationDataProvider interface:** ```typescript @@ -1478,10 +1482,21 @@ interface SaveActivityResolverRequest { interface ResolverConfig { resolveTimeout?: number; - fieldsToRemove?: string[]; // Extra fields to strip beyond defaults + getRetryConfig?: RetryConfig; // Retry behavior for `get` + saveRetryConfig?: RetryConfig; // Retry behavior for `save` (supports `revertOnFailure`) + getConfig?: ResolverEndpointConfig; // Endpoint URL + headers for fetching activity PII + saveConfig?: ResolverEndpointConfig; // Endpoint URL + headers for saving stripped activity PII + fieldsToRemove?: string[]; // Extra fields to strip beyond defaults +} + +interface ResolverEndpointConfig { + url: string; + headers?: Record; } ``` +Note: activity is **append-only**, so there is no `delete` / `deleteConfig`. + **Function-based example:** ```tsx @@ -1509,11 +1524,35 @@ const activityDataProvider: ActivityAnnotationDataProvider = { }; // Wire into VeltProvider (or via client.setDataProviders / Velt.setDataProviders) + +const activityResolverConfig = { + getConfig: { + url: 'https://your-backend.com/api/velt/activity/get', + headers: { 'Authorization': 'Bearer YOUR_TOKEN' } + }, + saveConfig: { + url: 'https://your-backend.com/api/velt/activity/save', + headers: { 'Authorization': 'Bearer YOUR_TOKEN' } + }, + resolveTimeout: 60000, + getRetryConfig: { retryCount: 3, retryDelay: 2000 }, + saveRetryConfig: { retryCount: 3, retryDelay: 2000, revertOnFailure: true }, + fieldsToRemove: ['customSensitiveField'] +}; + +const activityDataProvider = { + config: activityResolverConfig +}; + ``` +**Endpoint-based example** (SDK performs the POST for you; pair `getConfig` and/or `saveConfig` with retry/timeout/`fieldsToRemove` on the same `config` object): +The SDK POSTs the same `GetActivityResolverRequest` / `SaveActivityResolverRequest` bodies the function-based handlers would receive, and expects the same `ResolverResponse` shape back. Do not modify the endpoint URLs — copy them verbatim into your config. `saveRetryConfig.revertOnFailure: true` rolls back the optimistic cache update when the save retries are exhausted. **Compatibility:** Currently only compatible with the `setDocuments` method. Providers must be set before `identify()` is called. **Storage-boundary contract (what persists where):** @@ -1577,7 +1616,7 @@ const activityDataProvider: ActivityAnnotationDataProvider = { }; ``` -Reference: https://docs.velt.dev/self-host-data/activity ("Sample Data"); https://docs.velt.dev/self-host-data/field-inventory - "Activity strip rules" +Reference: https://docs.velt.dev/self-host-data/activity ("Implementation Approaches", "Endpoint based DataProvider", "Function based DataProvider", "Sample Data"); https://docs.velt.dev/self-host-data/field-inventory - "Activity strip rules" --- @@ -2621,6 +2660,7 @@ result = sdk.api.documents.addDocuments( | GDPR | `sdk.api.gdpr` | | Workspace | `sdk.api.workspace` | | Token | `sdk.api.token` | +| Workflow | `sdk.api.workflow` | --- @@ -2727,7 +2767,66 @@ from velt_py import ( ) ``` -Reference: `https://docs.velt.dev/api-reference/sdk/python/users` (## Python SDK > ### Users & Reactions) +**Verification:** + +```python +from velt_py.models.reaction import PartialReactionAnnotation +from velt_py.models.user import PartialUser + +@dataclass +class PartialReactionAnnotation: + annotationId: str + metadata: Optional[BaseMetadata] = None + icon: Optional[str] = None + from_: Optional[PartialUser] = None # 'from' on the wire; from_ avoids the Python keyword. Replaces the former 'user' field. + extra_fields: Optional[Dict[str, Any]] = None # Catch-all for customer-configured custom keys. +``` + +**Incorrect (v0.1.11-style construction; breaks on v0.1.12):** + +```python +from velt_py.models.reaction import PartialReactionAnnotation +from velt_py.models.user import PartialUser + +# `user=` is no longer a valid constructor kwarg in v0.1.12 — raises TypeError +ann = PartialReactionAnnotation( + annotationId='r-1', + icon='+1', + user=PartialUser(userId='u-1'), +) +ann.to_dict() # would have emitted {'user': {...}} pre-v0.1.12 +``` + +**Correct (v0.1.12 — use `from_=`, serializes as `from`):** + +```python +from velt_py.models.reaction import PartialReactionAnnotation +from velt_py.models.user import PartialUser + +ann = PartialReactionAnnotation( + annotationId='r-1', + icon='+1', + from_=PartialUser(userId='u-1'), +) +ann.to_dict()['from'] # {'userId': 'u-1'} — serialized as `from` +``` + +**Correct (backward-compatible reads — legacy `user` documents still resolve):** + +```python +# Legacy document stored under the old `user` key still resolves: +ann = PartialReactionAnnotation.from_dict({ + 'annotationId': 'r-1', + 'icon': '+1', + 'user': {'userId': 'u-legacy'}, +}) +ann.from_.userId # 'u-legacy' +ann.to_dict()['from'] # {'userId': 'u-legacy'} (re-serialized as `from`) +``` + +References: +- `https://docs.velt.dev/api-reference/sdk/python/users` (## Python SDK > ### Users & Reactions) +- `https://docs.velt.dev/backend-sdks/python` (### `PartialReactionAnnotation`) --- @@ -2816,3 +2915,4 @@ Reference: https://docs.velt.dev/self-host-data/overview - "Debugging"; https:// - https://docs.velt.dev/self-host-data/activity - https://docs.velt.dev/self-host-data/notifications - https://docs.velt.dev/self-host-data/field-inventory +- https://docs.velt.dev/backend-sdks/python diff --git a/skills/velt-self-hosting-data-best-practices/AGENTS.md b/skills/velt-self-hosting-data-best-practices/AGENTS.md index 25dab53..d0706c5 100644 --- a/skills/velt-self-hosting-data-best-practices/AGENTS.md +++ b/skills/velt-self-hosting-data-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Self Hosting Data Best Practices -|v1.0.8|Velt|March 2026 +|v1.0.12|Velt|March 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules diff --git a/skills/velt-self-hosting-data-best-practices/SKILL.md b/skills/velt-self-hosting-data-best-practices/SKILL.md index 9472ec7..499d4cb 100644 --- a/skills/velt-self-hosting-data-best-practices/SKILL.md +++ b/skills/velt-self-hosting-data-best-practices/SKILL.md @@ -9,7 +9,7 @@ metadata: # Velt Self-Hosting Data Best Practices -Comprehensive implementation guide for Velt's self-hosting data feature in React and Next.js applications. Contains 18 rules across 7 categories, prioritized by impact to guide automated code generation and integration patterns. +Comprehensive implementation guide for Velt's self-hosting data feature in React and Next.js applications. Contains 24 rules across 8 categories, prioritized by impact to guide automated code generation and integration patterns. ## When to Apply @@ -32,8 +32,9 @@ Reference these guidelines when: | 3 | Attachment Data Provider | HIGH | `attachment-` | | 4 | Additional Providers | MEDIUM | `provider-` | | 5 | Backend Implementation | MEDIUM | `backend-` | -| 6 | Python SDK | HIGH | `python-` | -| 7 | Debugging | LOW-MEDIUM | `debug-` | +| 6 | Data Types | MEDIUM | `data-` | +| 7 | Python SDK | HIGH | `python-` | +| 8 | Debugging | LOW-MEDIUM | `debug-` | ## Quick Reference @@ -41,6 +42,8 @@ Reference these guidelines when: - `core-provider-setup` — Configure VeltProvider dataProviders prop with correct initialization order - `core-response-format` — Return the required response shape from all data provider handlers +- `core-auth-provider` — Use authProvider on VeltProvider with dataProviders; never call identify() +- `core-python-sdk-setup` — Install and initialize the Velt Python SDK (velt-py) ### 2. Comment Data Provider (HIGH) @@ -55,6 +58,9 @@ Reference these guidelines when: - `provider-user-resolver` — Implement read-only user data provider for PII protection - `provider-reaction-recording` — Configure reaction and recording data providers +- `provider-recorder` — Self-host recording data and media files +- `provider-notification` — Self-host notification data for custom notifications +- `provider-activity` — Self-host activity log data for custom activities (function-based and endpoint-based DataProvider) - `provider-retry-timeout` — Configure retry policies and timeouts per data provider ### 5. Backend Implementation (MEDIUM) @@ -63,17 +69,20 @@ Reference these guidelines when: - `backend-database-patterns` — Implement database storage with upsert and proper indexing - `backend-s3-attachments` — Store and delete attachments in S3-compatible object storage -### 6. Python SDK (HIGH) +### 6. Data Types (MEDIUM) -- `python-rest-api-backend` — Use sdk.api.* for REST API operations without a database (no MongoDB required) +- `data-types-reference` — Self-hosting provider interfaces, config, and request/response type reference (the SDK ↔ backend contract) + +### 7. Python SDK (HIGH) + +- `python-rest-api-backend` — Use sdk.api.* for REST API operations without a database (no MongoDB). Covers the REST services — documents `getDocumentsCount`, users invitation lifecycle, crdt `deleteCrdtData`, workspace extensions (domain requests, API key copy/update, advanced webhooks, `getApiKeyMetadata`), workflow definitions/executions — and the opt-in `filter_unknown_fields` flag on add/update methods - `python-comments` — Comments CRUD via sdk.selfHosting.comments - `python-attachments` — Attachment upload and delete via sdk.selfHosting.attachments with S3 -- `python-users-reactions` — Users and reactions management via sdk.selfHosting.users/reactions +- `python-users-reactions` — Users and reactions management via sdk.selfHosting.users/reactions, PartialReactionAnnotation model, and the v0.1.12 `user` → `from_` rename - `python-frameworks` — Django, Flask, and FastAPI integration patterns - `python-token` — Generate user auth tokens via sdk.selfHosting.token.getToken for frontend authProvider -- `python-data-models` — Python dataclasses for self-hosting comment annotations (PartialCommentAnnotation, PartialTargetTextRange, UNSET sentinel, BaseMetadata) introduced in v0.1.10 -### 7. Debugging (LOW-MEDIUM) +### 8. Debugging (LOW-MEDIUM) - `debug-data-provider-events` — Monitor data provider events for troubleshooting diff --git a/skills/velt-self-hosting-data-best-practices/metadata.json b/skills/velt-self-hosting-data-best-practices/metadata.json index d042d64..44bb703 100644 --- a/skills/velt-self-hosting-data-best-practices/metadata.json +++ b/skills/velt-self-hosting-data-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.0.8", + "version": "1.0.12", "organization": "Velt", "date": "March 2026", "abstract": "Comprehensive guide for Velt self-hosting data feature, enabling storage of sensitive user-generated content (comments, attachments, reactions, recordings, user PII) on your own infrastructure. Covers endpoint-based and function-based data providers, VeltProvider dataProviders configuration, backend API route patterns, database schemas, file storage, retry and timeout configuration, and debugging. All guidance is evidence-backed from official Velt documentation and sample applications.", @@ -14,6 +14,7 @@ "https://console.velt.dev", "https://docs.velt.dev/self-host-data/activity", "https://docs.velt.dev/self-host-data/notifications", - "https://docs.velt.dev/self-host-data/field-inventory" + "https://docs.velt.dev/self-host-data/field-inventory", + "https://docs.velt.dev/backend-sdks/python" ] } diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md b/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md index 1340d6a..0bcf09a 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/data/data-types-reference.md @@ -280,10 +280,12 @@ interface PartialReactionAnnotation { annotationId: string; // join key metadata?: BaseMetadata; // getClientMetadata(annotation.metadata ?? {}) icon: string; // the only relocated field — never sent to Velt - from?: PartialUser; // { userId }; copied-not-moved + from?: PartialUser; // { userId } — reaction author; copied-not-moved } ``` +The canonical field names on `PartialReactionAnnotation` are `icon` and `from`. Older docs and flow prose sometimes paraphrased these as "emoji" and "user"; that vocabulary is historical only — the wire payload, the SDK contract, and the Python `PartialReactionAnnotation` dataclass all use `icon` and `from` (`from_` on the Python side, since `from` is a keyword). + `icon` is the only field withheld from Velt; the strip operates on the emoji-code `icon` only. Per-element `Reaction` entries on the Velt-side `reactions[]` carry their own `variant` field — they are kept verbatim and are not part of the `Partial` payload. #### `PartialRecorderAnnotation` (your DB) diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-activity.md b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-activity.md index 865cf5f..f3f1eb0 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-activity.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-activity.md @@ -2,13 +2,15 @@ title: Self-Host Activity Log Data for Custom Activities impact: MEDIUM impactDescription: Route activity log PII, entity snapshots, and custom fields through your own infrastructure -tags: activity, ActivityAnnotationDataProvider, get, save, self-hosting, audit-log, isActivityResolverUsed +tags: activity, ActivityAnnotationDataProvider, get, save, getConfig, saveConfig, endpoint-based, function-based, self-hosting, audit-log, isActivityResolverUsed --- ## Self-Host Activity Log Data for Custom Activities The activity data provider handles PII for activity log records — comment text embedded in change history, feature-specific entity snapshots (e.g., PR titles, deployment metadata), and arbitrary custom fields. The SDK strips configured fields before writing to Velt and re-hydrates them on read via your `get` handler. +Both `get` and `save` can be supplied as either a **callback function** (`get` / `save`) **or** a **config endpoint URL** (`getConfig` / `saveConfig`). Each method is valid as long as one of the two forms is set; the modes can be mixed (e.g., function `get` with endpoint `saveConfig`). See [[provider-retry-timeout]] for the retry/timeout knobs shared across all providers. + **ActivityAnnotationDataProvider interface:** ```typescript @@ -32,10 +34,21 @@ interface SaveActivityResolverRequest { interface ResolverConfig { resolveTimeout?: number; - fieldsToRemove?: string[]; // Extra fields to strip beyond defaults + getRetryConfig?: RetryConfig; // Retry behavior for `get` + saveRetryConfig?: RetryConfig; // Retry behavior for `save` (supports `revertOnFailure`) + getConfig?: ResolverEndpointConfig; // Endpoint URL + headers for fetching activity PII + saveConfig?: ResolverEndpointConfig; // Endpoint URL + headers for saving stripped activity PII + fieldsToRemove?: string[]; // Extra fields to strip beyond defaults +} + +interface ResolverEndpointConfig { + url: string; + headers?: Record; } ``` +Note: activity is **append-only**, so there is no `delete` / `deleteConfig`. + **Function-based example:** ```tsx @@ -68,6 +81,35 @@ const activityDataProvider: ActivityAnnotationDataProvider = { }}> ``` +**Endpoint-based example** (SDK performs the POST for you; pair `getConfig` and/or `saveConfig` with retry/timeout/`fieldsToRemove` on the same `config` object): + +```tsx +const activityResolverConfig = { + getConfig: { + url: 'https://your-backend.com/api/velt/activity/get', + headers: { 'Authorization': 'Bearer YOUR_TOKEN' } + }, + saveConfig: { + url: 'https://your-backend.com/api/velt/activity/save', + headers: { 'Authorization': 'Bearer YOUR_TOKEN' } + }, + resolveTimeout: 60000, + getRetryConfig: { retryCount: 3, retryDelay: 2000 }, + saveRetryConfig: { retryCount: 3, retryDelay: 2000, revertOnFailure: true }, + fieldsToRemove: ['customSensitiveField'] +}; + +const activityDataProvider = { + config: activityResolverConfig +}; + + +``` + +The SDK POSTs the same `GetActivityResolverRequest` / `SaveActivityResolverRequest` bodies the function-based handlers would receive, and expects the same `ResolverResponse` shape back. Do not modify the endpoint URLs — copy them verbatim into your config. `saveRetryConfig.revertOnFailure: true` rolls back the optimistic cache update when the save retries are exhausted. + **Compatibility:** Currently only compatible with the `setDocuments` method. Providers must be set before `identify()` is called. **Storage-boundary contract (what persists where):** @@ -116,8 +158,10 @@ Stored-on-Velt example (everything the SDK retains when the resolver is active): Entity snapshots (`entityData`, `entityTargetData`), display message templates and their data, and any fields listed in `config.fieldsToRemove` are NOT stored on Velt — they live exclusively on your database and are merged back via `get` at render time. **Key details:** -- `get` and `save` only — there is no `delete` on the activity resolver +- `get` and `save` only — there is no `delete` on the activity resolver (and no `deleteConfig`) +- Each method has two equivalent forms: callback (`get` / `save`) or endpoint config (`getConfig` / `saveConfig`). At least one form per method is required; the two forms can be mixed per-method - `fieldsToRemove` extends the default strip list with extra custom field names (e.g., `['customSensitiveField']`) +- `saveRetryConfig.revertOnFailure: true` reverts the optimistic cache update when the save ultimately fails after retries — set this on the activity resolver to avoid leaving stale PII in the UI when your backend rejects a write - `isActivityResolverUsed: true` on `ActivityRecord` means PII has been stripped; use it to gate a loading skeleton while `get` is in flight - The `metadata` block contains both Velt-internal IDs (`documentId`, `organizationId`) and your client-facing IDs (`clientDocumentId`, `clientOrganizationId`) — both shapes live on Velt - Use a longer `resolveTimeout` (30–60s) than for comments since activity feeds can fan out across many records @@ -172,12 +216,16 @@ const activityDataProvider: ActivityAnnotationDataProvider = { ``` **Verification:** -- [ ] `get` returns `Record` with `entityData`, `entityTargetData`, and display templates hydrated from your DB -- [ ] `save` persists stripped fields to your DB and returns `ResolverResponse` +- [ ] `get` (or `getConfig.url`) returns `Record` with `entityData`, `entityTargetData`, and display templates hydrated from your DB +- [ ] `save` (or `saveConfig.url`) persists stripped fields to your DB and returns `ResolverResponse` +- [ ] Each of `get` / `save` has exactly one of: callback function OR endpoint config — never both for the same method +- [ ] Endpoint URLs are copied verbatim from your backend; the SDK posts the same `GetActivityResolverRequest` / `SaveActivityResolverRequest` body the callback would receive +- [ ] No `delete` / `deleteConfig` is configured — activity is append-only +- [ ] `saveRetryConfig.revertOnFailure` set to `true` if you want optimistic cache updates rolled back when save retries are exhausted - [ ] Provider set before `identify()` is called - [ ] Customer DB stores entity snapshots, display templates, template data, and any `fieldsToRemove` fields; Velt stores only minimal identifiers, action metadata, resolver flag, and `targetEntityId` - [ ] UI gates a loading skeleton on `isActivityResolverUsed === true` - [ ] `fieldsToRemove` is treated as `featureType === 'custom'`-only; built-in feature types do not strip extra fields through it - [ ] `displayMessage` is never persisted — only the template and template data are stored -**Source Pointer:** https://docs.velt.dev/self-host-data/activity ("Sample Data"); https://docs.velt.dev/self-host-data/field-inventory - "Activity strip rules" +**Source Pointer:** https://docs.velt.dev/self-host-data/activity ("Implementation Approaches", "Endpoint based DataProvider", "Function based DataProvider", "Sample Data"); https://docs.velt.dev/self-host-data/field-inventory - "Activity strip rules" diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-retry-timeout.md b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-retry-timeout.md index 2c0d6ae..7c778ae 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-retry-timeout.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/provider/provider-retry-timeout.md @@ -2,7 +2,7 @@ title: Configure Retry Policies and Timeouts Per Data Provider impact: MEDIUM impactDescription: Prevents cascading failures and handles transient backend errors -tags: retry, timeout, resolveTimeout, retryCount, retryDelay, config, resilience +tags: retry, timeout, resolveTimeout, retryCount, retryDelay, revertOnFailure, config, resilience --- ## Configure Retry Policies and Timeouts Per Data Provider @@ -58,9 +58,12 @@ const commentDataProvider = { | Reactions | 5-10s | 2-3 | 1s | | Recordings | 10-20s | 3 | 2s | | Users | 5-10s | 3 | 1s | +| Activity | 30-60s | 3 | 2s | | Attachments (save) | 20-30s | 3 | 2-3s | | Attachments (delete) | 5-10s | 2 | 1s | +Activity feeds can fan out across many records, so prefer the longer end of the timeout range. Activity's `saveRetryConfig` also supports `revertOnFailure: true` to roll back the optimistic cache update when save retries are exhausted — see [[provider-activity]] for the full activity-specific surface. + **Config options available on ALL provider types:** ```typescript @@ -74,8 +77,10 @@ interface DataProviderConfig { } interface RetryConfig { - retryCount: number; // Max retry attempts - retryDelay: number; // Delay between retries (ms) + retryCount?: number; // Max retry attempts + retryDelay?: number; // Delay between retries (ms) + revertOnFailure?: boolean; // Activity `saveRetryConfig` only — revert the optimistic cache + // update when the save ultimately fails after all retries } ``` diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-rest-api-backend.md b/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-rest-api-backend.md index 8c7e5cb..ff7de89 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-rest-api-backend.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-rest-api-backend.md @@ -2,7 +2,7 @@ title: Use sdk.api.* for REST API Operations Without a Database impact: HIGH impactDescription: Using sdk.api.* eliminates the need for MongoDB/AWS setup when calling Velt APIs directly, reducing backend complexity significantly -tags: python, rest-api, sdk.api, organizations, documents, users, notifications +tags: python, rest-api, sdk.api, organizations, documents, users, notifications, workflow --- ## Use sdk.api.* for REST API Operations Without a Database @@ -64,11 +64,15 @@ result = sdk.api.documents.addDocuments( | GDPR | `sdk.api.gdpr` | | Workspace | `sdk.api.workspace` | | Token | `sdk.api.token` | +| Workflow | `sdk.api.workflow` | **Key points:** - Import request dataclasses from `velt_py.models.` (e.g., `velt_py.models.organization`, `velt_py.models.document`, `velt_py.models.user_api`). - All service methods use camelCase names matching the Velt Node SDK (e.g., `addOrganizations`, `getDocuments`, `deleteUsers`). +- Current docs expose 18 services. The Agents and Memory sections are intentionally hidden in commented MDX; do not document `sdk.api.agents` or `sdk.api.memory` until they are visible in the Python SDK reference. +- `sdk.api.documents.getDocumentsCount(GetDocumentsCountRequest(...))` returns document counts, optionally scoped to a folder or excluding folder documents. +- `sdk.api.workflow.*` covers Approval Engine workflow definitions, executions, lifecycle events, step cancellation/resolution, and reviewer/agent decisions through request dataclasses from `velt_py.models.workflow`. - Responses are dicts: `result['result']` on success, `result['error']` on failure. Always check for the `error` key before accessing `result`. - `sdk.api.*` and `sdk.selfHosting.*` are independent — you can use both in the same initialized SDK instance if you also provide `database` config. @@ -76,6 +80,8 @@ result = sdk.api.documents.addDocuments( - [ ] `VeltSDK.initialize` is called with at least `apiKey` and `authToken` (or env vars `VELT_API_KEY` / `VELT_AUTH_TOKEN`) - [ ] Request dataclasses are imported from `velt_py.models.`, not constructed as raw dicts - [ ] Service method names are camelCase (e.g., `addOrganizations`, not `add_organizations`) +- [ ] Approval workflow calls use `sdk.api.workflow.*` and request classes from `velt_py.models.workflow` +- [ ] Hidden/commented Agents and Memory SDK sections are not treated as available Python APIs - [ ] Response `error` key is checked before accessing `result` **Source Pointers:** diff --git a/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-users-reactions.md b/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-users-reactions.md index c5ec6ce..cf1de78 100644 --- a/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-users-reactions.md +++ b/skills/velt-self-hosting-data-best-practices/rules/shared/python-sdk/python-users-reactions.md @@ -123,3 +123,93 @@ from velt_py import ( - [ ] Error handling uses `response['error']` and `response['errorCode']` **Source Pointer:** `https://docs.velt.dev/api-reference/sdk/python/users` (## Python SDK > ### Users & Reactions) + +--- + +## PartialReactionAnnotation Model (v0.1.12) + +`PartialReactionAnnotation` is the Python dataclass that resolvers emit when handing a reaction-annotation payload to your DB. Import it from `velt_py.models.reaction`. Starting in **v0.1.12**, the reaction-author field is `from_` (wire key `from`), replacing the former `user` field — aligning with the velt-sdk contract and `PartialCommentAnnotation`. + +```python +from velt_py.models.reaction import PartialReactionAnnotation +from velt_py.models.user import PartialUser + +@dataclass +class PartialReactionAnnotation: + annotationId: str + metadata: Optional[BaseMetadata] = None + icon: Optional[str] = None + from_: Optional[PartialUser] = None # 'from' on the wire; from_ avoids the Python keyword. Replaces the former 'user' field. + extra_fields: Optional[Dict[str, Any]] = None # Catch-all for customer-configured custom keys. +``` + +**Field notes:** + +- `from_` — Python alias for the JSON key `from` (reserved keyword); serialized as `from`. Replaces the former `user` field (renamed in v0.1.12 to match the velt-sdk contract and the comment models). +- `icon` — the emoji code carried in the partial payload (e.g. `'+1'`). +- `extra_fields` — catch-all because the frontend contract includes `[key: string]: any` for customer-configured custom keys. + +--- + +## v0.1.12 Rename: `user` → `from_` (Wire: `from`) on PartialReactionAnnotation + +In v0.1.12 the reaction-author field on `PartialReactionAnnotation` was renamed from `user` to `from_`. The wire key on the serialized document is now `from` (matching `PartialCommentAnnotation`). Construction with `user=` no longer works — call sites must pass `from_=`. Reads remain backward-compatible: `from_dict()` accepts either the canonical `from` key or the legacy `user` key (`from` wins when both are present) and populates `from_`. `to_dict()` always emits `from`. No data migration is required for reaction documents already stored under `user`. + +**Incorrect (v0.1.11-style construction; breaks on v0.1.12):** + +```python +from velt_py.models.reaction import PartialReactionAnnotation +from velt_py.models.user import PartialUser + +# `user=` is no longer a valid constructor kwarg in v0.1.12 — raises TypeError +ann = PartialReactionAnnotation( + annotationId='r-1', + icon='+1', + user=PartialUser(userId='u-1'), +) +ann.to_dict() # would have emitted {'user': {...}} pre-v0.1.12 +``` + +**Correct (v0.1.12 — use `from_=`, serializes as `from`):** + +```python +from velt_py.models.reaction import PartialReactionAnnotation +from velt_py.models.user import PartialUser + +ann = PartialReactionAnnotation( + annotationId='r-1', + icon='+1', + from_=PartialUser(userId='u-1'), +) +ann.to_dict()['from'] # {'userId': 'u-1'} — serialized as `from` +``` + +**Correct (backward-compatible reads — legacy `user` documents still resolve):** + +```python +# Legacy document stored under the old `user` key still resolves: +ann = PartialReactionAnnotation.from_dict({ + 'annotationId': 'r-1', + 'icon': '+1', + 'user': {'userId': 'u-legacy'}, +}) +ann.from_.userId # 'u-legacy' +ann.to_dict()['from'] # {'userId': 'u-legacy'} (re-serialized as `from`) +``` + +**Key points:** + +- Construction: only `from_=` works on v0.1.12. `user=` raises `TypeError`. +- Serialization (`to_dict()`): always emits `from`. Existing readers that key off `user` must be updated when they ingest newly-written documents. +- Deserialization (`from_dict()`): accepts both `from` and `user`. `from` wins when both are present. This is the back-compat hatch for documents already in your DB — no migration required. +- Attribute access: the field is exposed in Python as `from_` (with the trailing underscore), because `from` is a Python keyword. +- The rename aligns `PartialReactionAnnotation` with `PartialCommentAnnotation`, where the author field has long been `from_` / wire `from`. + +**Verification:** +- [ ] All `PartialReactionAnnotation(...)` constructors use `from_=`, not `user=` +- [ ] Any consumer that reads `ann.user` is updated to read `ann.from_` +- [ ] Any consumer that reads `to_dict()['user']` is updated to read `to_dict()['from']` +- [ ] `from_dict()` paths are left as-is — they already accept the legacy `user` key +- [ ] `velt-py` is pinned to `>= 0.1.12` + +**Source Pointer:** `https://docs.velt.dev/backend-sdks/python` (### `PartialReactionAnnotation`) diff --git a/skills/velt-setup-best-practices/AGENTS.full.md b/skills/velt-setup-best-practices/AGENTS.full.md index d937f00..a27c4d5 100644 --- a/skills/velt-setup-best-practices/AGENTS.full.md +++ b/skills/velt-setup-best-practices/AGENTS.full.md @@ -1,6 +1,6 @@ # Velt Setup Best Practices -**Version 1.0.0** +**Version 1.0.1** Velt January 2026 @@ -36,9 +36,10 @@ Comprehensive setup guide for integrating Velt collaboration SDK into web applic - 3.4 [Structure User Object with Required Fields](#34-structure-user-object-with-required-fields) 4. [Document Identity](#4-document-identity) — **CRITICAL** - - 4.1 [Attach Metadata to Documents](#41-attach-metadata-to-documents) - - 4.2 [Generate and Manage Document IDs](#42-generate-and-manage-document-ids) - - 4.3 [Initialize Documents with setDocuments API](#43-initialize-documents-with-setdocuments-api) + - 4.1 [Attach Custom Page Info to Newly Created Data](#41-attach-custom-page-info-to-newly-created-data) + - 4.2 [Attach Metadata to Documents](#42-attach-metadata-to-documents) + - 4.3 [Generate and Manage Document IDs](#43-generate-and-manage-document-ids) + - 4.4 [Initialize Documents with setDocuments API](#44-initialize-documents-with-setdocuments-api) 5. [Config](#5-config) — **HIGH** - 5.1 [Call enableFirestorePersistentCache Before Authentication to Enable Offline and Multi-Tab Sync](#51-call-enablefirestorepersistentcache-before-authentication-to-enable-offline-and-multi-tab-sync) @@ -1148,7 +1149,50 @@ await client.setVeltAuthProvider({ Document initialization with setDocuments API. Documents define collaborative spaces where users can interact. SDK will not function without calling setDocument. -### 4.1 Attach Metadata to Documents +### 4.1 Attach Custom Page Info to Newly Created Data + +**Impact: MEDIUM (Apps with client-side routing or custom URL schemes otherwise record browser-derived page info on comments, reactions, recordings, presence, and cursors)** + +By default Velt derives page info (URL, title, path) from the browser and stamps it onto newly created data — comments, reactions, recordings, presence, and cursors. In apps with client-side routing or custom URL schemes the browser URL may not be the identity you want recorded. `setPageInfo()` opts into supplying your own `PageInfo`; it affects **only newly created data** (existing records are untouched). `clearPageInfo()` reverts to the automatic browser-derived behavior. + +**Incorrect (relying on browser-derived URL in a client-side-routed app — created data records the wrong page):** + +```jsx +// SPA route is /doc/42 but the browser URL/title may lag or use a hash scheme; +// new comments/reactions get stamped with whatever the browser reports. + +``` + +**Correct (stamp your own page info via the hook or the client API):** + +```jsx +import { useSetPageInfo, useClearPageInfo } from '@veltdev/react'; + +const { setPageInfo } = useSetPageInfo(); +setPageInfo({ url: 'https://app.example.com/doc/42', title: 'Design Doc' }); + +// Or via the client API +client.setPageInfo({ url: 'https://app.example.com/doc/42', title: 'Design Doc' }); + +// Revert to automatic browser-derived page info +const { clearPageInfo } = useClearPageInfo(); +clearPageInfo(); +``` + +**For HTML/Vanilla JS:** + +```js +Velt.setPageInfo({ url: 'https://app.example.com/doc/42', title: 'Design Doc' }); + +// Revert to automatic browser-derived page info +Velt.clearPageInfo(); +``` + +The SDK signature also accepts `options?.documentId` on `setPageInfo()` / `clearPageInfo()`, but the docs mark it as reserved for a future per-document scope. Do not rely on per-document page-info behavior yet; treat custom page info as global until that scope ships. + +--- + +### 4.2 Attach Metadata to Documents **Impact: MEDIUM-HIGH (Metadata enables document names in UI and custom filtering)** @@ -1258,7 +1302,7 @@ async function loadDocument(docId: string) { --- -### 4.2 Generate and Manage Document IDs +### 4.3 Generate and Manage Document IDs **Impact: CRITICAL (Document ID determines which users see the same collaborative content)** @@ -1402,7 +1446,7 @@ function Dashboard() { --- -### 4.3 Initialize Documents with setDocuments API +### 4.4 Initialize Documents with setDocuments API **Impact: CRITICAL (SDK will not function without calling setDocuments)** diff --git a/skills/velt-setup-best-practices/AGENTS.md b/skills/velt-setup-best-practices/AGENTS.md index ba63e56..8284aad 100644 --- a/skills/velt-setup-best-practices/AGENTS.md +++ b/skills/velt-setup-best-practices/AGENTS.md @@ -1,5 +1,5 @@ # Velt Setup Best Practices -|v1.0.0|Velt|January 2026 +|v1.0.1|Velt|January 2026 |IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any Velt tasks. |root: ./rules @@ -16,7 +16,7 @@ |shared/identity:{identity-jwt-generation.md,identity-organization-id.md,identity-user-object-shape.md} ## 4. Document Identity — CRITICAL -|shared/document-identity:{document-metadata.md,document-id-generation.md,document-set-document.md} +|shared/document-identity:{document-page-info.md,document-metadata.md,document-id-generation.md,document-set-document.md} ## 5. Config — HIGH |shared/config:{config-firestore-persistent-cache.md,config-api-key.md,config-proxy-config.md,config-auth-token-security.md,config-domain-safelist.md} diff --git a/skills/velt-setup-best-practices/metadata.json b/skills/velt-setup-best-practices/metadata.json index d96d5e5..16578ea 100644 --- a/skills/velt-setup-best-practices/metadata.json +++ b/skills/velt-setup-best-practices/metadata.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.0.1", "organization": "Velt", "date": "January 2026", "abstract": "Comprehensive setup guide for integrating Velt collaboration SDK into web applications. Covers installation, VeltProvider configuration, user authentication (userId, organizationId, JWT tokens), document initialization (documentId, setDocuments), project folder structure, and debugging patterns. Primary focus on React and Next.js with secondary coverage of Angular, Vue.js, and vanilla HTML. All guidance is evidence-backed from official Velt documentation and sample applications.", diff --git a/skills/velt-setup-best-practices/rules/shared/document-identity/document-page-info.md b/skills/velt-setup-best-practices/rules/shared/document-identity/document-page-info.md new file mode 100644 index 0000000..a7a3863 --- /dev/null +++ b/skills/velt-setup-best-practices/rules/shared/document-identity/document-page-info.md @@ -0,0 +1,61 @@ +--- +title: Attach Custom Page Info to Newly Created Data +impact: MEDIUM +impactDescription: Apps with client-side routing or custom URL schemes otherwise record browser-derived page info on comments, reactions, recordings, presence, and cursors +tags: setPageInfo, clearPageInfo, useSetPageInfo, useClearPageInfo, pageInfo, page-info, client-side-routing +--- + +## Attach Custom Page Info to Newly Created Data + +By default Velt derives page info (URL, title, path) from the browser and stamps it onto newly created data — comments, reactions, recordings, presence, and cursors. In apps with client-side routing or custom URL schemes the browser URL may not be the identity you want recorded. `setPageInfo()` opts into supplying your own `PageInfo`; it affects **only newly created data** (existing records are untouched). `clearPageInfo()` reverts to the automatic browser-derived behavior. + +**Params:** +- `pageInfo`: `PageInfo` (see [data-models#pageinfo](https://docs.velt.dev/api-reference/sdk/models/data-models#pageinfo)) +- `options?`: `{ documentId?: string }` on the SDK signature. `documentId` is reserved for a future per-document scope; the current release applies custom page info globally. + +**For React / Next.js:** + +**Incorrect (relying on browser-derived URL in a client-side-routed app — created data records the wrong page):** + +```jsx +// SPA route is /doc/42 but the browser URL/title may lag or use a hash scheme; +// new comments/reactions get stamped with whatever the browser reports. + +``` + +**Correct (stamp your own page info via the hook or the client API):** + +```jsx +import { useSetPageInfo, useClearPageInfo } from '@veltdev/react'; + +const { setPageInfo } = useSetPageInfo(); +setPageInfo({ url: 'https://app.example.com/doc/42', title: 'Design Doc' }); + +// Or via the client API +client.setPageInfo({ url: 'https://app.example.com/doc/42', title: 'Design Doc' }); + +// Revert to automatic browser-derived page info +const { clearPageInfo } = useClearPageInfo(); +clearPageInfo(); +``` + +**For HTML/Vanilla JS:** + +```js +Velt.setPageInfo({ url: 'https://app.example.com/doc/42', title: 'Design Doc' }); + +// Revert to automatic browser-derived page info +Velt.clearPageInfo(); +``` + +The SDK signature also accepts `options?.documentId` on `setPageInfo()` / `clearPageInfo()`, but the docs mark it as reserved for a future per-document scope. Do not rely on per-document page-info behavior yet; treat custom page info as global until that scope ships. + +**Verification:** +- [ ] `setPageInfo` is called only when you need to override the browser-derived page info (it is opt-in) +- [ ] Callers understand only newly created data is affected — existing comments/reactions/recordings keep their original page info +- [ ] React uses `useSetPageInfo()` / `useClearPageInfo()` (or `client.setPageInfo` / `client.clearPageInfo`); other frameworks use `Velt.setPageInfo` / `Velt.clearPageInfo` +- [ ] `clearPageInfo()` is used to return to automatic behavior rather than passing stale values +- [ ] `options.documentId` is not used as a live per-document scope; it is reserved for future support + +**Source Pointers:** +- https://docs.velt.dev/get-started/advanced — "Set Custom Page Info" / "Clear Custom Page Info"