Fragment Vocabulary
A host does not have to speak HTML. It can declare its own vocabulary of nodes, name their props and prop types, and say which nodes may nest inside which. A mod then authors UI as a typed node tree, and the toolchain holds it to the contract.
This is the same fragment surface as the UI Dashboard, pointed at a custom format instead of text/html.
What It Shows
Section titled “What It Shows”- A declared vocabulary of three nodes (
Panel,Link,Progress) over aj5mlformat the host namesapplication/x-panel+json - Typed props with sinks and defaults:
Panel { heading, collapsible },Link { href (a uri sink) },Progress { value, max, tone } - Containment as a separate axis that defaults open:
Panelleaveschildrenunset so it nests anything to any depth,Linktakestext, andProgressis a childless leaf - A mod that authors an open-nested tree, a
Panelholding aLink, aProgress, and a nestedPanel, resolved against the vocabulary with node names and prop types intact - The toolchain as a contract:
validatefails a misspelled node,typegentypes the node tree a mod author writes, anddocgenpublishes the node table
Running the Demo
Section titled “Running the Demo”node examples/fragment-vocabulary/src/demo.jsThe demo loads the mod, resolves its fragment against the vocabulary, and prints the resulting node tree with props preserved (value stays a number, collapsible stays a boolean).
App Manifest
Section titled “App Manifest”The host declares the format and its nodes inline. A single format inlines its nodes; a vocabulary shared by more than one format is declared once under a top-level vocabularies block and named from each format’s scope.
{ "slots": [ { "id": "sidebar", "accepts": ["application/x-panel+json"], "capability": "ui-mount", "multiple": true, "style": "isolated" } ], "formats": { "application/x-panel+json": { "syntax": "j5ml", "nodes": { "Panel": { "props": { "heading": { "type": "string" }, "collapsible": { "type": "boolean", "default": false } } }, "Link": { "children": "text", "props": { "href": { "type": "string", "sink": "uri" } } }, "Progress": { "children": "none", "bindTo": "value", "props": { "value": { "type": "number" }, "max": { "type": "number", "default": 100 }, "tone": { "type": "string", "enum": ["ok", "warn", "error"] } } } } } }}A node that declares no children accepts any node in scope to any depth; children: "text" takes only text; children: "none" is a leaf; and a named allow-list (children: ["Panel", "Link"]) narrows to exactly those nodes. A prop’s sink (uri, image-uri, css, html) tells the sanitizer how to treat its value.
The Mod — Authoring a Node Tree
Section titled “The Mod — Authoring a Node Tree”The mod fills the slot with a fragment whose source is a j5ml node tree rather than HTML. Each node is a [name, props, ...children] tuple:
["Panel", { "heading": "Session", "collapsible": true }, ["Link", { "href": "https://xript.dev" }, "docs"], ["Progress", { "value": 72, "tone": "ok" }], ["Panel", { "heading": "Nested" }, ["Link", { "href": "https://xript.dev/changelog" }, "changelog"] ]]The nested Panel is what “containment defaults open” buys: no declaration is needed to let a panel hold another panel.
The Toolchain as a Contract
Section titled “The Toolchain as a Contract”Because the vocabulary is declared data, the tools can hold a mod author to it. Run each against the host manifest:
npx xript validate examples/fragment-vocabulary/manifest.jsonnpx xript typegen examples/fragment-vocabulary/manifest.jsonnpx xript docgen examples/fragment-vocabulary/manifest.json -o docs/validaterejects a misspelled node or an out-of-set enum before it ever mounts.typegenemits a tuple type per node and a union over the vocabulary, so a mod author writing["Panel", { heading }, ...]gets the props, the children, and the nesting checked by the compiler.docgenpublishes a node table listing each node’s props and what it may parent.
See the source code for the complete example.