Velloo documentation
Markdown for LLMs ↗MCP: snippets
add_snippet, update_snippet, remove_snippet, and update_snippet_instance, plus typed params, $param and $if control forms, placing snippets by tag with compose, per-instance overrides, and body editing.
Snippets are named reusable subtrees with typed parameters. A snippet lives in snippets/<id>.json; a screen holds a $snippet instance node that passes args for the declared params. Agents place instances by the snippet’s PascalCase tag in compose JSX. Concepts: Snippets.
Params and control forms
params is an array of { name, type, default?, optional?, enum?, min?, max?, step?, description? }. A param with no default and no optional: true is required; list_components (full mode) and get_snippet surface this as a derived required flag. An optional param may be omitted with no default: it resolves to nothing, so an omitted node slot renders and emits nothing and an omitted prop is left off.
| Type | Meaning |
|---|---|
string | Text input |
number | Numeric input; honors min / max / step in the inspector |
boolean | Checkbox; pair with $if for branching |
icon | Lucide icon picker (string-valued). emit_code bakes one glyph into the component, so an icon that varies per instance belongs in a node param |
color | Color swatch (string-valued) |
enum | Select from the enum array, which must be non-empty |
node | Subtree slot: one node, or an array rendered as siblings, passed by the instance; useful for icon blocks, overlays, slot composition |
A default must match the declared type (and, for enum, be one of the values); a mismatch is rejected when the snippet is written.
Inside the body, two control forms consume params:
$paramplaceholder:{ "$param": "name" }substitutes its argument value at render time. Placement depends on the param type: anodeparam fills a child slot (put the placeholder directly in achildrenarray); a scalar param fills a prop value (e.g.{ "$ref": "Heading", "props": { "children": { "$param": "title" } } }). A scalar$paramplaced directly in achildrenarray renders as nothing; that’s an authoring error.$ifbranch:{ "$if": "name", "then": …, "else": … }picks a branch by the truthiness of the named arg; add"eq": valueto branch on strict equality instead. Declare truthiness params asboolean; an empty string is falsy and the string"false"is truthy.
add_snippet
Create a reusable subtree.
| Arg | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
id | string | no | Default: derived from name |
params | array | no | Typed param declarations; default [] |
tree | node | yes | Body; may contain $param placeholders and $if branches |
Returns { snippetId, snippet }, plus propWarnings and diagnostics when there is something to fix. An id already in use returns SnippetIdConflict. A body that references itself, directly or transitively, returns SnippetCycle.
Always call render_snippet after add_snippet: $param wiring bugs and $if truthy-coercion mistakes are silent at definition time and only surface at instantiation.
update_snippet
Update a snippet’s metadata or body. Sparse patch: pass only what changes. The id stays stable, and every screen using the snippet re-renders.
| Arg | Type | Required | Description |
|---|---|---|---|
snippetId | string | yes | |
patch | object | yes | { name?, params?, tree?, innerPatch? } |
innerPatch is { innerPath, propPatch }: patch one body node’s props in place without resending the whole tree. innerPath is "@id" (preferred), a dotted index path such as "0.2", or "" for the body root; propPatch keys merge and null removes a key. A path that doesn’t resolve to a component in the body returns InvalidPath.
It is the definition-level member of the prop-patch trio:
| Operation | Scope |
|---|---|
update_props | A plain screen node |
update_snippet_instance | One instance’s args, extra classes, or body node |
update_snippet with innerPatch | The shared definition: every instance at once |
Pass tree only for a full body replacement; a replacement that would create a cycle returns SnippetCycle. Returns { snippet }. When params changes, propWarnings name any existing instance the change strands (a now-missing required arg or a now-unknown one) so it can be fixed with update_snippet_instance.
remove_snippet
Delete a snippet.
| Arg | Type | Required | Description |
|---|---|---|---|
snippetId | string | yes |
Returns { removedId }. Refuses with SnippetInUse while anything still instantiates it. The error payload splits the referencers so the agent can clean up first: screenIds lists screens holding an instance (clear them with remove_node), and snippetIds lists other snippets whose body embeds this one (clear them with update_snippet).
Placing a snippet
There is no separate instantiate operation. Place an instance with compose using the tag list_components reports for the snippet, its id or name in PascalCase (nav-item becomes <NavItem />):
{
"screenId": "dashboard",
"mode": "append",
"parentPath": "@sidebar",
"jsx": "<NavItem vellooId=\"nav-settings\" label=\"Settings\" icon=\"Settings\" className=\"mt-auto\" />"
}
- Attributes are args. Each must name a declared param, and every required param must be present; otherwise the call returns
BadRequestwith the offending line and column. className, unless the snippet declares aclassNameparam, becomes the instance’s extra classes, appended to the body root.- Text children, or one element child for a
node-typed param, fill a param namedchildren. vellooIdgives the instance a stable$id.
The instance is opaque from outside; its interior is not addressable with screen locators. Per-instance interior overrides are applied after placement with update_snippet_instance, which can stamp a shared snippet on many screens with a different override on each.
update_snippet_instance
Edit one instance without touching the shared definition. The three edits compose in one call.
| Arg | Type | Required | Description |
|---|---|---|---|
screenId | string | yes | Screen id, or snippet:<id> for an instance nested in a snippet body |
path | locator | yes | The instance |
argPatch | object | no | Merges into the instance’s args; null removes a key |
extraClassName | string or null | no | Replaces the instance’s extra classes; null or an empty string clears |
innerPath | string | with propPatch | Body node: "@id" (preferred; survives body restructures), a dotted index path ("0.2" = third child of first child), or "" for the body root |
propPatch | object | with innerPath | Merges into the instance’s $overrides for that body node; null removes a key, and an empty result clears the override |
Pass at least one of argPatch, extraClassName, or innerPath + propPatch; innerPath and propPatch must come together. Violations return BadRequest.
{
"screenId": "dashboard",
"path": "@nav-settings",
"argPatch": { "label": "Preferences" },
"innerPath": "@nav-link",
"propPatch": { "className": "bg-accent" }
}
Returns { path, applied }, where applied lists which sides ran ("args", "override"), plus diagnostics when there is something to fix. A locator that isn’t a snippet instance, or an innerPath that doesn’t resolve to a component in the snippet body, returns InvalidPath.
Overrides persist on the instance and are applied after param substitution at render time. emit_code inlines instances that carry overrides instead of emitting the shared component.
Previewing
render_snippet renders a snippet in isolation (no host screen) and returns a PNG; full reference on the visual page.
Editing a snippet body
update_props, move_node, remove_node, set_node_id, update_snippet_instance, and find_nodes accept screenId: "snippet:<snippetId>" and act on the snippet’s body with the same locator semantics and error model; the change persists to the snippet and every screen using it re-renders.
compose does not accept snippet: ids. To add structure to a body (including $param and $if nodes, or another snippet nested inside), send a new body with update_snippet’s tree. Full treatment in the MCP overview.