How a demo looks

../README.md says what a demo is: a pure init/update/view, discovered by its demo export, whose output a reader can check from outside. This file says how one looks, so that a reader who has used one demo can read the next without learning a new page.

The rules here were drawn from the demos already on the site, and where they disagreed, one way was picked and the reason written down. The shared builders put the required lead before the controls; the shared refusal element puts Refused: above the module's message in its verdict box. A demo may deviate where its subject needs it, and says why in its JSDoc.

One order, top to bottom

Demo                      ← the page's own heading; the demo never writes it
  lead                    ← what this is, how to read it, how to check it
  examples                ← optional drop-down of named inputs
  input                   ← labelled field(s); a button only for slow work
  output                  ← caption, then the result — or the refusal

The demo never writes its own h2. The page wraps every demo in a section headed Demo (demoSection in ../page/module.f.mjs). Inside it, a demo made of several parallel parts heads each with an h3: several results side by side, as the compiler's side-by-side page and a grammar's railroad diagrams show, or the halves of one workflow, as the VDF demo's Evaluate and Verify are. It uses no other heading level.

The lead: say what the reader is looking at

Every demo opens with a lead: one to three sentences in a p, before any control. The page shows only the demo's view; the module's @module JSDoc is not on it. Without a lead, a reader meets a textarea and a block of output with nothing to say what either is for — which is how the bigint demo explains writing its own (fjs/types/bigint/demo.f.mjs).

The lead says, in this order and as briefly as it can:

  1. What the module does to the input — "Parses the text as JSON and writes it back with its keys sorted, on one line."
  2. How to read the output, when it has a notation — the railroad diagrams' "a pill is text the input holds, and a box is another diagram".
  3. How to check it from outside, when there is a way. A check that lives only in JSDoc is a check no reader of the page sees. Where the check is a command, the demo shows it under the result with the reader's input already in it, as a copyable block — the SHA-2 demo's "Verify independently with OpenSSL:" — rather than in the lead.

The lead is plain prose, not a caption: no trailing colon, no bold.

DOM identifiers and repeated instances

Demo views declare IDs and matching references locally. The shared page builder and browser runtime scope them identically to demo-control:<length>:<scope>:<id>. The default scope is the demo module path. The : delimiter cannot occur in a README heading slug, so documentation keeps its existing anchors and demo labels resolve to their controls. IDs must be unique within each demo view.

demoSection(demo, scope) accepts an explicit stable scope when embedding additional demo sections, including repeated instances of one module. These extra sections also receive a scoped demo-section:<length>:<scope> heading ID; the ordinary module-page section keeps #demo. Give every additional section an explicit scope. A manually embedded root uses the matching data-demo-scope attribute; distinct instances must have distinct scopes. Keep that scope fixed across renders, including when controls appear in a new view.

The transform covers shared widgets and manual HTML/SVG together: label for, datalist list, form and accessibility ID references, local href/xlink:href, and SVG/CSS url(#...) references follow IDs declared in the same view. Links to documentation or external targets and code-copy payloads are left intact. Future select/button widgets use this same boundary; demos need no separate ID rewrite. Document references inside attributes rather than embedding them in script text, which this renderer does not interpret.

Event names stay the demo's local names. Native radio name values are also scoped so repeated instances do not share a browser radio group; data-demo-event-name retains the original name and the runtime delivers it in input events, including startup edits and later renders. Other control names remain unchanged.

Input

  • Every field has a visible label naming what to type, as a noun in sentence case and without a colon: Source, JSON, Text, A release file. Where a label would break a sentence the field sits in, as the bigint demo's exponent does, the field carries aria-label instead.
  • A field's own label is bold, at the text's own size, so a reader scanning the page tells the controls from the prose around them. It stays a label, not a heading. A field that reads better inside a sentence, as the bigint demo's exponent does, is part of that sentence and stays regular with it.
  • The control fits what the reader types. Text that can span lines — a program, a document, a release file — is a textarea, and a demo that is text in, text out uses textDemo, which owns the textarea, the examples drop-down and their wiring. One short value — a message to hash, a key, a number — is a single-line <input type="text">, as the SHA-2, bigint and versions demos use. A choice from a fixed set is a <select>, as SHA-2's algorithm is. For two short alternatives, inline radio buttons keep both choices visible, as secp's graph target and geometric branches do.
  • A field that holds code is highlighted: textDemo takes highlight, the function that draws its text as nodes, as the compiler demos pass highlight. The textarea is laid over a coloured copy of its text, with its own text transparent, so the reader types into the colours and the caret, selection and undo stay the browser's. The runtime keeps the copy where the textarea is scrolled, and makes each keystroke's edit in the copy at once, in the one text node it falls in (mirror), so that the colours around it do not blink while the render that colours the edit is still to come. A text the tokenizer refuses stays plain.
  • Every independent field or radio group has a unique name. Radios within a group share that name and each option has a unique id. The runtime finds a field by its name to keep the reader's focus and the field's size across a render, a textarea, an input and a select alike, so two fields sharing one would lose them.
  • Typing updates the output at once. A button is for an action typing must not trigger on its own: starting slow work (Measure, Run the example), committing a change, so a half-typed value never becomes one (the versions demos' Insert and Remove), or a tool such as the copy button on a code block. Its text is a verb that says what it does.

The initial state shows the point

The input the page opens on demonstrates what the demo exists for, so the reader sees it before changing anything. Where one input can show every property, it does: the JSON demo opens on keys out of order and pretty-printed, so both are visibly undone; the UTF-8 demo opens on one character of each byte length. Where it cannot — properties that need inputs of their own, such as a refused input beside an accepted one — the demo offers examples, and the one it opens on is an overview: the central property the lead names, with as many of the others as one input holds. The DataJS and EDAG demos name that first example Overview. An empty initial input is right only when empty is itself the point.

Examples

option(value, text, selected), selectField({ name, label }, options) and button(name, label, disabled?) are exported by the shared demo module. A select accepts both options and option groups. Its label and ID use the local name; the control-ID boundary scopes their references when rendered. The shared stylesheet caps a select's border box at its containing column, so long options fit on phones while short selects keep their natural width. The menu and accessible label remain native. Buttons use type="button" and keep their semantic event names.

The implementations live in controls/, below both the examples picker and the shared demo module, so reusing them does not introduce an import cycle. The picker is .f.mjs because the compiler currently refuses the name property read in selectField's options destructuring.

Offer an examples drop-down when the module has several behaviours one input cannot show — the compiler's language features, DataJS's kinds of sharing. Use examples, through textDemo's examples option where the state is a text.

  • The first example is the initial state.
  • An example's name says what the input is (Equal is not shared, Diamond, An import), never Example 1, and never what a demo does with it. The verdict is the output's to show, in the refusal box where there is one: one input can be accepted by one demo and refused by another — the parser takes an import the Rust page cannot link — and a name carrying a verdict would be wrong on one of them. compiler/examples makes the same call for the same reason.

Stable example IDs

Every example has a nonempty, unique ID within its demo, separate from its label and dropdown position. For shared text examples the tuple is [label, source, id]:

['Hex escape', 'export default "\\x41";', 'hex-escape']

Write IDs explicitly; short lowercase names with hyphens are recommended. Keep an ID when renaming or reordering an example. Never reuse a retired ID for a different example. An ID identifies the same example across compiler stages because those pages import the same specification-owned list.

examplePicker keeps dropdown values and pick(value) based on labels. sourceOf(id) looks up a stable ID, and idOf(text) matches the complete source. Unknown or retired IDs and custom source return null; an empty source remains a valid example. Consumers must report an unknown ID rather than selecting a different example or silently falling back to the first. Options expose data-example-id; the disabled Custom entry has no ID.

Tree presets append an ID as their fifth tuple entry, [label, keys, key, hint, id]. RTTI lessons declare an id beside their name. Both use the same nonempty, unique-ID check and expose the same option metadata. Labels and the events demos receive stay unchanged.

Stable IDs are used by shareable demo inputs. The plain chain beside Demo copies the section link without input parameters. The chain labeled Copy with inputs copies #demo for defaults, a matching example query or the complete custom input set as individual query parameters. Every new demo with editable inputs must declare the sharing contract; textDemo does this automatically. A demo without editable inputs may omit it and copies #demo. Restoration validates the entire set and constructs one state, preserving explicit Run actions. See navigation and startup edits.

Output

  • A caption names each result before it: a p ending in a colon, saying what the result is and, where it matters, its notation — SHA-256, hex:, Code points, UTF-8 hex:, Parsed, then written back:. A demo whose only output is self-evident from the lead may omit it.
  • A caption is marked data-caption, which sets it bold at the text's own size, as a label is, so a reader tells each result's name from the result and from the lead. It stays a p, not a heading: the demo's only headings are the h3s over parallel parts. Everything else around the output — the lead, explanatory notes, a progress summary, a disclosure's title — stays regular, so the bold marks only what names a control or a result.
  • A text result is a code block: a pre in an element marked data-code, the bordered, neutral box the SHA-2 demo draws its digest in. A result a reader will paste somewhere else — a digest, a command to run — is marked data-code-block too and carries a copy button, as both of the SHA-2 demo's blocks do (codeBlock in crypto/sha2/demo.f.mjs).
  • A tint is a verdict, and only a verdict. Where the demo's point is whether a reader accepts the input, the box is marked data-result="ok" (green) or data-result="error" (red) instead, as the rtti demo's parse and validate answers are (answerView in rtti/demo.f.mjs). A result that is simply what the module computed — a digest, an AST, a generated module — stays neutral: green there would claim a check nobody made. A verdict box that holds code is marked data-code as well: it keeps the tint and border, gains an edge in the verdict's colour, and its text takes the text colour, so the token colours read as code and green is not both "accepted" and "string". Its caption's verdict word is marked data-verdict and carries the colour, as the rtti demo's answers do.
  • Generated code is coloured by what its producer wrote. A result the repository's own writers produce — JSON, DataJS, the EDAG, a FunctionalScript module, Rust — is marked text, and the demo draws it with render, which wraps each run that has a kind in a span marked data-token and leaves the text itself unchanged. The demo does not read the text again to find the words. Only a text with no producer behind it, such as an example a reader typed, goes through highlight, which tokenizes it; a text the tokenizer refuses stays plain. A demo whose output is one producer's answer — the marked text or the producer's refusal — ends in codeOutput beside render, which draws the caption and the copyable block, or the refusal, from the producer's Result. What a kind is, how a producer marks one and how it is proved: ../../text/marked/README.md.
  • How each marker looks is the stylesheet's, with its reasons, in ../style/module.f.js; this file names the markers, not the colours.
  • Values are written the way the reader's own tool prints them, so they can be compared character for character: lower-case hex padded to its full width, bytes separated by spaces.
  • An empty result is said in words, never an empty box: (empty), or a sentence where one reads better (The trie is empty.).

Refusals

When the module refuses the input, the demo shows the module's message in place of the output, in a pre marked data-result="error": the box a result sits in, tinted red, so the verdict reads before the message does. A refusal is always a verdict, so it is always tinted, whether the demo's results are. The caption says it in words as well, because a colour alone is not a verdict: Refused: where the result's caption would be. Where a demo shows several readers' answers side by side, each caption carries its verdict, as the rtti demo's parse · ok and validate · error do.

  • Refused, not Error. The module did what it is designed to do with input it cannot handle (DESIGN.md §10), and the word says so. Error is kept for a defect: the runtime reports a throw from update or view itself, apart from the demo.
  • The message is the module's own, unedited, so what the page says is what the API answers.
  • A failure that belongs to one item of a valid result stays on that item's line, as the UTF-8 demo marks one unpaired surrogate among valid code points: the result as a whole was not refused.

VDF evaluates and verifies in separate sections. A refusal replaces only the output that its invalid input prevents: oversized text and invalid steps can therefore show separate live refusal boxes. The controls remain available so readers can correct each input, and verification verdicts keep their own result presentation.

What a demo's proof covers

Besides the 100% coverage every module owes, a demo's proof drives its view for the initial state, for each example, and — where the module can refuse an input — for one refused input, so a change that breaks any of them is caught where it is made rather than on the page. A module that answers every input has no refusal to drive: the tokenizer reads text it cannot tokenize as an error token among the others, a line of its output, not a refusal.

Interactive SVG controls

An SVG control can send a click event through data-demo-click, with role, tabindex, and a visible/textual equivalent supplied by its demo. Enter and Space activate it. A horizontally draggable SVG handle names a corresponding native range input in data-demo-drag; its SVG supplies data-demo-x-min, data-demo-x-max, data-demo-x-left, and data-demo-x-right. The runtime transforms screen coordinates into SVG coordinates, clamps to the range input, and sends an ordinary input event. The demo constrains that value to its model. Keep the native input available for keyboard and assistive-technology users.

Demo

Choose a greeting to change the result. Reset restores Hello and is disabled while Hello is selected.

Greeting:

Hello!

Emergent Testing

Idle. Press Run to start the suite.

  • ./bits/proof.f.mjs
  • ./code/proof.f.mjs
  • ./control_ids/proof.f.mjs
  • ./controls/proof.f.mjs
  • ./examples/proof.f.mjs
  • ./graph/proof.f.mjs
  • ./hash/proof.f.mjs
  • ./highlight/proof.f.mjs
  • ./mirror/proof.f.mjs
  • ./proof.f.mjs
  • ./railroad/proof.f.mjs
  • ./secp/proof.f.mjs
  • ./sharing/proof.f.mjs
  • ./versions/proof.f.mjs