Skip to content

Variants and versions

A component changes in two independent ways: its format (a version, for the engine and stored content) and its presentation (variants, for the student). The rules come from decision 0010.

manifest.version is the element format version: the attributes and children a source may have and what they mean.

  • The compiler writes it as v in every view: <astl-gap id="kbz4hfyfwsrba2cht" v="1" size="8">.
  • Sources may write v; without it a source element counts as version 1.
  • A breaking change bumps version and adds a migration: ComponentDefinition.migrations[n] upgrades a source element written for version n to n + 1. The engine runs the steps in order (1 to 2, 2 to 3, ...) before it validates, so definitions only ever see the current format, and the view carries the current v.
  • A source with a v newer than the registered manifest, or an older one without every migration step, gets the error VERSION_UNSUPPORTED ({ tag, version }).
  • Stored views are immutable: a player keeps rendering every older view version of a registered element. An element whose v is newer than the player's manifest is replaced by the "unsupported" notice instead of being guessed at.
import type { ComponentDefinition } from '@astrolabe/core';
export const blankDefinition: ComponentDefinition = {
manifest: { ...blankManifest, version: 2 },
// ...validate, slots, toView
migrations: {
// Version 1 called the answer `value`.
1: (el) => {
const { value, ...rest } = el.attrs;
return { ...el, attrs: value === undefined ? rest : { ...rest, answer: value } };
},
},
};

What is breaking, and so bumps version:

  • removing or renaming an attribute or a child, or changing what a value means;
  • changing a default (of an attribute, a scoring mode or the variant);
  • removing a variant.

Not breaking: adding an optional attribute, a child type, a scoring mode or a variant.

manifest.variants is { default, values } (or null for one presentation). It gives the element a public variant attribute, an enum of values (variantAttribute in @astrolabe/core), that authors pick per element:

<p>An apple a day <astl-gap id="apple" answer="keeps the doctor away" variant="resizable"></astl-gap>.</p>
<astl-choice id="pet" variant="cards">...</astl-choice>
  • Presentation only. A variant changes how the element looks and behaves for the student, never what it grades or which answers exist: every variant of a source compiles to the same answer key and grades the same. The built-in components prove it with a test that compiles every fixture with every variant.
  • The engine validates it like any enum. variant="fancy" gives ATTRIBUTE_INVALID with the allowed values in its message ("Use one of: list, cards."). A valid value stays in the view.
  • Elements render it. A value the element does not know (from a newer view) renders the default.
  • The authoring kit offers it after the element's own inspector fields, labelled by editor.variant (the kit's "Variant" when the component has none) and editor.value.variant.<value> in the component's catalogs.
Element Variant What changes
<astl-gap> inline (default) The field is size characters wide and grows with the answer up to 40 characters.
<astl-gap> resizable It grows with the answer up to the whole line, for phrases and sentences. Neither variant gets narrower than size.
<astl-choice> list (default) One option per row.
<astl-choice> cards The options as a grid of tiles, as many columns as fit, with the same radios or checkboxes, keyboard and verdict cues. In one column (a phone) they keep the list's height. Best for short options: long texts wrap in narrow tiles.

Each component page lists its variants with their labels: <astl-gap>, <astl-choice>.

  1. In the manifest, list it: variants: { default: 'list', values: ['list', 'grid'] }. The default is what elements show without the attribute, so it should be today's presentation.
  2. In the element, render it. CSS often does it alone: :host([variant='grid']) .options { display: grid; }. In code, this.stringAttribute('variant') (on AstlElement) returns the default for a missing or unknown value.
  3. In every catalog, add editor.value.variant.<value> (and editor.variant for the field label).
  4. Add a fixture per variant (<type>-variant-<value>), so the matrix, the conformance goldens and the end-to-end tests cover it, and keep the variant out of validate, slots and anything else that decides grading.

Variants belong to the component and its author: how it works for the student. A look for a whole exercise or for one element, made by the host, is a look (a style template) instead; it can restyle any variant.