Register your components
One object, keyed by component name, under one name at both ends:createVendo({ components }) on the server and <VendoProvider components> in the browser take the same object.
vendo.ts
descriptionis selection guidance. Say what the component shows and when to reach for it.propsvalidates at render and derives the model-facing JSON Schema. One schema, never two.examplesare JSON prop objects the model copies from, and they draw the console’s preview.
What a screen may name
A name outside these two sets fails the save. It never renders as an empty box.
There is no value-formatting tier: format a figure with
Intl in the screen’s own code and hand the result to a <Text>. There is no Skeleton either, and DataTable is the only table. It sorts, filters, searches, paginates, resolves dot-path keys, and formats each cell.
What vendo sync captures
Two artifacts, both deterministic and byte-stable. Commit them.
catalog.json is what your components are called. .vendo/components/ is what they are.
catalog.json is what your components are called: names, prop schemas, descriptions, examples. A re-scan keeps description and example copy you edited by hand.
components/ is what they are. Source is stored once per module and referenced by content hash, so a format-currency.ts that ten components import is stored once. The .vendo directory has every path.
Where the walk stops
The import walk follows your own code to closure with no depth limit, and stops at exactly two lines: anything resolving intonode_modules, and 256 KB of source per component across the whole closure.
Three packages are bundled into the sandbox itself, because they blocked almost everything and are tiny.
The bundled
zod is a shim, not zod. It resolves the declaration surface so your module loads, which is all a registry’s props: schema needs.It does not validate. .parse() and .safeParse() throw a named ZodShimError rather than returning a plausible wrong value.When a component cannot be captured
Sync never goes quiet. The record on disk carries a machine-readableskipped.reason plus a sentence you can read.
A component that could not be read at all this run is a different case. Sync leaves the previous capture exactly as it was and moves on.
How a preview draws
A preview has no data plane, so every query resolves to an empty list. Sync fills it from what you have already written, down three rungs.1
Your examples
The first
examples string that parses to a JSON object wins. A human’s example is real product data, and it reads better than anything Vendo could invent.2
Generated from your props schema
Values synthesized from the schema: typed correctly, respecting enums,
min, max, string formats, and optionality. Plausible rather than pretty.3
An honest label
With neither, the capture records
noSampleProps with a reason. The component still renders in your product, and previews as a labeled placeholder..vendo/components/ never churns. Sample props travel with the capture, so keep them realistic and non-sensitive.
Sending the corpus to Cloud
Sync asks once per project whether your component source may go to Vendo Cloud, so the console can render your real components instead of grey placeholders. It commits your answer..vendo/cloud.json
In CI, pass
--push-components or --no-push-components. The CLI reference has every sync flag.