View templates and results#

A script that prints to the console gives you text that scrolls away. A script that renders a template gives you a page.

view.present("research/evidence", {
  title = "Does a four day week maintain output?",
  rows = findings,
  caveats = { "small samples", "two of four are funded by an interested party" },
})

In Volt that is present(brief, template: "research/evidence").

Templates#

A template is markdown with a deliberately boring subset of mustache.

{{name}} a value, HTML-escaped
{{{name}}} a value, raw — for markdown you built on purpose
{{a.b.c}} a path into nested data
{{#rows}}…{{/rows}} the block once per item; {{.}} is the item
{{^rows}}…{{/rows}} the block only when rows is empty or missing
{{! note }} a comment, dropped

There are no partials, no lambdas, and nothing that can execute. A template formats data and that is the whole of what it can do.

Two details worth knowing. An empty list counts as absent, so {{^rows}} is how you write "nothing found" — but 0 counts as present, because a measured zero is a result. And the nearest scope wins, so a row's own name shadows the document's.

An unclosed or mismatched section is an error rather than silently swallowing the rest of the page.

The ones that ship#

Template For
research/summary A question, an answer, and the sources it came from
research/matrix A grid: one row per subject, one column per question
research/comparison Side-by-side findings with an explicit "where they disagree"
research/evidence A weighed table — what each source says, how big it is, who paid
research/brief One page: the answer first, then the working

They live in the same place as prompts and Lua functions — Settings → Library — so they get the same editor, the same storage, and the same "reset to the shipped version". Edit one and your version is used; delete your changes and the original comes back.

Writing your own#

Add an entry in Settings → Library with kind template. The name is how scripts ask for it; a / in the name is just part of the name, which is why the shipped ones are grouped under research/.

The data is whatever your script passes. A template that expects rows and is handed nothing renders the empty case rather than failing — which is the right behaviour for a view, and the reason sections treat missing and empty the same way.

Results#

view.show(text, title, format) puts anything in the Results panel; view.present(name, data, title) renders a template and shows it, which is what almost every script wants.

Results arrive as the script produces them, not when it finishes — a long run fills the panel as it goes. The store is bounded at the most recent fifty, and view.results() reads the whole list back inside a script.

From a script#

local rows = {}
for _, study in ipairs(studies) do
  rows[#rows + 1] = { source = study.id, n = study.n, finding = study.effect, funding = study.funding }
end

view.present("research/evidence", { title = "Four studies", rows = rows })

And without a template, when the text is already what you want:

view.show(report, "Weekly scan", "markdown")

See the script API for the full view.* surface.