v0.42.0 · What a composition can express
Since v0.41.0, anyone can write a composed rune. This milestone widens what a definition can say. Templates may be indented, and refrakt format indents nested runes for you. Content may be split on --- into named zones, a sections model may treat content with no headings as one section, and each can walk the items of a plain list. An itemModel declared in YAML now extracts its fields instead of leaving every one empty (What the composition parser must accept, and what belongs elsewhere, Markdoc sources may be indented).
Changes
- Composed runes can now say more in their content model (SPEC-169):
type: delimitedsplits content on---into fixed, named zones. The slots are all the zones' fields together, andprimarynames the zone that receives content written with no---. A field name used in two zones, a delimiter other thanhr, anddynamicZonesare each construction errors (zone-field-twice,delimiter-unsupported,dynamic-zones).implicitSectionon a sections model makes content with no section heading one item ofsectionsrather than preamble. Itsattributessupply$eachand must give exactly the keys ofemitAttributes(implicit-section-mismatch).eachover a list field iterates the items of a non-greedy field that matches one list. Each item's content is the bare{% slot /%}, and$eachbinds nothing.eachover a greedy list field is an error (each-on-list-ambiguous).- Fix (BUG-038): an
itemModeldeclared in YAML now compiles itspatternstrings into regular expressions at every nesting level. Before this, every$eachfield it fed was silently empty. An invalid pattern is reported ascontent-model-invalid, naming the field. refrakt inspectshows a composed rune's content model and template slots (compositionin--json), andrefrakt referencelists its template slots and marks a delimited model'sprimaryzone.- Markdoc content may now be indented. A line indented by a tab or by four or more spaces is no longer an indented code block, so the content of a rune can be indented to show its nesting:
- Before, those lines silently became a code block. That broke a tag whose closing line was indented, with no error. Every surface parses this way: the content loader,
refrakt validate, the language server, the editor preview, composed-rune templates and plan files. They all go through one new shared function,parsefrom@refrakt-md/runes, which tokenizes with Markdoc'sallowIndentation. - Behaviour change: a code sample written as an indented code block, with four spaces or a tab and no fence, now renders as ordinary text. Use a fenced block (
```) instead. Fenced code is unaffected, including its own indentation. Every tracked Markdown file in this repository parses to the same tree as before. - Markdoc is upgraded from 0.4.0 to 0.5.10, pinned exactly in every package. New sites get
^0.5.10. An existing site that lists@markdoc/markdocitself should move it to^0.5.10, so that it does not install a second copy. - Every tracked
.mdfile in this repository parses to the same AST as before, and rendered output, contracts and structured data are unchanged. What authors may notice: {% if %}and{% else %}conditions are type-checked. A string or number written as a condition, such as{% if "yes" %}, is now anattribute-type-invaliderror. Before, any value was accepted. Use a variable, a function such asequals(…), ortrue/false.{% table %}is stricter. Content between table rows that is not part of a row is now a criticaltable-syntaxerror, where it was silently dropped before.- Validation errors point at their own line. An error that a rune's validator raises with a specific location is now reported at that line, not at the first line of the rune.
- Fewer formatting round-trip bugs. An image URL with several parentheses no longer loses everything after the first
(. A list or code fence inside a blockquote no longer falls out of the quote. Both affected content that refrakt formats and re-parses internally, such as collection item bodies. - New
refrakt format [paths]re-indents nested runes. A rune's content is indented one tab only when the rune holds another block rune. A rune holding only prose stays flat, and inline runes do not count. Only leading whitespace changes, and fenced code moves as a block with its own indentation kept. Before writing, the formatter checks that the file still parses to the same tree; if it would not, the file is left alone and reported with its line. refrakt format --checkwrites nothing and exits 1 if any file would change, for CI.--format jsongives a machine-readable report.- The language server provides document formatting with the same rule, so editors can format on save.
- The MCP server adds a
refrakt.formattool. formatIndentationis exported from@refrakt-md/runesfor other tools.RuneConfig.sequencecan now be read from a modifier:{ fromModifier, valueMap?, default? }, the formsequenceDirectionalready has.valueMaptranslates a modifier value into a sequence style, anddefaultapplies when the value is unset or unmapped. The literal form is unchanged.plotuses it (sequence: { fromModifier: 'structure', valueMap: { linear: 'connected' } }) in place of itspostTransform. Its rendered HTML is unchanged for everystructure.refrakt inspect --audit-dimensionscounts a modifier-sourced rune under each style its map or default can produce.- Indented Markdoc now displays correctly in code samples.
- Highlighting: a
markdoccode block whose lines are indented with tabs is highlighted the way its flat form would be. The tabs are kept as written. Before, Markdown's grammar read every tab-indented line as an indented code block, so indented runes, headings and emphasis showed as plain text. Lines indented with four spaces at least get their tags highlighted. previewwithsource=true: the source it shows is now dedented to the preview's own content, keeping the nesting inside it. Before, the first line lost its indentation and the rest kept theirs.- Code blocks show blank lines again, and render tabs two columns wide.
- Blank lines: a code block lays its lines out as a grid, and an empty line had no height, so every blank line in a code sample disappeared. An empty line now takes a normal line's height. A trailing newline still adds no extra row.
- Tab width:
prenow setstab-sizefrom a new custom property,--rf-code-tab-size, which defaults to2. Rune content is indented with tabs, and the browser default of eight columns pushed indented samples off narrow screens. Set it in a theme or site stylesheet to change it. - Rune tags inside an indented code fence are no longer processed as real tags. refrakt escapes
{% … %}inside fences before parsing, but it only recognised a fence that started at the beginning of a line. A fence inside a list item, or inside a rune whose content is indented, had its tags parsed: for example, anmdexample showing{% hero %}became a realhero.refrakt formatnow also checks its rewrites against the tree the site actually builds from, with tags inside fences escaped.