Visualize vault data
A read-only widget runs named queries and renders the rows. Start with a built-in kind; write a custom page when you need your own layout or charts. This page assumes the setup from Build a widget.
Built-in kinds
Set kind and a matching config; the runtime renders into <div id="app"> and handles the empty and not-connected states. Your entry HTML is only the mount point.
| Kind | Renders | config |
|---|---|---|
list |
One query as a list | { kind: "list", query, item: { title, subtitle?, meta? }, empty?, limit? } |
record |
One row as labelled fields | { kind: "record", query, fields: [{ label, column, format? }], empty? } |
dashboard |
Several panels | { kind: "dashboard", panels: [{ title, query, render, value?, label?, columns?, item? }] } |
render is metric, bar, table, or list. Item and field values are column names, optionally with a format: "created_at:reltime". Formats: text, multiline, date, reltime, number.
widgets: [ { id: "insights", name: "Insights", cubbyAlias: "analysis", kind: "dashboard", queries: [ { id: "total", label: "Total", sql: "SELECT COUNT(*) AS n FROM analysis_findings" }, { id: "by_tag", label: "By tag", sql: "SELECT tag, COUNT(*) AS n FROM analysis_findings GROUP BY tag ORDER BY n DESC LIMIT 5" }, { id: "recent", label: "Recent", sql: "SELECT title, created_at FROM analysis_findings ORDER BY created_at DESC LIMIT 10" }, ], config: { kind: "dashboard", panels: [ { title: "Total findings", query: "total", render: "metric", value: "n" }, { title: "Top tags", query: "by_tag", render: "bar", value: "n", label: "tag" }, { title: "Recent", query: "recent", render: "list", item: { title: "title", subtitle: "created_at:reltime" } }, ], }, dir: "./widgets/insights", entry: "index.html", },],composite combines panels of any kind behind a menu, with a first-run gate; see the widget-runtime reference.
Custom page
function render(result) { var tag = result.columns.indexOf("tag"), n = result.columns.indexOf("n"); document.getElementById("app").innerHTML = result.rows.map(function (r) { return "<div>" + String(r[tag]) + ": " + String(r[n]) + "</div>"; }).join("");}
window.WidgetRuntime.query("by_tag").then(render).catch(function (err) { if (err && err.name === "AgentNotConnectedError") { /* show Connect → connectAgent() */ }});Parameters
Positional parameters bind to ? in the declared SQL:
window.WidgetRuntime.query("since", [Date.now() - 7 * 24 * 3600 * 1000]).then(render);The page can only run declared queries; it never sends SQL.
Read the Memory Bank
Declare a tool query instead of sql:
queries: [ { id: "find", label: "Search", tool: "search", limit: 20 }, { id: "record", label: "Record", tool: "get" }, { id: "links", label: "Neighbours", tool: "neighbours", limit: 50 }, { id: "counts", label: "By type", tool: "countByType" },],| Tool | First parameter |
|---|---|
search |
The match text. |
get, neighbours |
A record id. |
countByType |
None. |
window.WidgetRuntime.query("find", ["renewal"]).then(render);The reader sees only the records their access allows.
Live updates
Follow a stream with subscribe; each poll’s new events arrive as one ordered batch:
var stop = window.WidgetRuntime.subscribe( "run:123", { types: ["analysis.updated"], intervalMs: 2500 }, function (events) { window.WidgetRuntime.query("recent").then(render); },);// later: stop();| Option | Default | Meaning |
|---|---|---|
types |
all | Event types to deliver. |
intervalMs |
2500 |
Poll interval. |
maxBackoffMs |
30000 |
Ceiling for the wait while polls keep failing. |
onError |
console.warn |
Called on a failed poll. |
Polling stops when you call the returned function or when the reader signs out. onIdentityChange(cb) reports sign-in changes (status: anon, connecting, ready).