Language completion

LZR language reference

This page completes the LZR language surface documented across LZR templating and expressions and scope: how the parser really treats the two syntax layers, the numeric <for> loop, the <frame> element, the data-hm-* runtime binding vocabulary, and the diagnostics the compiler and renderer can emit. Every example and every diagnostic message on this page was produced by the real engine (lzr render-page / lzr compile-template), not transcribed from intent.

Two syntax layers: tags author, markers lower

LZR control flow has two textual forms. The structural tag form (<if>, <elseif>, <else>, <foreach>, <for>, <set>, <frame>) is the authoring surface. Before compilation the parser rewrites each structural tag into an internal marker region in mustache syntax — {{#if expr}}, {{else}}, {{/if}}, {{#foreach item,index in collection}}, {{#set name = expr}}, {{#for key=n;until=3;…}}, {{#frame name64=…}} — and those markers are what the compiler turns into region kinds (if_open, else, if_close, foreach_open, for_open, set, frame, expr, static). An <elseif> has no marker of its own: it lowers to {{else}} plus a nested {{#if}}, closed by as many {{/if}} markers as the chain nested.

The numeric range loop: <for>

Where <foreach> iterates a collection you already have, <for> generates its collection from numeric bounds. All bounds are expressions and must evaluate to numbers.

for attributes

Verified against parseFor and evaluateForRange behavior through lzr render-page.

AttributeRequiredDefaultMeaning
keyyesScope name bound to the current range value.
untilone of until/throughExclusive end bound: iterate while the value is before it.
throughone of until/throughInclusive end bound: iterate while the value has not passed it. Declaring both until and through is malformed.
fromno0Start value.
stepno1Increment per iteration. Negative counts down; zero is an error; fractions are allowed.
indexnoindexScope name bound to the zero-based iteration counter.

Verified example — this source:

for source lzr
<ol>
  <for until="{{ 3 }}" key="n">
    <li>n={{ n }} index={{ index }}</li>
  </for>
</ol>
<p><for from="{{ 2 }}" through="{{ 10 }}" step="{{ 4 }}" key="v" index="slot">{{ slot }}:{{ v }} </for></p>
		

renders through lzr render-page to:

for output (real engine output) html
<ol>
  <li>n=0 index=0</li>
  <li>n=1 index=1</li>
  <li>n=2 index=2</li>
</ol>
<p>0:2 1:6 2:10 </p>
		

A negative step counts down (from=3 until=0 step=-1 renders 3 2 1), and fractional steps keep their fractions while whole numbers render as integers (from=0 through=1 step=0.5 renders 0 0.5 1). Item identity inside the loop is the zero-based index over a synthetic range collection named after the loop key, so editing a bound refreshes the loop frame in place. The live scenario fixture is controls/for-range.

for failure modes

Messages are verbatim engine output.

DiagnosticWhenMessage
malformed_forMissing key, neither or both of until/through, or whitespace in names.for syntax must be <for from="{{ start }}" until="{{ end }}" step="{{ step }}" key=value index=index>
for.zero_stepstep evaluates to 0.for step must not be zero
for.non_numeric_boundA bound expression produces a non-number.for expression "'x'" produced string, not a number
for.range_too_largeMore than 100000 iterations.for range exceeds 100000 iterations

Bounded route-render jumps: <frame>

A <frame> declares a named region whose content is rendered from another route. The server render emits a host element; the browser runtime fills, refreshes, and navigates it. name and src are required; src may interpolate expressions (src="/items/{{ 40 + 2 }}" resolves to /items/42 — engine-verified).

frame attributes

Defaults are the values the renderer stamps when the attribute is omitted - taken from real host output.

AttributeValuesDefaultMeaning
nameidentifier (no whitespace)requiredFrame identity for the runtime and inspector.
srcroute path, expressions allowedrequiredRoute whose render fills the frame.
targetstringderived from src (slashes become dashes)Render-target key for the frame content.
loadingeager | visible | on-activate | lazy (legacy)eagerWhen the runtime loads the frame content.
route-patternabsolute path, literal or {param} segmentsnoneRoute family this frame serves; parameters must be unique.
refreshstringmanualRefresh policy stamped for the runtime.
historypush | replace | nonepushHow frame navigation writes browser history.
errorinline | retaininlineShow errors in place, or retain the last good content.
preservestringnoneState preservation hint stamped for the runtime.

A frame written with a body is a container: the server-authored body stays in the host as placeholder content and the host is stamped data-hm-frame-state="placeholder". That body is mandatory for loading="visible", loading="on-activate", and error="retain" — a self-closing frame with those values fails compile with frame.placeholder_missing. Host attributes on the tag pass through a whitelist (class, id, role, aria-hidden, data-*); everything else is dropped. Verified render:

frame source lzr
<frame name="docs-stage" src="/runtime/frame/root-1/stage.html"
       loading="visible" history="replace" error="retain"
       class="stage-shell" data-stage="docs">
  <p>Placeholder body shown until the frame activates.</p>
</frame>
		
frame host output (real engine output) html
<div class="stage-shell" data-stage="docs" data-hm-frame="docs-stage"
  data-hm-frame-src="/runtime/frame/root-1/stage.html"
  data-hm-frame-target="runtime-frame-root-1-stage.html"
  data-hm-frame-route="/runtime/frame/root-1/stage.html"
  data-hm-frame-source-template="..." data-hm-frame-source-span="..."
  data-hm-frame-loading="visible" data-hm-frame-refresh="manual"
  data-hm-frame-history="replace" data-hm-frame-error="retain"
  data-hm-frame-state="placeholder">
  <p>Placeholder body shown until the frame activates.</p>
</div>
		

frame failure modes

Messages are verbatim engine output.

DiagnosticMessage
malformed_frameframe syntax must be <frame name=main src=/routes/path />
frame.loading_invalidframe loading "fast" is not eager, visible, on-activate, or the legacy lazy value
frame.history_invalidframe history "rewrite" is not push, replace, or none
frame.error_mode_invalidframe error mode "explode" is not inline or retain
frame.route_pattern_invalidframe route pattern "a//b" must be an absolute pathname with literal or {param} segments
frame.placeholder_missingvisible, on-activate, and retained frames require a server-authored container body
frame.src_missingRender-side guard: frame src is required when the evaluated src is empty.

Live frame fixtures with runtime canaries live under src://runtime/frame/eager.html and src://runtime/frame/visible.html.

Runtime binding attributes: data-hm-*

Templates opt elements into browser-runtime behavior with data-hm-* attributes. The binding compiler (lzr compile-template-metadata) classifies every such attribute into a binding kind; the table below is the verified classification of a real report.

data-hm-* binding vocabulary

Kinds and keys verified via lzr compile-template-metadata (schema lzr.template-metadata.v1).

AttributeBinding kindKeyRole
data-hm-routerouterouteClient-graph navigation to a route on activation.
data-hm-route-param-*route-paramthe * suffixNamed parameter fed into the route pattern.
data-hm-partialpartialpartialMarks a region the runtime can swap partially.
data-hm-selectionselectionselectionSelection state binding.
data-hm-resourceresourceresourceDeclares the data resource an element depends on.
data-hm-commandcommandcommandDispatches a named command (for example an action-pack action).
data-hm-command-param-*command-paramthe * suffixNamed argument for the command.
data-hm-cutoutimperative-cutoutcutoutRegion owned imperatively by runtime code, excluded from declarative patching.
any other data-hm-*local-effectname minus data-hm-Local presentation effect; no graph authority.

Frames join this vocabulary from the other side: a <frame> tag is projected into the same metadata report as data-hm-frame, data-hm-frame-src, and its option attributes, and the rendered host carries them (see above). Expressions inside attribute values additionally produce expression-binding rows that record the expression, its root, and its resolved data target. The expansion-provenance attribute data-hm-from is documented with components.

Diagnostics vocabulary

Compile and render diagnostics carry a severity, a stable code, a message, and a source span. Three families cover template structure; each code below was reproduced against the real engine. The malformed-repair scenario shows how the Inspector surfaces them with repair affordances.

Structural balance diagnostics

Emitted by the marker-region scanner when control regions do not pair up.

CodeMeaning
unclosed_if / unclosed_foreach / unclosed_forA control region was opened and never closed. The scope mapper mirrors this as template.scope_unclosed_if (etc.) with its own span.
unmatched_if / unmatched_foreach / unmatched_forA closing tag or marker appeared without its matching open.
unmatched_elseelse without a matching if.
malformed_foreach / malformed_for / malformed_set / malformed_frameThe construct's attributes do not satisfy its grammar; the message states the expected shape.

Markup diagnostics (template.markup.*)

Tag-syntax validation over the authored source; balance of ordinary HTML elements is deliberately not enforced here.

CodeMeaning
template.markup.unterminated_tagA < opened a tag that never reaches >.
template.markup.unterminated_comment<!-- without -->.
template.markup.malformed_tagTag body is empty or not a tag at all.
template.markup.malformed_tag_nameTag name starts with an illegal character.
template.markup.closing_tag_has_attributesClosing tags must not contain attributes or trailing bytes.
template.markup.invalid_self_closing_tailThe self-closing / must be the final token in a start tag.
template.markup.invalid_attributeAttribute does not start with a legal name character.
template.markup.invalid_attribute_tailUnexpected bytes after an attribute name (expected whitespace, =, /, or >).
template.markup.missing_attribute_valuename= with no value following.
template.markup.unterminated_attribute_valueA quoted attribute value never closes its quote.
template.markup.invalid_unquoted_attribute_valueAn unquoted value contains ", ', <, or =.

Value and strict-mode diagnostics

Emitted at expression evaluation, frame validation, and the strict-runtime gate.

FamilyCodesMeaning
for rangesfor.zero_step, for.non_numeric_bound, for.range_too_largeNumeric-range rules above.
framesframe.loading_invalid, frame.history_invalid, frame.error_mode_invalid, frame.route_pattern_invalid, frame.placeholder_missing, frame.src_missingFrame runtime-contract rules above.
strict-runtime gatelegacy.runtime.handlebars_control, legacy.runtime.repeat_tag, legacy.runtime.make_tag, legacy.runtime.set_shorthand, legacy.runtime.root_wrapper, legacy.runtime.original_src_protocol, legacy.runtime.json_decode_pipeLegacy-only constructs rejected before strict rendering; most messages name a migration path (lzr legacy-normalize or --legacy-compat).
Verify this page's claims shell
lzr render-page -in src/controls/for-range.html -out /tmp/for-range.html
lzr compile-template-metadata -in src/docs/lzr-language-reference.html -out /tmp/meta.json
hypermake explain rendered/public/controls/for-range.html
hypermake affected-by src/docs/lzr-language-reference.html