From 935d3c3e8a47c976f716de4532150c7dacb103d3 Mon Sep 17 00:00:00 2001 From: bdeshi Date: Fri, 31 Jul 2026 02:00:22 +0600 Subject: [PATCH] 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. --- docs/decisions.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/decisions.md b/docs/decisions.md index 5103186..221151a 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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.