Relationships
Informs 1
Related 14
Context
Before composition, a rune like recipe had one definition, in code, and its output was neutral so the theme could hold the creative power: a theme could rely on recipe meaning one thing, and override its config by name. ADR-028 and SPEC-158 drew the line inside that config: identity fields (sections, modifiers, schema, sequence, metaFields.*.metaType, …) are the rune's, and everything else, blocks and the rest of metaFields included, is the theme's to override (packages/transform/src/identity-fields.ts, enforced in merge.ts).
Composition moves the creative power over structure and meaning to whoever writes the definition, and since SPEC-153 that includes a site's own runes/ directory. Two sites can each have a recipe with different fields. That breaks an assumption the override path rests on:
- A theme override of a composed rune is a guess. A theme that overrides
Recipe.blocks.metadata.fieldsnames fields it cannot see. Against a recipe that declarestotalTimeinstead ofprepTime, the named field resolves to nothing and its row silently disappears. Nothing reports the mismatch. - It is already contested for one plugin. SPEC-152 D4: "A composed plan rune whose distinguishing manifest a theme can rewrite is not the same rune."
- It is the opposite of what makes compositions themeable. A composed rune's output is the shared vocabulary: primitive classes (
rf-section__*), section roles (data-section), block layouts (data-zone-layout), value semantics (data-meta-type,data-meta-sentiment), media kinds (data-media), the universal axes. A theme that styles the vocabulary renders every definition ofrecipecorrectly without knowing what a recipe is.
The question this decision answers is whether a theme should keep config authority over composed runes, and whether native runes need metadata at all once the planned compositions land.
Measured: which runes carry metadata
Every RuneConfig with metaFields or blocks, across core and the nine official plugins (baseConfig.runes and each plugin's theme.runes, read at runtime): 20 runes.
| Plugin | Runes | Disposition per its audit |
|---|---|---|
| plan | spec, work, bug, decision, milestone | compose (SPEC-152) |
| storytelling | character, realm, lore, faction, plot | four composed beside the plugin; plot after the segmented model (SPEC-147) |
| learning | howto, recipe | compose (SPEC-154) |
| docs | api, symbol | compose (SPEC-157) |
| places | event | composes after SPEC-146 (SPEC-148) |
| media | playlist | composes after SPEC-146 (SPEC-155) |
| marketing | testimonial | composes (SPEC-161) |
| core | hint, codegroup, budget | native |
17 of the 20 are composition candidates. None of the runes the audits keep as permanent plugin runes (comparison, quiz, glossary, map, the design runes) carries metadata. The three native runes that do use it as chrome, not as a description of an entity:
hint:hintType, rendered as the icon-and-label header ("Warning");codegroup:title, rendered in the tab bar;budget:durationandcurrency, in a bar above the header.
Decision
Option 2.
- A composed rune's config is its definition's. Its generated
RuneConfig(modifiers,metaFields,blocks,provides,composedRuneConfiginpackages/runes/src/lib/composition.ts) is not theme-overridable. A theme override keyed to a composed rune's type name is reported like an identity violation, naming the theme, the rune and the path, and dropped. - A theme styles composed runes through the vocabulary. It may target
[data-rune="…"]in CSS to special-case one, which is an explicit, visible choice and needs no config. - Native runes keep today's rules. ADR-028's identity fields stay reserved, and everything else stays overridable.
- Metadata is declared by a rune's definition. For a composition that is its frontmatter. The site author, not the theme, changes what metadata a rune shows, for example by redefining
recipein the project'srunes/directory.
Rationale
- One owner per rune's metadata. "Where is this rune's metadata defined?" has one answer: in its definition. A theme author and a site author can no longer both be partly right.
- It removes a silent failure instead of reporting it. Option 1's warning would be needed on every theme/definition pair forever. Option 2 makes the mismatch impossible.
- It costs native runes nothing. The 17 composition candidates are the runes whose metadata a theme might override today, and they move to definition ownership as they convert. The three native runes left use metadata as chrome.
- It generalises an existing decision. SPEC-152 D4 asked for exactly this for the plan runes; this states it for every composed rune.
Consequences
SPEC-145 D7 changes. Its sentence "blocks is not an identity field, so which fields, their order and which primitive renders them stay theme-overridable" no longer holds for composed runes. D7's placement mechanism (
{% metablock %}) is unchanged; only the theme's authority over a placed block goes. D7 gets a note pointing here.It takes effect per rune, as each converts. Until a rune becomes a composition it is native, and today's rules apply. No theme override is broken by this decision on the day it lands, because no shipped theme overrides a composed rune today.
A theme can no longer slim a plugin rune's metadata, for example hiding
difficultyon recipes. That becomes a site decision, made by redefining the rune. This is the intended owner: what a recipe shows is content, not skin.The theme-authoring docs change emphasis. They describe styling the vocabulary as the default and
data-runehooks as the exception. Overriding a composed rune's config is listed as not possible, with this decision as the reason.Follow-up for the three native runes. Whether
hint,codegroupandbudgetshould keepmetaFieldsis open:hint's andcodegroup's labels are chrome, and could come from the engine'sstructureentries, which already inject headers and icons;hintcould also be a composition;budgetis domain content living in core, and looks like a composition candidate itself.
If all three move,
metaFieldsandblocksbecome a composition-only concept, and the question "should native runes carry metadata at all" is answered no. That is a separate decision, made on that evidence.Where each stands:
budgethas a path: SPEC-162 moves its arithmetic into aledgerprimitive and composes it oversection.hintstays native for now. It becomes the target case once three things exist, and each is useful without it:- SPEC-145 D13's keyed strings, which give a composition translatable enum labels such as "Warning";
- D14's chrome carrier, so the
sunkensurface and compact density are defaults an author can override; - SPEC-163's
sentimentaxis, which replaceshint.css's per-type colours with a value a theme keys without knowing the rune.
Its icon per
typeis the one part with no answer yet.codegroupis unexamined. Itstitlesits in the tab bar of a behaviour-driven rune, which no composition places today.
Implementation. The guard is one check in
mergeRuneConfig: a rune key whose config came fromcomposedRuneConfigaccepts no theme override.assembleThemeConfigalready carries provenance for plugin runes. A test pins that an override of a composed rune'sblocksis reported and dropped.