Layer 1: tags and contracts
Status: planned; most of it is shipped and documented under Custom tags. This page states what the layer is and what it can and cannot express, so the layers above are measured against it.
Layer 1 is every extension that needs no parser involvement. The surface it offers today:
- Template tags (
tags/x.mx), compiled as units and called like components;<return>and/var. - Sidecars (
tags/x.tag.ts) withtransform,analyze,finalize, andparseOptionsread statically before the parse. - Contracts (
mx.contracts, a “dialect package” in the custom-tags docs: a tag vocabulary with validation rules):attributes,attributeTags,children,parents,declares. - Package tag sets (
mx.tags) with per-host filtering, discovered from the nearestpackage.jsonupward. - Build hooks a transform may use:
ctx.build,ctx.hoist,ctx.bindings.register, delegated tags (isDelegatedTag). - Wildcard children (
children["*"]) resolved in one top-down walk.
What layer 1 can express that MX itself used to carry
<try>is a core-owned custom tag today; the same shape is reachable from a sidecar transform plus a host claim.<fragment>is a transform that returns its children.- A narrowing conditional (
<when|u|=cond>) is a custom tag; the name<if>itself is core and cannot be shadowed, so the layer adds forms under new names, never changes core ones.
What layer 1 cannot express
Anything that changes where a token begins or ends: a new delimiter, a sigil in expression or text position, a block form, a filter block. Those are layer 2. Anything that changes an existing core form’s meaning (attribute grammar, structural tags, element resolution) is the core and is not extensible.
Hooks shared with layer 2
afterLower(ir, ctx) runs once per unit after lowering. The first user is
the Mesh contract checker (atom contracts), which moves out of core behind
it. A layer-1 package may use it for whole-unit checks that finalize
cannot do, because finalize sees one tag’s calls, not the unit.