Fragment Protocol
import CodeTabs from ’../../../components/CodeTabs.astro’;
The fragment protocol governs the fragment-format slot — a slot whose accepts type is a fragment format like text/html+jsml. The host declares the slot; a mod fills it with inert UI. The runtime sanitizes fragment content, resolves data bindings, evaluates conditional visibility, and routes events through the sandbox. A fragment is a fill of this slot type; everything below describes that fill’s shape and lifecycle.
Hosts declare slots in their app manifest. A fragment-format slot is a named mounting point whose accepts type names a fragment format, with optional capability gating.
{ "slots": [ { "id": "sidebar.left", "accepts": ["text/html+jsml"], "capability": "ui-mount", "multiple": true, "style": "isolated" } ]}Slot Fields
Section titled “Slot Fields”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | Unique slot identifier (e.g. sidebar.left) |
accepts | string[] | yes | — | Fragment format(s) this slot accepts |
capability | string | no | — | Capability required to mount here |
multiple | boolean | no | false | Allow multiple mod fragments |
style | enum | no | "inherit" | Styling mode |
Styling Modes
Section titled “Styling Modes”inherit— fragment inherits host styles; good for inline UI like status barsisolated— no host styles bleed in; good for panels and overlaysscoped— host exposes CSS custom properties; the fragment uses them
Fragment Fills
Section titled “Fragment Fills”Mods fill a fragment-format slot through the fills surface of their mod manifest, keyed by the slot id. Each fill provides markup and optionally declares data bindings and event handlers.
{ "fills": { "sidebar.left": [ { "format": "text/html+jsml", "source": "fragments/panel.html", "bindings": [ { "name": "health", "path": "player.health.val" }, { "name": "maxHealth", "path": "player.health.max" } ], "handlers": [ { "selector": "[data-action='heal']", "on": "click", "handler": "onHealClicked" } ], "priority": 10 } ] }}Inline Fragments (JSML)
Section titled “Inline Fragments (JSML)”For simple fragments, inline the markup:
{ "fills": { "header.status": [ { "format": "text/html+jsml", "source": "<span data-bind=\"health\">0</span> / <span data-bind=\"maxHealth\">0</span>", "inline": true, "bindings": [ { "name": "health", "path": "player.health.val" }, { "name": "maxHealth", "path": "player.health.max" } ] } ] }}Data Binding: data-bind
Section titled “Data Binding: data-bind”The data-bind attribute wires host data into fragment markup. The runtime finds elements with data-bind="<name>" and sets their content to the resolved binding value.
<p>Health: <span data-bind="health">0</span>/<span data-bind="maxHealth">0</span></p>Attributes persist in the DOM, so updates stay O(1). That holds 60fps game-loop speed without re-parsing the template every frame.
For text elements: sets textContent. For input elements: sets value.
Conditional Visibility: data-if
Section titled “Conditional Visibility: data-if”The data-if attribute evaluates an expression against the binding context to control element visibility.
<div data-if="health < 50" class="warning">You're hurting!</div><div data-if="health < 20" class="critical">Get to a healer!</div>Expressions use the same safe evaluator that powers tier 1. Truthy = visible, falsy = hidden. Re-evaluates on binding change, skips when the boolean result hasn’t changed.
The Hard Wall
Section titled “The Hard Wall”data-bind and data-if are the only two “smart” attributes the spec defines. No data-each, no data-else, no template language. Everything beyond binding and conditional visibility goes through the sandbox fragment API.
Event Handlers
Section titled “Event Handlers”DOM event handlers are declared in the manifest, not the markup. The runtime attaches listeners and delegates to sandbox functions. Each entry pairs a selector, the DOM event to listen for (on), and the sandbox handler to call.
"handlers": [ { "selector": "[data-action='heal']", "on": "click", "handler": "onHealClicked" }]Multi-match is intentional: three [data-action='heal'] buttons all wire to the same handler.
Sandbox Fragment API
Section titled “Sandbox Fragment API”For logic beyond data-bind and data-if, mods use the sandbox fragment API with the command buffer pattern:
hooks.fragment.update("health-panel", function (bindings, fragment) { fragment.toggle(".warning", bindings.health < 50); fragment.addClass(".bar", bindings.health < 20 ? "critical" : "normal"); fragment.replaceChildren(".inventory-list", bindings.inventory.map(item => "<li>" + item.name + "</li>") );});The fragment proxy accumulates operations; the host applies them after the callback returns. No direct DOM access.
Available Operations
Section titled “Available Operations”| Method | Effect |
|---|---|
toggle(selector, condition) | Show/hide matching elements |
addClass(selector, className) | Add class to matching elements |
removeClass(selector, className) | Remove class |
setText(selector, text) | Set text content |
setAttr(selector, attr, value) | Set attribute |
replaceChildren(selector, html) | Replace children |
Lifecycle Hooks
Section titled “Lifecycle Hooks”hooks.fragment.mount("panel", (fragment) => { /* inserted into slot */ });hooks.fragment.unmount("panel", (fragment) => { /* removed from slot */ });hooks.fragment.update("panel", (bindings, fragment) => { /* data changed */ });hooks.fragment.suspend("panel", (fragment) => { /* temporarily inactive */ });hooks.fragment.resume("panel", (fragment) => { /* reactivated */ });HTML Sanitization
Section titled “HTML Sanitization”For text/html fragments, the runtime sanitizes content before the host sees it. The guarantee: what you’re mounting is inert.
Preserved: structural/presentational elements, class, id, data-*, aria-*, role, scoped <style>, safe src/href.
Stripped: <script>, <iframe>, <object>, <embed>, <form>, all on* event attributes, javascript:/vbscript: URIs, document wrapper elements.
data: URIs are stripped from href and src, with one narrow exception: data:image/png, data:image/jpeg, data:image/gif, and data:image/svg+xml are allowed in src only — the scheme is gated by subtype so a data:text/html payload can never slip through.
All runtime implementations must pass the sanitizer conformance suite.
Fragment Ordering
Section titled “Fragment Ordering”When multiple fills target the same slot:
- Sort by
prioritydescending (higher renders first) - Break ties alphabetically by mod name
Cross-Validation
Section titled “Cross-Validation”When a mod loads against a host, the runtime validates each fragment fill:
- The fill keys to a slot that exists in the app manifest
- The fill’s
formatis in the target slot’sacceptslist - If the slot gates on a capability, the mod must declare it
- If the slot is
multiple: false, only the deterministic winner resolves (highestpriority, ties broken by fillid)
A slot’s payload carries a full JSON Schema, so a slot can describe exactly what a valid fill looks like — nested required, patterns, the lot. cross-validate checks each fill’s payload against that schema. It applies the schema as authored: a fill carrying more than the payload declares still passes unless the slot explicitly closes its payload. The check is on by default; pass --no-fill-payloads on the CLI (or checkFillPayloads: false to the library and MCP tool) to skip it and check only slot existence, accepted formats, and gates.
Security Model
Section titled “Security Model”Fragments are inert templates. All dynamic behavior routes through the sandbox:
- Data display → declared
data-bindbindings - Conditional visibility → declared
data-ifexpressions - User interaction → declared
handlers→ sandboxed handler functions - Complex mutations → sandbox fragment API (command buffer)
No inline scripts. No inline event handlers. No embedded code survives sanitization.