What gets refused
Every rejection code, the reserved attributes no Block declares, the budgets a document lives inside, and why refusals are structured.
A rejection names a code, the element and the parameter at fault, and — where the shape of the mistake makes it knowable — a suggestion. That is deliberate: a caller that knows what was wrong repairs the named thing, while a caller handed a paragraph tries a different shape of the same call.
Validation is accumulating. The whole list of issues comes back at once, so a document can be repaired in one pass rather than one issue per attempt.
The codes
| Code | What it means |
|---|---|
| parse-error | Malformed markup at the tokenizer level: bad attribute syntax, a stray `<`, end of input inside a tag. |
| malformed-markup | Structurally malformed: an unclosed element, a stray end tag, mismatched nesting. |
| forbidden-syntax | Syntax the format forbids outright: comments, doctype, CDATA, processing instructions, self-closing tags. |
| unknown-element | A `vf-*` element with no Block registered for it. The vocabulary is closed. |
| foreign-element | A non-`vf-*` element — `<script>`, `<div>`, `<img>`. Only Blocks may appear. |
| unknown-attribute | An attribute the Block's schema does not declare. |
| forbidden-attribute | An attribute the format bans by name: `style`, `class`, `id`, `on*`, `data-*`. |
| invalid-param | An attribute value the Block's schema rejects. |
| content-model | Children or text where the Block's content model forbids them. |
| structure | A document-shape rule: the root, a required or unique child, an allowed parent. |
| forbidden-uri | A parameter carrying an `http`, `file`, `data`, `blob` or `javascript` URI. Material is referenced as `asset://`. |
| duplicate-id | Two nodes carrying the same `vf-id`. |
| invalid-id | A `vf-id` that is not a legal id. |
| limit-exceeded | Node count, nesting depth or source size over budget. |
| unsupported-version | The document declares a format version this build cannot read or migrate from. |
Reserved attributes
Four attributes are handled outside every Block schema, so no Block declares them and no Block can:
| Attribute | What it holds |
|---|---|
vf-id | The node's identity. Minted at parse for anything that did not write one. |
vf-name | A readable layer name, up to 64 characters. |
vf-locked | A decision that this node and everything inside it is not to be changed. |
vf-hidden | Not drawn, but still present, still parameterised, still in paint order. |
Because they are not parameters, block.setParam structurally cannot reach
them: locking has its own command (node.setLocked), with its own entry in the
history. That is the point — unlocking something is somebody overriding a
decision, and it should read that way in the record rather than looking like an
ordinary attribute write.
A locked node refuses every command except node.setLocked, and so does
everything inside it: deleting an unlocked parent that holds a locked child is
refused. Locking a group locks its contents, and a reading of the document
reports the lock resolved — so an agent sees a lock rather than meeting one.
Undo is deliberately outside the guard. Undo restores a state that was legitimate when it existed, and a guard there would make history un-replayable.
What is forbidden outright
- Comments.
<!-- … -->is refused, and so are doctype, CDATA and processing instructions. A document is Blocks and nothing else. Reasoning about the design goes in<vf-brief>, which travels with it; a note about one node goes invf-name. - Foreign elements.
<div>,<img>,<script>— anything that is not a registeredvf-*Block. - The escape hatches.
style,class,id,on*anddata-*attributes are refused by name. There is no way to write CSS into a document, which is what makes the render predictable enough to check. - Self-closing tags. Write
<vf-background …></vf-background>.
Budgets
A document may hold at most 5000 nodes and nest at most 32 levels deep;
over either, validation fails with limit-exceeded rather than the renderer
discovering it later. A render call is bounded in turn, by emitted nodes and by
wall clock, so a Block cannot loop the renderer.
The gate is separate from this
Validation says whether a document is legal. The Channel Checks say whether it
is deliverable, and they are a separate pass with their own version stamp. Two
of them fail a frame — a character no bundled face can draw, and an asset://
that resolves to nothing, both of which mean the rendered file is broken rather
than that the design is wrong. The rest report and block nothing: contrast, the
safe area, a synthesized italic, clipped content, overlapping type.
Conventions
The rules that are true of vf-HTML and are in no schema — published verbatim from the engine, because three blind runs found them by getting them wrong.
Blocks
Every Block in the core vocabulary, its content model, its parameters and the worked examples the registry carries — generated from the registry itself.