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.