Skip to content

ViewNode IR ​

The declarative UI tree a driver emits and every pavo renderer (CLI, TUI, web) interprets. Wire shape: each node is a JSON object with a "kind" discriminator — internally tagged, no wrapper envelope. Rust source of truth: crates/pavo-protocol/src/view_node.rs; protocol/v1/view-node.schema.json mirrors it.

json
{ "kind": "table", "title": "Board", "query": "items",
  "columns": [{ "key": "id", "title": "ID" }],
  "rows": [["T-001", "pavo scaffold"]] }

Shared fields (NodeBase) ​

Every kind flattens these to the top level:

fieldtypemeaning
idstring?stable id — deep-link target, diffing, expansion state
titlestring?heading the renderer may show above the node
querystring?names a QueryBinding on the enclosing ViewSpec; kind-specific meaning (table→rows, kv→pairs, tree→items, graph→nodes/edges)
descriptionstring?longer help text (tooltips, help panes)
propsobjectrenderer hints (density, direction, collapsed, …); renderers ignore unknown keys

Kinds ​

kindpurposekey fields
paneltitled container grouping childrenchildren[]
rowhorizontal layoutchildren[], gap?
columnvertical layoutchildren[], gap?
tabletabular datacolumns[] (key,title,align?,width?), rows? (inline cell arrays win over query data), sortable?
treehierarchical itemsitems[] (id?,label,detail?,icon?,children[],props), expandable?
graphnodes + edges; layout is the renderer's problemnodes[] (id,label,kind?,props), edges[] (from,to,label?), layout? ("dot","mermaid","layered",…)
forminput fields + submit op — interactive via the submit wire opfields[], submit_op?, submit_label?
kvkey/value pairs — detail panes, metadata, universal degradation targetpairs[] (key,value,kind?)
markdownfree-form markdowntext
embedescape hatch for rich rendererscomponent (registry/web-component name); component props ride in props

Field details ​

  • table.columns[].key — for query-fed data, rows arrive as objects keyed by key; inline rows are positional arrays of arbitrary JSON cells (renderers stringify).
  • table.rows — inline rows ship static previews; when both inline rows and query data exist, inline wins.
  • tree.items — recursive; id is required for interactive trees, optional for static previews. Omitted items (vs []) matters on the typed round-trip so an empty inline list can't shadow query-bound data.
  • graph — the IR floor covers structure only. Real layout is an embed/renderer-plugin concern; layout is a hint renderers may ignore.
  • form.fields[].kind — text (default), textarea, number, bool, select (with options). required, default, value, hint, editable (default true; false renders read-only and is never submitted), and placeholder are supported. Submission goes through the submit op; the form node's id is the form param when a view has several forms, and submit_op names the driver-side action ("set", "update", "add", …).
  • embed — for visuals no built-in model covers (live graphs, timelines, WASM). component resolves against the host's renderer registry and, on web, registered components.

Degradation is mandatory ​

Renderers that meet an unknown kind must degrade to generic key/value rendering rather than failing — forward compatibility is a host obligation, and the protocol is additive-only within v1. The same applies to props keys and enum values.