add shortcodes as the first internal/ext feature

A call is `{{< name key="value" >}}` alone on a line, parsed by a goldmark block
parser into an AST node and rendered by executing a theme template of that name
(ADR-0036). `figure` ships; `include` and `gallery` need the including bundle's
directory, which the parser does not carry yet, so they wait.

The layering did the design work here. internal/render may not import
internal/ext, so render.New takes a callback that receives a Partial and returns
Markdown extensions, and cmd/khosra/wire.go holds the only list of enabled
features. Empty that list and the engine still builds and serves — which is the
property extensions.md says the contract should have.

Raw HTML stays disabled. An author's text reaches a page only as arguments that
html/template escapes in context, which the real binary shows: a hostile alt
becomes &lt;script&gt; and src="javascript:…" becomes #ZgotmplZ. Getting
contextual escaping from the standard library rather than writing it is the whole
reason a fragment renders this instead of the feature.

parseSet became variadic so the fragment set reuses it rather than growing a
second copy of the overlay logic; `Partial` takes map[string]string after the
advisory correctly flagged `any` as generality nothing had asked for.
This commit is contained in:
2026-08-01 02:23:35 +06:00
parent 47df821056
commit 1f7ca09313
14 changed files with 459 additions and 55 deletions
+16 -4
View File
@@ -268,11 +268,23 @@ Widow prevention is not implemented: doing it safely needs a transform over the
pass over rendered HTML, which cannot tell prose from an escaped code span. It waits for the Stage
pipeline.
## Includes and shortcodes `[spec]`
## Shortcodes
Shortcodes are a Stage running on trusted content only (ADR-0003), never on comments. File inclusion
resolves relative to the including bundle and may not escape the site root. Transclusion of another
bundle's body is Arc 4 and needs a cycle guard on the first attempt.
A shortcode is `{{< name key="value" >}}` **alone on a line** — the whole line, or it is prose. Every
argument is `key="value"`; there is one spelling, so nothing is guessed and a malformed call stays visible
as text instead of half-working.
Arguments are data, never markup: the call renders through a theme template of the same name
(`theme-contract.md`), and raw HTML in a body remains dropped, so the only HTML on a page came from a
template the site owns (ADR-0036). A call naming a shortcode the theme has no template for renders nothing
and logs it — one typo does not take a page down (ADR-0029).
Shortcodes run on site-root content only (ADR-0003), never on anything untrusted.
`figure` exists. `include` and `gallery` are `[spec]`: both need the including bundle's directory, which is
context the parser does not carry yet. File inclusion will resolve relative to the including bundle and may
not escape the site root. Transclusion of another bundle's body is Arc 4 and needs a cycle guard on the
first attempt.
## Images `[spec]`