record ADR-0038: an included file cannot itself include

The human chose non-recursive includes over textual splicing, and this amends the
phase table that promised otherwise: includes turn out to be parse-phase, not
load-phase.

The reason is in the ADR rather than lost in a commit: splicing an included file's
AST into the page is invalid, not just buggy, because goldmark nodes hold offsets
into their own source. Converting separately is what remains, and once conversion
is separate, nesting costs a read and a parse per level with a crash at the end of
any cycle. Forbidding nesting removes that failure mode rather than bounding it.

Names the declined option and its trigger, so textual splicing can arrive with the
Stage pipeline's load phase if composing partials ever becomes a real need.
This commit is contained in:
2026-08-01 02:23:35 +06:00
parent 6c9a34a8fd
commit 935d3c3e8a
+20
View File
@@ -478,3 +478,23 @@ the data they mean. Expensive — the embedded fragments and this document chang
fragment carries one extra hop (`.Args.`) for a clarity that only pays off from the second fragment on.
Revisit if: a feature needs structured items rather than strings — a gallery of images with captions and
dimensions, most likely at image derivatives. Then `Items` becomes a slice of a named struct, additively.
## ADR-0038 — An included file cannot itself include
Date: 2026-07-30 · Status: accepted (amends `extensions.md`'s phase table, which listed includes under
`PhaseLoad`)
Decision: `{{< include file="…" >}}` renders the named file as Markdown in place, resolved against the
including bundle's directory and read through the rooted filesystem. The included file is converted with the
same Markdown configuration, in a parse marked as nested, and an include *inside* an included file renders
nothing and logs. One level, always.
Why: the obvious implementation — parse the included file and splice its nodes into the page's tree — is not
merely buggy but invalid: goldmark nodes hold byte offsets into their own source, so a spliced node makes the
renderer read past the page's buffer and panic. That leaves converting the file separately, and once
conversion is separate, nesting stops being free: each level is another read and another parse triggered by
content, with a cycle costing a crash unless bounded. A depth limit would bound it; forbidding nesting
removes the failure mode instead, and no real page needs an include that includes.
Consequence: cheap — no depth counter, no cycle detection, and a self-including file is a logged line rather
than a bounded recursion. A `gallery` inside an included file still works, because the nested parse carries
the same Origin. Expensive — a page assembled from parts that themselves compose is not expressible, and
`PhaseLoad` loses its example: includes turned out to be parse-phase, not load-phase.
Revisit if: composing partials out of partials becomes a real need. That is textual splicing before parsing,
and it wants the Stage pipeline's load phase rather than a second mechanism here.