Skip to content

Driver manifest ​

What tamias-driver-<type> manifest (or {"op":"manifest"} on the serve wire) answers β€” the discovery contract. Pavo reads it without spawning serve, so routes and chrome contributions are listable even when the product isn't running.

Rust source of truth: crates/pavo-protocol/src/manifest.rs; protocol/v1/driver-manifest.schema.json mirrors it.

json
{
  "name": "myco",
  "title": "Myco",
  "icon": "πŸ—‚οΈ",
  "version": "0.4.0",
  "verbs": ["manifest", "serve"],
  "capabilities": ["pavo"],
  "contributes": { "navbar": [...], "routes": [...],
                   "sidebar": [...], "jobs": [...] },
  "deps": { "requires": ["config"], "optional": ["myco"] },
  "views": [ { "id": "board", "route": "/myco/board", "root": {…} } ],
  "renderers": [ { "id": "graph", "kinds": ["graph"], "surfaces": {…} } ]
}

Fields ​

fieldtypemeaning
namestringthe <type> in tamias-driver-<type>; namespaces routes (/<name>/<view>)
titlestringhuman-facing title for chrome (navbar, window titles)
iconstring?emoji, icon name, or renderer-resolvable path
versionstringsemver
verbsstring[]wire verbs beyond tamias install verbs; pavo treats a driver as a view driver iff "serve" is listed (or capabilities contains "pavo")
capabilitiesstring[]free-form role tags ("pavo", "config", "chrome")
depsobject?driver dependency declarations β€” see below
contributesobjectchrome slots β€” see below
viewsViewSpec[]views the driver renders
renderersRendererDecl[]view-kind renderer plugins β€” may appear with zero views

deps β€” DriverDeps ​

json
{ "requires": ["config"], "optional": ["myco"] }

Names are driver names β€” the <type> in tamias-driver-<type> (also DriverManifest::name). Both kinds of entry mean the same thing for ordering: the dep sorts before the dependent (the host topologically sorts its driver list).

  • requires β€” drivers this driver cannot function without. A missing required dep is a hard error: the host quarantines the dependent β€” it stays discovered but every manifest/view/feed access surfaces the deps error rather than half-rendering.
  • optional β€” drivers used when present. Absent optional deps contribute no edge and never block discovery; features degrade.

Cycles (including a driver requiring itself) are host-side errors and quarantine every member. Drivers whose manifests arrive lazily are unordered until fetched; their requires are validated on first manifest access. Ordering is deterministic: among satisfied drivers, name ascending.

contributes ​

Named places in pavo's chrome where drivers register entries. The eight built-in slots:

slotlands in
navbartop navigation bar (web/TUI chrome)
dashboardthe shared dashboard route β€” composable panels from many drivers
statusbarbottom status bar β€” one-line summaries
routesstandalone routes the driver owns outside its views
sidebarleft sidebar shell β€” grouped nav entries (web host)
headerActionstop-bar action buttons β€” wire spelling is camelCase; icons that open a popover rendering the action's view, or plain navigation (web host)
notificationsmarks the driver as a notification provider β€” entries are display metadata; item data flows through the notifications wire op as NotificationItems
jobsmarks the driver as a job/queue feed provider β€” same pattern; data comes from the jobs wire op as JobItems

Unknown slots are preserved via contributes.extra (serde flatten) β€” newer drivers degrade gracefully on older hosts.

Contribution ​

json
{"id": "demo:board", "title": "Board", "route": "/demo/board",
 "priority": 100, "icon": "πŸ“‹"}

id is namespaced <driver>:<id> by convention so contributions never collide; route is host-relative; priority orders entries within a slot (lower wins, default 100, ties by discovery order).

views β€” ViewSpec ​

json
{"id": "board", "title": "Board", "route": "/demo/board",
 "description": "…",
 "queries": [{"name": "items", "op": "query",
              "params": {"kind": "items"}}],
 "root": { "kind": "panel", "children": [ …ViewNode… ] }}
  • id β€” unique within the driver; <driver>:<id> is the deep link.
  • route β€” the host-relative path the view answers.
  • queries β€” named data sources the tree references via NodeBase.query. Each QueryBinding has name (what nodes reference), op (the wire op the host calls β€” typically "query"), and static params merged under caller-supplied view params. Optional driver makes it a cross-driver binding: the host issues the op against that driver's serve instead of the view's own and stores the payload in data[name] β€” failures degrade to {"error": "…"} like local bindings. A driver equal to the view's own driver is treated as a local binding.
  • root β€” the declarative ViewNode tree.

renderers β€” RendererDecl ​

json
{"id": "graph", "kinds": ["graph"], "priority": 50,
 "entry": "graph",
 "surfaces": {
   "cli":  {"formats": ["svg", "ascii"]},
   "tui":  true,
   "web":  {"component": "viz-graph", "island": true}}}
fieldmeaning
idrenderer id β€” namespaced <driver>:<id> by convention
kindsnode kinds claimed β€” ViewNode kind strings or "*" catch-all; explicit beats "*", lower priority wins, ties by driver name then renderer id
surfacesper-surface support; absent surface = unsupported
surfaces.cli.formats--format values claimed; empty = wildcard (all formats incl. text)
surfaces.tuitrue renders plugin nodes on the ratatui host
surfaces.web.component / .islandcomponent hint; island: true = needs client-side execution
priorityordering hint, default 100
entryoptional entry-point hint (reserved; hosts currently always use the wire)

Claims are canonical per node on their declared surfaces β€” a plugin listing a built-in --format name (text/dot/mermaid) overrides it for claimed nodes (json stays host-owned); unclaimed kinds/surfaces keep built-in rendering, and plugin failure degrades to it. See Write a renderer plugin and Renderer plugins.