WORK-622
ID:WORK-622Status:in-progress

A rune can be defined by a composition template

The mechanism at the centre of SPEC-145. A definition is frontmatter (the input declaration: tag, attributes, content model, schema, registers) plus a Markdoc body (the output template). Slot names are the join between them.

This item builds the definition → PluginRune path and the template's render. It covers only the vocabulary the two slices need:

Priority:highComplexity:complexMilestone:v0.40.0Source:SPEC-145
claude/v040-composition-templates View source

Criteria completion

Criteria completion: 16 of 17 (94%) checked; history from Oct 8 to Oct 80%25%50%75%100%Oct 8Oct 8
Branches 3
claude/v040-composition-templates current in-progress
claude/v040-metablock doneclaude/post-v0.38-roadmap-jb7pie in-progressmain done
History 2
  1. 5174eb4
    • ☑ A rune can be defined by a Markdoc template that places named slots into other runes
    • ☑ Authored content reaches a named slot as a node list, not a string
    • ☑ The template renders at the rune's transform, after field resolution, at the call site {% ref "SPEC-143" /%} substitutes at — not at preprocess (D4)
    • ☑ Runes placed by a template are transformed normally, verified by composing from a behaviour-driven primitive and confirming its markup and behavior binding are unchanged (D4)
    • ☑ `emitAttributes`' existing `$heading`, `$field` and `'$a|$b'` forms all work from a composition template, asserted per form (D4a)
    • ☑ `{% slot name="x" %}…{% /slot %}` renders its fallback content when `x` is empty
    • ☑ `sections` is placed with an ordinary `{% slot name="sections" each %}`; no special form exists (D26)
    • ☑ The emitted tree carries the composed rune's `data-rune`; the primitives' markers remain but do not claim the rune's identity (D3)
    • ☑ Slot substitution sets the ownership marker {% ref "SPEC-146" /%} defines, on both slot-placed and template-placed nodes (D10)
    • ☑ A slot emits no element; every top-level node it places carries `data-slot` in the rendered HTML, `data-owner` does not, and the composed rune's contract lists its slot names (D10c)
    • ☑ A composed rune whose `registers` source is a placed node registers the same id and data as one whose source is an attribute, read from the field bag in Phase 2; `findRef` and the strip timing are unchanged (D10b)
    • ☑ An author's nested rune inside a slot is never retyped as part of the composed entity (D10)
    • ☑ A composed rune nested inside another cannot reach the inner one's names, and vice versa (D10)
    • ☑ `extractTitle`, breadcrumb resolution and the cross-page registry read the composed rune, not its primitives
    • ☑ A plugin rune entry carrying a template and no `transform` passes `validatePlugin`, and one carrying both is rejected naming the rune ({% ref "SPEC-153" /%} D11)
    • ☑ `refrakt contracts` derives a composed rune's structure by expansion (D6)
    by bjornolofandersson
  2. 1f4aee2
    Created (in-progress)by bjornolofandersson

Blocked by

  • WORK-620
  • WORK-621

Acceptance Criteria

  • A rune can be defined by a Markdoc template that places named slots into other runes
  • Authored content reaches a named slot as a node list, not a string
  • The template renders at the rune's transform, after field resolution, at the call site SPEC-143 substitutes at — not at preprocess (D4)
  • Runes placed by a template are transformed normally, verified by composing from a behaviour-driven primitive and confirming its markup and behavior binding are unchanged (D4)
  • emitAttributes' existing $heading, $field and '$a|$b' forms all work from a composition template, asserted per form (D4a)
  • {% slot name="x" %}…{% /slot %} renders its fallback content when x is empty
  • sections is placed with an ordinary {% slot name="sections" each %}; no special form exists (D26)
  • The emitted tree carries the composed rune's data-rune; the primitives' markers remain but do not claim the rune's identity (D3)
  • A composed rune has a generated, block-less RuneConfig: its root carries no rf-* class and no data-rune-fields, and its modifiers, universal attributes and meta blocks render (D2a)
  • Slot substitution sets the ownership marker SPEC-146 defines, on both slot-placed and template-placed nodes (D10)
  • A slot emits no element; every top-level node it places carries data-slot in the rendered HTML, data-owner does not, and the composed rune's contract lists its slot names (D10c)
  • A composed rune whose registers source is a placed node registers the same id and data as one whose source is an attribute, read from the field bag in Phase 2; findRef and the strip timing are unchanged (D10b)
  • An author's nested rune inside a slot is never retyped as part of the composed entity (D10)
  • A composed rune nested inside another cannot reach the inner one's names, and vice versa (D10)
  • extractTitle, breadcrumb resolution and the cross-page registry read the composed rune, not its primitives
  • A plugin rune entry carrying a template and no transform passes validatePlugin, and one carrying both is rejected naming the rune (SPEC-153 D11)
  • refrakt contracts derives a composed rune's structure by expansion (D6)