dogbed

The document shell

By default render emits a bare HTML fragment — ready to pipe into something bigger. Passing --title, --css, --head or --lang switches the output to a finished document: the same fragment wrapped in a minimal HTML shell. With no flags the fragment is byte-identical to what oliver rendered alone.

A bare fragment — no shell flags:

$ dogbed render share/verdict.knap -d share/verdict.json
<h1 id="dogbed-010-first-verdict">dogbed 0.1.0 — first verdict</h1>
<p><strong>approve</strong> — the pipeline sings</p>
<h2 id="blockers">Blockers</h2>
<h2 id="nits">Nits</h2>
<ol>
<li>ship it</li>
</ol>

--title

--title wraps the fragment in the shell and sets <title>. The text is HTML-escaped for &, <, > and " — the data is untrusted:

$ dogbed render share/verdict.knap -d share/verdict.json --title 'Art & "Code" <review>' | grep '<title>'
<title>Art &amp; &quot;Code&quot; &lt;review&gt;</title>

--css

--css adds a stylesheet link to the shell. Repeatable — links keep flag order — and the href passes through verbatim: URL or relative path, unvalidated. The consumer owns it.

The whole deal — one command, one finished document:

$ dogbed render share/verdict.knap -d share/verdict.json --title 'Verdict — dogbed' --css style.css
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Verdict — dogbed</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1 id="dogbed-010-first-verdict">dogbed 0.1.0 — first verdict</h1>
<p><strong>approve</strong> — the pipeline sings</p>
<h2 id="blockers">Blockers</h2>
<h2 id="nits">Nits</h2>
<ol>
<li>ship it</li>
</ol>
</body>
</html>

--profile xhtml

--profile xhtml swaps the HTML5 shell for XHTML 1.0 Strict: strict doctype, an http-equiv meta instead of <meta charset> (which is invalid XHTML), self-closing <link … /> tags, and xmlns on the root element.

$ dogbed render share/verdict.knap -d share/verdict.json -p xhtml --title 'Verdict' --css style.css
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Verdict</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1 id="dogbed-010-first-verdict">dogbed 0.1.0 — first verdict</h1>
<p><strong>approve</strong> — the pipeline sings</p>
<h2 id="blockers">Blockers</h2>
<h2 id="nits">Nits</h2>
<ol>
<li>ship it</li>
</ol>
</body>
</html>

--lang

--lang sets the document language on the shell's root element — what screen readers pick pronunciation rules from (WCAG 3.1.1, Level A). HTML5 gets lang; XHTML also gets xml:lang. The tag passes through verbatim, so pick a valid BCP 47 tag: en, pt-BR.

$ dogbed render share/verdict.knap -d share/verdict.json --lang en --title 'Verdict — dogbed' | head -3
<!DOCTYPE html>
<html lang="en">
<head>

--max-output

--max-output caps the final document — shell included. Every output stage is bounded by the same value, so a runaway template or an escaping-heavy body dies the moment a write would cross the cap: over-limit output is never materialized. An emitted-byte ceiling is not a memory or CPU sandbox — embedders still need timeouts, concurrency limits, and OS-level resource controls:

$ dogbed render share/verdict.knap -d share/verdict.json --title T --css style.css --max-output 250
dogbed: output exceeds --max-output (250)

Data gotchas (dogfood)

Dogfood finding: data strings are Markdown source — *, backticks and [label](url) in a value render live. Mostly that is what you want; occasionally it eats characters you meant literally:

$ echo '{"t":"a *em* and `code` and [x](http://e.com)"}' | dogbed render note.knap -d -
<p>a <em>em</em> and <code>code</code> and <a href="http://e.com">x</a></p>

Dogfood finding: block markers count too — a value that opens with # renders as a heading, > as a quote. Markdown owns the whole stream, top to bottom:

$ echo '{"t":"# looks like a heading"}' | dogbed render note.knap -d -
<h1 id="looks-like-a-heading">looks like a heading</h1>

The fix: the escape_md filter walks a value back to plain text — inline specials get backslash-escaped before Markdown ever sees them:

$ cat note.knap
{{ t | escape_md }}
$ echo '{"t":"a *em* and `code` and [x](http://e.com)"}' | dogbed render note.knap -d -
<p>a *em* and `code` and [x](http://e.com)</p>

rendered by oliver rendered by dogbed — k4o templates it, oliver renders it