From 34b1b18012f779158ace17ee09bbda50610fa913 Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Thu, 30 Jul 2026 10:21:48 +0600 Subject: [PATCH] record ADR-0036: shortcodes render through theme templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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
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. --- docs/decisions.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) 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.