Skip to main content

Overview

  • Modular SDK: The SDK now ships as an eager velt.js core (bootstrap + facade) plus 19 lazy-loaded feature chunks (comment, cursor, presence, huddle, recorder, notification, reaction, and more). Each feature is fetched on demand instead of bundled into one eager payload.
  • Modular SDK backward compatibility: The public Velt / Snippyly browser API is unchanged. Existing browser integrations work as-is unless they also author Approval Engine definitions with the legacy rejection contract.
  • New opt-in control APIs: Use featureAllowList at init to preload only the features you use, and preload<Feature>() to warm a chunk or load tag-only features ahead of time.

Breaking Changes

  • [Modular SDK] None. The public Velt / Snippyly API is unchanged. If you do not pass featureAllowList, the SDK preserves pre-modular behavior and preloads all chunks in the background.
  • [Approval Engine — unified edges[] contract] The Approval Engine removed the per-human-node onReject shorthand and the authored top-level loops[] array. All workflow transitions — approve routing, reject routing, group fan-out/in, and loop-backs — are now expressed as a single unified edges[] array. Human nodes no longer carry any rejection config; a human node’s reject path is an outgoing on:"reject" edge.
    • Who is affected: any definition that POSTs HumanNodeConfig.onReject (routeToNodeId or loopBack) or a top-level loops[] array. These are now rejected at write time with INVALID_ARGUMENT.
    • Who is not affected: already-stored definitions and in-flight executions. The runtime still consumes the compiled forward edges and derived loops unchanged.
    • Why: the prior model had two ways to express rejection that desugared at write time into a form callers never saw, breaking round-trip symmetry. Under the unified model, POST.edges === GET.edges.

How to Upgrade

Approval Engine: migrate to edges[]

Rewrite every onReject field and every loops[] entry as one or more edges[] entries before writing new definitions.
Old formNew edge equivalent
"onReject": { "routeToNodeId": "rework-notice" }{ "from": "human-review", "to": "rework-notice", "on": "reject" }
"onReject": { "loopBack": { "toNodeId": "agent-draft", "maxIterations": 3 } }{ "from": "human-review", "to": "agent-draft", "on": "reject", "loop": { "maxIterations": 3 } }
"loops": [{ "loopId": "...", "entryNodeId": "agent-draft", "bodyNodeIds": [...], "maxIterations": 3 }]{ "from": "human-review", "to": "agent-draft", "on": "reject", "loop": { "maxIterations": 3 } }
onReject.loopBack.onExhausted.routeToNodeIdsibling { "from": "human-review", "to": "escalate", "on": "exhausted" } edge
New EdgeSchema fields:
FieldTypeRequiredNotes
fromEdgeEndpointyesBare node-id string, or { kind: "node", nodeId }, or { kind: "group", groupId }.
toEdgeEndpointyesSame shape as from.
on"approve" | "reject" | "always" | "exhausted" | "custom"no (default "always")Semantic role. approve / reject auto-compile their predicates.
whenJSON-AST stringonly for on:"custom"Custom predicate. Invalid on any other edge.
loop{ maxIterations: 1–20 }only on an on:"reject" back-edgeMarks the reject edge as a loop-back; the server derives the loop region.
New INVALID_ARGUMENT compile error keys. A definition that violates the unified contract is rejected with one of these keys in the error message:
Error keyCause
APPROVAL_EDGE_CUSTOM_REQUIRES_WHENon:"custom" without a non-empty when.
APPROVAL_EDGE_WHEN_ONLY_FOR_CUSTOMwhen supplied on a non-custom edge.
APPROVAL_EDGE_LOOP_REQUIRES_REJECTloop on a non-reject edge.
APPROVAL_EDGE_LOOP_TARGET_NOT_ANCESTORreject+loop whose to is not an ancestor of from.
APPROVAL_EDGE_REJECT_CYCLE_REQUIRES_LOOPon:"reject" to an ancestor without loop (unmarked cycle).
APPROVAL_EDGE_EXHAUSTED_REQUIRES_LOOP_SIBLINGon:"exhausted" with no sibling reject+loop from the same from.
APPROVAL_HUMAN_NODE_REQUIRES_REJECT_PATHHuman node with no outgoing on:"reject" edge.
APPROVAL_EDGE_GROUP_TO_GROUP_FORBIDDENBoth endpoints are group containers.
APPROVAL_GROUP_FROM_REJECT_REQUIRES_LOOPForward on:"reject" from a joinOnQuorum / cancelOnQuorum group (dead edge — those policies fan out only on approval-quorum).
APPROVAL_GROUP_FROM_LOOP_REQUIRES_JOINONQUORUMreject+loop back-edge from a non-joinOnQuorum group.
For the full edge model, group-as-edge-source semantics, and the read-only compiled block, see Customize Behavior and Create Definition.

How to Upgrade the Modular SDK

Adopting the modular SDK is optional. Update the package and your existing code keeps working. To opt into on-demand loading, use the following control APIs.

1. Preload only the features you use

Pass featureAllowList at init to preload only the listed chunks. Omit it to keep the default (all chunks preloaded in the background). Valid modular feature keys are: 'comment', 'cursor', 'presence', 'huddle', 'recorder', 'notification', 'reaction', 'arrow', 'tag', 'rewriter', 'selection', 'area', 'activity', 'views', 'userInvite', 'userRequest', 'videoPlayer', 'crdt', and 'liveStateSync'.
<VeltProvider apiKey={apiKey} initOptions={{ featureAllowList: ['comment', 'presence'] }}>
  {children}
</VeltProvider>

2. Warm a chunk or load tag-only features with preload<Feature>()

Call a preloader to fetch a chunk ahead of use. This is also how you load tag-only features (userInvite, userRequest, videoPlayer) that have no element accessor. Preloaders are idempotent and non-throwing.
client.preloadComment();
client.preloadUserInvite();

3. getXElement() accessors auto-load their chunk

Every getXElement() accessor automatically loads its chunk and auto-extends the allow-list (ensureFeatureEnabled), so calling an accessor for a feature you didn’t list still works — you’re never locked out.
// Auto-loads the comment chunk and enables the feature, even if not in featureAllowList
const commentElement = client.getCommentElement();
Tags placed before their chunk loads render inert and upgrade in place once the chunk lands — no remount or re-render required.