diff --git a/docs/decisions.md b/docs/decisions.md index 78db789..cb7cc8e 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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 `
` 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.