Write a renderer plugin
View-kind renderers are themselves tamias drivers — there is no separate plugin ecosystem. A driver whose manifest carries renderers[] claims ViewNode kinds on one or more surfaces and answers the render wire op with a canonical RenderModel.
Use this when a built-in interpretation isn't good enough — examples/drivers/viz-graph/ claims graph and renders SVG, ascii edge lists, and mermaid-free layouts the built-ins don't.
1. Declare the claim in the manifest
"renderers": [{
"id": "graph",
"kinds": ["graph"],
"priority": 50,
"entry": "graph",
"surfaces": {
"cli": { "formats": ["svg", "ascii"] },
"tui": true,
"web": { "component": "viz-graph", "island": true }
}
}]kinds— ViewNodekindstrings, or["*"]for a catch-all. Explicit kinds beat"*"; lowerprioritywins conflicts (default 100); ties break by driver name then renderer id — the same policy chrome contributions use.surfaces.cli.formats— the--formatvalues you claim; empty = wildcard (you handle every format asked, includingtext). Built-in names are claimable: listingdot,mermaid, ortextoverrides the built-in renderer for your claimed nodes — unclaimed nodes keep the built-in output.jsonis never claimable (the rawViewResultdump is host-owned).surfaces.tui—trueto render on the ratatui host.surfaces.web—componentis a hint for rich mounts;island: truemarks output needing client-side execution.
A driver may declare renderers with zero views — a pure plugin ships no routes of its own.
2. Answer the render op
For every node whose kind is claimed on the asking surface, the host sends:
{"op":"render","kind":"graph","node":{…ViewNode verbatim…},
"data":{…view data map…},"surface":"cli|tui|web","format":"…"?}nodeis the raw ViewNode JSON — unknown/future fields reach you untouched.datais the view's query-binding map verbatim — resolvenode.queryagainst it yourself.surfaceis"cli","tui", or"web"(open string — hosts may add surfaces).formatis the surface's format hint ("svg","ascii", …) when one applies.
Respond with a RenderModel ({"ok":true,"result":{…}}):
| variant | shape | CLI | TUI | web |
|---|---|---|---|---|
rows | {type,headers,aligns?,rows} | aligned table | table rows | <table> |
svg | {type,markup} — self-contained <svg> | raw markup | [svg] hint | v-html island |
text | {type,lines} | lines | lines | <pre> |
placeholder | {type,component,props?} | [component] | box + detail | named component |
Rules:
- Built-ins are not reserved — claim
graph(or any kind) and you override built-in rendering on the surfaces you declare. - Fallback is mandatory — when no plugin claims a kind+surface, or
rendererrors/times out, hosts use the built-in interpretation. Plugin failure must never break a view. - No HTML in RenderModel — self-contained markup is
svgonly. - Unknown
typevalues degrade likeplaceholderon every surface — keep to the four variants.
3. Test it
export PATH="$PWD/examples/drivers/viz-graph:$PATH"
pavo drivers # viz-graph … renderers:1
pavo view demo:deps --format svg # plugin SVG (claims svg+ascii)
pavo view demo:deps --format mermaid # built-in — plugin doesn't claim it
pavo view demo:deps --format ascii # plugin edge list/api/renderers on pavo serve lists every claim; /api/render proxies a render op for the web surface's plugin path. pavo-testkit conformance covers drivers that answer render — see check_driver.
Reference
examples/drivers/viz-graph/tamias-driver-viz-graph is the whole implementation (~200 lines of Python): manifest claim, render dispatch on kind/surface/format, three RenderModel shapes. Copy it.