record ADR-0036: shortcodes render through theme templates

Two things already recorded settle the shape. The theme contract says the engine
decides nothing about how content looks, "including how media is embedded", so a
<figure> assembled in Go would be the engine dressing content. And invariant 2
wants the trusted/untrusted split as real code, not goldmark's default.

Both are satisfied by the same design: parse the shortcode into an AST node, then
render it by executing a theme template of that name. Raw HTML stays disabled, so
every byte of HTML on a page came from a template the site owns, and an author's
text survives only as arguments that html/template escapes.

Recorded before the code because the syntax authors type is a disk contract, and
because a feature that cannot render itself needs a partial-rendering function
passed in at wiring time — a consequence worth agreeing to in advance.
This commit is contained in:
2026-08-01 02:23:35 +06:00
parent d80885a412
commit 47df821056
+21
View File
@@ -440,3 +440,24 @@ can be added or dropped without touching a URL. Expensive — a Bengali reader s
changing that later needs this decision reversed plus an alias for every path already published.
Revisit if: Bengali becomes the dominant language of the site — the trigger ADR-0009 already names. That
is a default-language change, not per-variant slugs.
## ADR-0036 — Shortcodes parse to engine-built nodes and render through theme templates
Date: 2026-07-30 · Status: accepted
Decision: a shortcode is `{{< name key="value" >}}` alone on a line, parsed into an AST node by a goldmark
block parser. It renders by executing a theme template of the same name with its arguments as data — the
engine supplies no markup — and raw HTML in Markdown stays disabled, so the only HTML on a page comes from
a template the site owns. A shortcode whose template is missing logs and renders nothing rather than
failing the request.
Why: two constraints meet here and both are already recorded. `theme-contract.md` says the engine decides
nothing about how something looks, "including how media is embedded", so a `<figure>` built in Go would be
the engine dressing content. And invariant 2 needs the trusted/untrusted split to be real code rather than
goldmark's default: with `html.WithUnsafe()` still off, an author's raw HTML is dropped while a
shortcode's output is trusted *because a template produced it*, and the author's bytes survive only as
arguments, which `html/template` escapes.
Consequence: cheap — markup lives where markup belongs, a theme restyles a shortcode by redefining one
template, and the escaping is the standard library's rather than ours. Expensive — a feature under
`internal/ext` cannot render itself, so it is handed a partial-rendering function at wiring time
(`cmd/khosra/wire.go`), and the shortcode's argument names become part of the theme contract, additive
only.
Revisit if: a shortcode needs to emit something no template can express. That is an argument for a new
contract field, not for the engine writing HTML.