Skip to content
zabloo

Versioning

One number, and it is a major: what ships without touching it, what forces a bump, and why the format deliberately has no minor version.

This page answers one question: what happens when the game is older than the content it is being handed? That is the normal case, not the edge case — a screen hot-updated to players today lands in whatever build they installed months ago. One number decides whether the two can work together at all, and it is the envelope’s v. If the SDK implements that major it renders the payload, quietly ignoring anything newer that it does not recognise. If it does not, it refuses the payload whole rather than drawing it wrong.

The three pages that explain hot-update

Shipping UI to a game that is already in players’ hands is what this format exists for, and three pages carry it between them. This page says whether an SDK and a payload can meet at all. Loading says what the SDK does with the payload once they do — what it repairs, what it refuses, and what it tells the game. The host channel is the call that hands the new payload over, reload, and the callback the diagnostics come back on.

An envelope carries one version number:

{ "v": 1, "tokens": {}, "views": {} }

v is the major version, and it is the only version number in the format. An SDK implements exactly one major and refuses anything else.

This matters more here than in a library, because the two sides move independently.

The policy

Every change to the format is one of two kinds, and which one it is decides whether games already in the wild keep working. There is no third case:

normativeVersion policy

ChangevWhat an older SDK does
Additive — the format growsunchangedIgnores what it does not know, renders the rest.
Breaking — the format changes its mindbumpedRefuses the payload (unsupported-version, fatal).

There is deliberately no minor version. A minor number could only tell an SDK “this content uses things you may not know”, and the answer to that is already written into the format: it ignores them, by rules that are normative and tested. What an SDK actually needs to decide is binary — can I render this at all? — and that is exactly what the major answers. Every loader gets one comparison instead of two, and there is no second compatibility rule to keep consistent across targets.

The cost is real and accepted: a v1 SDK cannot report “this content was built for a later v1”. It renders what it understands and stays silent about the rest, which is the same thing it does for a prop that was simply left out.

What is additive

These changes ship without touching v, because forward-tolerance already defines what an older SDK does with them:

  • A new node type. An SDK that does not know it renders it as a Container preserving layout, style, visible, disabled and children.
  • A new optional property, on a node or in Style/Layout. Unknown properties are ignored silently, and absent means “the default”, which is what the older SDK applies.
  • A new value in a closed set (Easing, ImageFit, AnchorAt, GroupBehavior, ScrollAxis, StateName, OverlayTrigger). The validator checks shapes, never vocabularies, and an unknown value falls back to the property’s default.
  • A new group behaviour. Ignored, so the children lay out as ordinary siblings.
  • A new token in the dictionary, a new asset entry field, a new diagnostic code.
Additive is a design constraint, not a shape

A new capability is additive only if its absence is a reasonable picture of the UI — only if its degradation still reads as the screen it was meant to be. A Repeat degrades to one static copy of its template, a ProgressBar to its track with an unsized fill, a Spinner to its beads at rest. If a new node type would degrade to something misleading — a control that looks operable but is not, a dialog that renders as an opaque box over the screen — it is not additive, whatever its shape.

Emitting an additive feature is therefore an authoring decision: the content still loads everywhere, and it looks complete only where the SDK is new enough.

What breaks

A change belongs here when an older SDK would render something wrong rather than something incomplete. That is the whole test: silence is acceptable, a lie is not.

normative

These require a new major, because no forward-tolerance rule can absorb them:

  • Removing or renaming a node type, a property, or a value of a closed set.
  • Changing the meaning or the default of an existing property. An old SDK keeps applying the old meaning to the same JSON, silently.
  • Changing a positional slot contract — which child is a Collapse’s header, a Toggle’s checked indicator, a Slider’s thumb, a Repeat’s template. Slots are read by index, so a renumbering is invisible and total.
  • Changing the output of a normative algorithm: the state merge order, the text wrap algorithm, the easing curves, the spatial navigation score, path resolution inside item scopes. Two SDKs on the same major must produce the same frame.
  • Making an optional property required, or changing the shape of the envelope itself.
  • Turning a silent degradation into a refusal, or the other way round.

Fixing a bug — where an implementation disagreed with this specification — is not a breaking change. The specification is the contract; an implementation that did not match it was already wrong.

What SDKs do

supportsVersion(v)  ⟺  v is an integer and v === IR_VERSION

IR_VERSION is the major an SDK implements — 1 today. A mismatch, in either direction, is fatal, reported as unsupported-version, and the payload never becomes a view. Content older than the SDK is refused for the same reason as content newer than it: a v1 SDK does not carry v2 semantics, and a v2 SDK does not keep v1’s.

Refusal is not a crash. On a hot-update it is a discarded update — the UI already on screen stays exactly as it is. Only a first mount of an unsupported payload surfaces as an error to the game, because there is nothing on screen to protect.

Package versions are a different number

The npm packages (@zabloo/format, @zabloo/react) and the engine SDKs follow ordinary semver, and their versions are not the IR version. A package major may change for reasons that have nothing to do with the format — a renamed export, a dropped Node version — while the IR it reads and writes stays at v: 1.

The only number that decides whether a payload and an SDK can meet is the envelope’s v.