Repository navigation
Releases
MESH v0.18.0
MESH v0.18.0 release notes
Prepared 2026-10-30 (not yet published: v0.17.0 is the latest release).
v0.18 gives the units API one word per concept, and adds the context channel (direction decision C-1, K27). MPRX already used "instance" for an element of a component and for a repeat's key; the runtime used it again for one occurrence of a component with a unit. The prose already said "occurrence". Units are Provisional, so the cheapest time to rename is before the first outside dependent (direction decision C-4).
Highlights
- The rename.
Render.instancesisRender.occurrences;intent.instanceisintent.occurrence; a listed unit'sinstanceisoccurrence; aunitsentry's"instance"key is"occurrence"; the diagnosticruntime-unknown-instanceisruntime-unknown-occurrence. In Rust:Render::occurrences,occurrences_to_json,Occurrence,Intent::occurrence(),RuntimeCode::UNKNOWN_OCCURRENCE. The WebAssembly results,render-v1's intent, the specification, the manuals and the conformance vectors (units/step-N.expected.occurrences.json) follow. - "Instance" keeps its MPRX meanings (an element of a component; a repeat's key, §9.10). Nothing about the language changes.
- The old names work for one release, deprecated, and are removed in the next minor. See below.
- The diagnostic
runtime-unknown-instanceis retired, never reused. A host that matched it must matchruntime-unknown-occurrence. - The screen (
openScreen) and the argument names on the intent, both Unreleased on the branch, ship in this release; the screen already saidoccurrence. - The context channel. A value many distant components need (a theme, a locale, the current user) reaches them without being threaded through every layer, declared and type-checked. See below.
The context channel
"context": { "theme": { "kind": "enum", "values": ["light", "dark"] } },
"components": { "badge": { "props": {}, "events": {}, "commands": {}, "scope": {}, "uses": ["theme"] }, ... }- Declared once, used by name. The manifest's top-level
contextdeclares each name and its type. A component lists what it reads inuses; those names join its scope read-only, andmesh checktype-checks every read before anything runs. A component that doesn't list a name can't read it: no inheritance, no lookup.manifest-unknown-contextis a name the context doesn't declare;manifest-context-clashis one that is also a prop or scope name, or listed twice. - Given beside the snapshot.
render({ program, model, snapshot, context });update(render, snapshot, units, context)(withoutcontext, the render's own); in Rustrender_contextandupdate_with_context. A value that doesn't fit is reported at$context.<name>. Dispatch evaluates against the context the render had. - Changed by changes. An edit whose path starts
{ "context": name }edits that value:{ "op": "set", "path": [{ "context": "theme" }], "value": "dark" }. Every node that reads it is evaluated again (in this release the whole tree is rendered again on a context change; the patches are still the difference). - Where it is read. A snippet, and the content a slot places, read the context of the template that declared them. Commands are not context. A subtree override (
{#with}) and per-subtree providers are left for later. - Use props first. Context is for values many distant components need; a value one or two levels down is a prop.
It is Tier 2 and Provisional. The specification gains §9.11, and §9.1 reads "no implicit names: a context name is in scope only where the manifest says the component uses it". The screen takes it too: screen.show(snapshot, context?), which keeps the values last shown when given none.
Migrating
| Was | Is |
|---|---|
render.instances / Render::instances() |
render.occurrences / Render::occurrences() |
each.instance (a listed unit), intent.instance |
each.occurrence, intent.occurrence (Rust: intent.occurrence()) |
[ { "instance": key, "state": ... } ] (Rust Units::from_json) |
"occurrence" (the old key is still read; both in one entry is refused) |
mesh_runtime::Instance, instances_to_json() |
Occurrence, occurrences_to_json() |
runtime-unknown-instance |
runtime-unknown-occurrence |
What the runtime writes (the JSON of an intent or of the occurrences, the module's result documents) has the new names only. The JavaScript aliases are not enumerable, so deep comparisons and JSON.stringify see the new shape. In Rust, Intent is Stable and its fields are public, so the instance field stays under its old name, #[deprecated], and Intent::occurrence() reads it: renaming it or adding a second field would break code that builds an Intent. A Rust host that builds Intent { .. } keeps compiling (with a deprecation warning for the field). The field is renamed in the release that removes the aliases.
Compatibility
A program, a manifest, a snapshot and every other document are unchanged; context and uses are new and optional. A manifest that has either is refused by an older MESH (manifest-unknown-property). render-v1, the patches and the intent are unchanged by the context channel. In Rust the new entry points sit beside the old ones, which are the same operations with no context, and Component and Declaration (both #[non_exhaustive]) gain uses and Context. A host that reads render-v1 intents or the module's result documents by the old property names must read the new ones. The module and the package still have to be the same version.
North-star scorecard
M2 (one term per concept): the "instance" collision is removed; M2 stays ◐ while the other collisions remain. Tier 2 (Provisional).
C1 (a value reaches a deep component without threading): ✗ → ✓, the context channel. C2 (the channel is explicit and checkable): not applicable → ✓, declared in the manifest and type-checked by mesh check ahead of run. Tier 2 (Provisional). No audit regresses.
Verification
cargo test --workspace; the runtime package's tests (a new test for each of the old names in both runtimes; the context channel through the package); the conformance vectors for units under their new names, and context/ in both runtimes. Not run in this environment: the runtime package's size test (needs binaryen) and the browser tests.
MESH v0.17.0
MESH v0.17.0 release notes
Prepared 2026-10-24 (not yet published: v0.16.0 is the latest release).
v0.17 lets a host check generated UI while it streams. A model writes MPRX a few tokens at a time; a batch check of half a file reports a syntax error and nothing else, so a wrong prop is invisible until the file ends. A stream session checks what has arrived.
Highlights
openSession(@valancex/mesh-compiler) andmesh_compiler::stream.push(chunk)answers with the diagnostics that are new, the ones standing, what is open, and, while no error stands, a checked preview (atemplate-v1) to draw as a fresh render.close()is exactlycheckof the whole text. See the streaming manual.- A repair that fails closed. The text is cut back to its last complete token and what is open is closed, by the module. Nothing is kept that was cut inside a start tag, an attribute string, an expression or a block header, an open
{#each}is withheld until it closes, and a text run grows. A planted mistake is heard within 20 bytes of where it starts (8 of 8 cases), and every prefix of every valid page checks clean. - A budget. 50 ms of checker time per push, then the session ends with
check-budget-exceeded. The input limits of 0.13 hold in a session. - Command annotations. A command in the manifest may say
"effect": "read" | "write" | "destructive"and a"title". A host builds a consent prompt from them, never from text a model wrote. They are annotations: nothing about a check, a template or a fingerprint changes. generated/in@valancex/mesh-conformance: six pages, eight planted mistakes and the digests of every prefix's step; both runtimes give the same digests.
Compatibility
check, compile, checkProgram, compileProgram, render, update, dispatch and every document are unchanged. The manifest accepts two more optional command properties; a manifest without them is read exactly as before. The WebAssembly compiler module gains one export, mesh_stream; the module and the package still have to be the same version.
Release order
MESH 0.17.0 (its nine packages); then VALANCE, whose @valancex/valance/generate builds the profile, the prompt, the correction loop and the consent prompt on the session.
Verification
cargo test --workspace (the law over 100,000 random prefixes and mutations, the prefixes of every page, the planted mistakes, the digests), cargo clippy --all-targets -- -D warnings, cargo fmt --check, RUSTDOCFLAGS="-D warnings" cargo doc, the grammar's parser regenerated with no difference, number_to_text.py --check; the compiler package's tests (the same law and digests through WebAssembly, the budget, the surrogate pair, the limits), the runtime package's (every preview of every page rendered and compared with the final tree, in three states of the data) and the conformance package's. Shown failing: with the withholding of an open {#each} removed, the native digest test, the repair test and the runtime's preview test fail; with a required-prop report added to the early check of a start tag, the false-positive test fails.
MESH v0.16.0
MESH v0.16.0 release notes
Prepared 2026-10-24 (not yet published: v0.15.0 is the latest release).
v0.16 adds no runtime behavior. It publishes what a renderer, a host and a debugger need to check replay: that a recorded session gives the same trees, byte for byte, in every MESH runtime and by every route to them.
Highlights
replay/in@valancex/mesh-conformance. The session-log format (replay/README.md), seven recorded logs and 1,000 random sessions. The format is line-JSON: aheader(the program's and the manifest's hashes, the initial snapshot),changes,event(what a PORT reported, the render it was drawn against, and the intent or diagnostic code the runtime returned),tree(a SHA-256 of the compact JSON, and the whole tree at every 50th line and at the end) and an informationalresult. Render versions are never recorded: they are process-local.- Three routes, both runtimes. A conforming player replays a log by an
update_changeschain, anupdatechain over whole snapshots and arenderof each snapshot, and requires the same hashes from each.cargo test -p mesh-runtime --test replay(the native runtime, which also records:MESH_BLESS=1) andpackages/mesh-runtime/test/replay.test.mjs(the WebAssembly package) both do; the 1,000 random sessions give the same digests in both.
Compatibility
Nothing changes in the compiler, the runtime, the manifest, render-v1, render-patch-v1 or template-v1/v2. @valancex/mesh-conformance gains files (conformance/replay/…); a consumer that reads the other directories is unaffected.
Release order
MESH 0.16.0 (its nine packages); then VALANCE, whose @valancex/conformance runs the logs (vlx replay) and records its own.
Verification
cargo test --workspace, cargo clippy --all-targets -- -D warnings, cargo fmt --check, RUSTDOCFLAGS="-D warnings" cargo doc, the grammar's parser regenerated with no difference, number_to_text.py --check; the runtime package's tests (including the replay test and the browser test with MESH_CHROMIUM), the compiler's and the conformance package's. The wrong-hash check of each player fails a log whose tree hash was changed.
MESH v0.15.0
MESH v0.15.0 release notes
Prepared 2026-10-23 (not yet published: v0.14.0 is the latest release).
v0.15 gives a component state of its own for one occurrence of it: whether a disclosure is open, which tab is chosen. A manifest, a program and a snapshot that use none of it compile and render as they did in 0.14.0: the same templates, the same render trees byte for byte, the same patches.
Highlights
- A component's
unit."unit": { "state": { "open": { "type": …, "default": false } }, "commands": { "toggle": { "parameters": [] } } }in a component's manifest entry. The state names join the component's scope (read-only) and the commands join its commands, so a template readsopenand runstoggle()like anything else and checking needs nothing new. A name that is already taken ismanifest-unit-clash. See the manifest reference. - The state is an input beside the snapshot.
render,update,updateChangesanddispatchtake the state the host holds for the occurrences it has changed (render_units,update_with_units,update_changes_units,dispatch_from_unitsin Rust;unitsin JavaScript). An occurrence with no entry has the defaults, which is also what the first render on a server and in a browser use, so they agree. The runtime holds none of it, so a render is still a function of its inputs and the equivalence law ofupdateholds with units in it. See Units. - A render lists the occurrences it met (
render.instances: the key, the component, the state the template saw, the fields the caller owned), so a host can keep the state of the occurrences that exist and let go of the rest. An occurrence's key is its place in the program, by the item's declared key inside a repeat: a re-sorted list keeps the state with the row; hiding and showing, or a changed key, is a new occurrence. - A unit's command names its occurrence. The intent gains
instanceand, when the caller owns a field,controlled. The host runs the unit's transition on the occurrence's state and renders again. controllablefields. A prop of the same name, when the caller writes it, is the value, and the unit's change to it is not applied: one copy of the value, owned by whoever wants it.raise(render, instance, event, payload)resolves an event a unit announces to the handler its caller bound to it.- An update does not keep a part that a unit under it has changed. The state of the units below a part is one of its inputs.
- New diagnostics:
manifest-unit-clash,runtime-unknown-instance,runtime-unknown-event. - Conformance vectors:
units/, with a committed tree and list of occurrences for each step, run natively and through the package.
Compatibility
- API: additive. The Rust
Intentgains two fields, andComponentgainsunit(both were open to growth:Componentisnon_exhaustive). - Manifest: a manifest with a
unitis refused by an older MESH (manifest-unknown-property); one without is read as before.manifest-v1still means what it meant. - render-v1, render-patch-v1, template-v1/v2: unchanged.
- WebAssembly: the module's exports are internal and changed (units in, occurrences out,
mesh_raise);@valancex/mesh-runtimeand its module are one version, as always. - Size: the module grows about 3.5 percent (gzipped 234,818 to about 243,000 bytes).
Release order
MESH 0.15.0 (its nine packages), NEXUS 0.14.0, PORT unchanged, then the VALANCE packages, which depend on MESH 0.15 and NEXUS 0.14 for units.
Verification
cargo test --workspace, cargo clippy --all-targets -- -D warnings, cargo fmt --check; the runtime package's tests (including the browser test with MESH_CHROMIUM), the compiler package's tests, and the conformance package's. Each new rule was shown failing against a deliberate wrong value: the update that keeps a part when a unit under it changed (the test fails when the check is removed), a reorder (the committed vectors), a clash of names (the fixture), and a state the manifest forbids (the diagnostic).
MESH v0.14.0
MESH v0.14.0 release notes
Prepared 2026-10-18 (not yet published: v0.13.0 is the latest release).
v0.14 gives MPRX its first new types and its first new template construct since 0.10. A program, a manifest and a template that use none of it compile and render as they did in 0.13.0: the same template, fingerprint, render tree and keys (a test installs 0.13.0 from the registry and compares).
Highlights
- Enum types.
{ "kind": "enum", "values": [...] }: a string that is one of a closed set. A literal that is not a member isunknown-variantwith a did-you-mean; the editor completes the members inside the quotes of a prop value and beside==and!=. has(x)andx ?? y, a presence test and a default; defaults for props (defaultin the manifest). They make a templatetemplate-v2; the compiler writes v2 only for a template that uses them, and a v1 document that contains them is malformed.- Snippets.
{#snippet name(a, b)} … {/snippet}and{@render name(args)}: a piece of template with parameters, declared in a template and passed to a component (a prop of kindsnippet, withchildrenas the content between the tags), or kept local and rendered, recursively if it likes (64 levels). A snippet reads the scope it was declared in. A local snippet can be passed along as a prop value (row={fmt}). An optional snippet must be rendered inside{#if has(name)}(possibly-absent-snippet). - A composite that only forwards. A template that is only
{@render children()}makes no node of its own. - Language server: completion of snippet names, snippet parameters and enum members; hover and go to definition for snippets; signature help in a render's arguments.
- Smaller compiler. The WebAssembly is optimized after linking: 320,778 gzipped bytes become 301,420.
Compatibility
@valancex/mesh-compilerand@valancex/mesh-runtime0.13.0 read a manifest that uses a new kind asmanifest-unknown-kind, and atemplate-v2document asassembly-unsupported-format-version: nothing older misreads them.- Identity: a render site adds the step
0x08, a forwarder the fragment step0x07before it. A program that has neither keeps its keys. - New diagnostics:
unknown-variant(with enums),unknown-snippet,snippet-arity-mismatch,duplicate-snippet,children-conflict,possibly-absent-snippet, the manifest codesmanifest-snippet-misplacedandmanifest-children-not-snippet, and the runtime codesassembly-snippet-to-primitive,assembly-snippet-root-scope,runtime-snippet-depth. See the diagnostics manual.
Verification
cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --check; the package tests (compiler, runtime, conformance, language server, editor grammar) including those that run Chromium; conformance vectors enums/, snippets/, forwarder/ and passing/ in Rust and JavaScript; and the replay hashes of evidence/language/replay.mjs, which are the same on 0.13.0 and 0.14.0.
MESH v0.13.0
MESH v0.13.0 release notes
Prepared 2026-10-17 (not yet released: v0.12.0 is the latest release).
v0.13 fixes how MPRX reads a space between two interpolations. <text>{first} {last}</text> rendered AdaLovelace: the space is a whitespace-only text child, and whitespace-only text is formatting (spec §3) and is dropped. A Valance application found it writing #{r.id} {r.customer} and worked around it with a separator character. A change in rendered text, so a minor version; nothing else changes.
Highlights
- A space on the line is content. A whitespace-only text child that has no line break and an interpolation immediately before and after it is kept verbatim, as part of the text run.
{first} {last}reads "Ada Lovelace";{a} {b}(two spaces) keeps both;#{id} {name} !is one run. - Layout is still layout. A line break is formatting wherever it is:
{a}and{b}on separate lines readab, as before, so a person or a formatter that re-wraps a template between two interpolations changes nothing it renders. Whitespace next to an element, a block, a slot or the start or end of the content is dropped as before (<b>x</b> <i>y</i>still has no space between the two elements; nothing asks for that yet, and a stray text child is not harmless on every target). {" "}still works where a space must sit at a line break or beside an element.
The rule is the one JSX and Vue (condensed) use: whitespace with a line break is layout, whitespace without one is content, narrowed here to the case the spec's own model of a text run already implied (§9.7: a run is literal text and interpolations concatenated).
Upgrading
Move @valancex/mesh-runtime, @valancex/mesh-compiler and @valancex/mesh-lsp to ^0.13.0 together (before 1.0 a minor is a new range). Search your templates for a space between two interpolations: {a} {b} on one line now renders its space. A template with none renders exactly what it did, with the same keys and handler identifiers; templates, manifests and programs compiled with 0.12 mean the same under 0.13 apart from this.
Compatibility
- Rendered text. The one change, above. The semantic model keeps a whitespace-only text child it used to drop (spec §3), so a template compiled with 0.13 has a text child in that place and one compiled with 0.12 has none; both are valid
mesh-templatedocuments and run on either runtime. - Rust and npm. No API changes. All packages move to 0.13.0 together.
- Tooling.
mesh-formatand the language server leave the space alone (it is content); the grammar does not change. - No diagnostics change.
Stability
The rule (a space with no line break between two interpolations is content; a line break is formatting; neighbours other than interpolations are unchanged) is stable on release. Whether whitespace between two inline elements should be content too is open, and would be a later minor with its own note.
Verification
cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --check, cargo doc with warnings as errors; five new runtime tests (mesh-runtime/tests/whitespace.rs: three fail without the change) and one formatter test; the design note is docs/superpowers/specs/2026-10-16-mprx-whitespace-between-interpolations.md.
MESH v0.12.0
MESH v0.12.0 release notes
Prepared 2026-10-13 (not yet released: v0.11.0 is the latest release).
v0.12 teaches the editor to format and indent MPRX. mesh-lsp answers Format Document, Format Selection and the indentation of the new line after Enter, so an .mprx file behaves in an editor the way HTML, XML or YAML does: the editor understands its scope. Nothing in the language, the compiler, the runtime or any document format changes; an application that changes nothing behaves as before.
Highlights
-
A formatter:
mesh-format. A new crate that changes only whitespace between tokens. Whitespace-only text between tags,{expressions}and block markers is formatting (spec §3), and is re-indented: one level per element and per{#if}/{#each}, with{:else},{:else if}and the closers one level out. A tag already written over several lines gets one attribute per line, with its>or/>on a line of its own; a tag on one line stays on one line. A run of blank lines becomes one, and the text ends with one newline. Text with a character other than whitespace in it, and everything inside{…}, is kept exactly as written.<list> <list> {#if ready} {#if ready} <a /> becomes <a /> {:else} {:else} <b/> <b /> {/if} {/if} </list> </list>
-
It checks its own work. The tokens of the result must be exactly those of the source, or the formatter returns an error and the source stands. A source with syntax errors is not formatted (the structure its indentation follows is not known).
-
Three LSP requests.
textDocument/formatting(Format Document),rangeFormatting(Format Selection: the edits of the whole formatting that touch the selection) andonTypeFormatting(trigger character: a newline; the new line is indented by the scope it is in, and this works while the file does not parse yet). They need no model and no check, and are never held. Indentation follows the editor'stabSizeandinsertSpaces. -
Indentation rules for editors that read a language configuration.
editors/vscode/language-configuration.jsoncarries indent and outdent patterns and Enter rules (between a tag and its closer, between a block opener and its closer); the VS Code language configuration turns oneditor.autoIndentandeditor.formatOnTypefor MPRX. Other editors use the server.
Upgrading
Nothing has to change. Move @valancex/mesh-runtime, @valancex/mesh-compiler and @valancex/mesh-lsp to ^0.12.0 together (before 1.0 a minor is a new range) to get formatting; templates, manifests and programs compiled with 0.11 mean the same under 0.12.
To format from an editor that is not VS Code, ask the server: textDocument/formatting with tabSize and insertSpaces in options (manual).
Compatibility
- Additive. Three new LSP capabilities (
documentFormattingProvider,documentRangeFormattingProvider,documentOnTypeFormattingProvider) ininitialize's result. A client that does not use them is unaffected. - Rust. A new workspace crate,
mesh-format(format,format_edits,format_range_edits,indent_for_line,Options,Edit,Error);mesh-lspdepends on it. No existing crate's API changes. - npm. All packages move to 0.12.0 together;
compileProgram,renderand the other entries are unchanged. - No diagnostics change.
Stability
mesh-format's guarantee (only whitespace between tokens changes) and the three requests are stable on release. What the layout looks like (one attribute per line in a multi-line tag; blank lines collapsed; text with content never re-indented) can be refined in a later minor, always within that guarantee, and the changelog will say so.
Verification
cargo test --workspace (934 tests), cargo clippy --workspace --all-targets, cargo fmt --check, cargo doc with warnings as errors; mesh-format's 18 tests (layout, tabs, idempotence, selection, every valid example and fixture formats and stays formatted, indentation on Enter); the protocol tests for the three requests; the editor's indentation-rule tests.
MESH v0.11.0
MESH v0.11.0 release notes
Prepared 2026-10-13 (not yet released: v0.10.0 is the latest release).
v0.11 makes control flow part of the language. Conditional and repeated structure are written as blocks in an element's content, not as reserved elements, so a tool that lists a model's components has nothing to offer for control flow and a model declares nothing for it.
Highlights
-
Blocks.
{#if condition}…{:else if condition}…{:else}…{/if}, and{#each items as item (key)}…{/each}. A chain of{:else if}branches replaces the multi-way choice. The key of a repeat is mandatory.<list> {#if loading} <spinner /> {:else if failed} <note>Could not load</note> {:else} {#each users as user (user.id)} <user-card user={user} /> {/each} {/if} </list>
-
A branch or body is one element. If it is exactly one element it is that element; anything else (text, several nodes, none, a lone block or slot) is wrapped in a
mesh-fragmentfor you. -
Nothing to declare. The constructs a block lowers to (
mesh-if,mesh-each,mesh-fragment) are built in. A manifest need not declare them, one that does is used as declared, and the model fingerprint covers only what is declared. -
Same templates. The runtime,
template-v1,render-v1, node identities and handler identifiers do not change. A program written with the old elements compiles to the same templates, apart from source spans. -
Better messages. A mistake in a block names the block ("the condition of
{#if}"). A repeat without a key ismissing-repeat-key. Syntax errors in blocks say what is wrong and where: never closed, no open block, malformed opener. -
Editors. The Tree-sitter grammar and highlight queries, the TextMate grammar, completion after the openers, and hover and go-to-definition on a repeat's binding.
Upgrading
-
Move
@valancex/mesh-runtimeand@valancex/mesh-compiler(andmesh-lsp) to^0.11.0together. Before 1.0 a minor is a new range. -
Rewrite control flow as blocks.
retired-elementtells you which block to write.Before After <mesh-if when={a}>X</mesh-if>{#if a}X{/if}<mesh-if when={a}>X Y</mesh-if>(two alternatives){#if a}X{:else}Y{/if}<mesh-each items={xs} as="x" key={x.id}>X</mesh-each>{#each xs as x (x.id)}X{/each}<mesh-switch><mesh-case when={a}>X</mesh-case><mesh-default>Y</mesh-default></mesh-switch>{#if a}X{:else}Y{/if}A two-alternative
mesh-ifand an{#if}/{:else}are both conditional sites, but an{:else}is written as a second conditional (as amesh-switchwas), so node identities under the else branch differ from the second alternative of the old element. -
A model that declared
mesh-if,mesh-each,mesh-switch,mesh-caseormesh-defaultcan drop them; one that keeps them is used as declared.
Compatibility
- Breaking, before 1.0. The reserved elements are errors. Templates already compiled keep their meaning: stored renders and handler identifiers are unchanged.
- Tooling. Tag completion and component listing offer nothing for a block.
Target::Bindingis a new resolution target (a RustmatchonTargetneeds an arm). - Diagnostics. New codes
retired-elementandmissing-repeat-key.invalid-switchis no longer reported and stays in the catalogue. - npm. All packages move to 0.11.0 together.
Stability
Blocks, retired-element and missing-repeat-key are stable on release. The runtime's behavior for the constructs a template names (mesh-if, mesh-each, mesh-fragment and their identity steps) stays provisional.
MESH v0.10.0
MESH v0.10.0 release notes
Prepared 2026-10-10 (not yet released: v0.9.0 is the latest release).
v0.10 makes an application cheap to update and cheap to compose. The runtime turns a new snapshot, or just the edits to the last one, into patches that touch only what changed; composites take children, named slots and events; and a program's composites no longer need a hand-written contract. The public APIs were reviewed against the API design principles before this release; the review record is docs/superpowers/specs/2026-10-09-api-design-review.md.
Highlights
- Updates.
update(render, snapshot)andupdateChanges(render, { base, changes })return the new render andrender-patch-v1patches (setProp,removeProp,setText,insert,remove,move,replace). A node whose inputs are unchanged is the same node, and the work is proportional to the change: on a keyed list of 10,000 items with one changed, about 22 ms from JavaScript through changes, against 460 ms for a full render.diff(previous, next)computes the edits between two snapshots.render.version,render.release()and[Symbol.dispose]manage what the module keeps;render()keeps nothing. - Composition. A composite's template places an occurrence's children with
mesh-slot, and, by name, themesh-fills of an occurrence with<mesh-slot name>. A composite declares events and forwards them by naming one as a handler. Identity extends without changing any existing key. - No contract to write.
compilePrograminfers the manifest entry of every listed component the manifest does not declare: props from the arguments its occurrences pass, events from the handlers it forwards. The reserved tags a program uses are declared for it. A declared entry is used as written. mesh-fragment. Content without a node: a composite, a conditional alternative, a switch case or a repeat item can be text or several nodes by writing a fragment, and MESH wraps nothing on the author's behalf. Provisional. A fragment cannot start the program's root (assembly-root-fragment).- Maximal text runs. Text on either side of a conditional or a repeat that chose nothing is now one run, as a render tree's runs are meant to be, so a server's single text node can be matched by the client.
mesh-switch. Cases with a booleanwhen, and a last default; the first that holds is rendered. The compiler writes it as themesh-ifs it stands for, sotemplate-v1and the runtime are unchanged. Provisional, likemesh-if.- Errors you can match on.
MeshInternalError,MeshVersionErrorand the newMeshUsageErrorcarry a stablecode; runtime diagnostics gain an optionalhint. New diagnostic codes:invalid-switch,runtime-changes-base-mismatch,runtime-invalid-change,runtime-changes-disagree. - Stability tiers. API stability says what each surface promises: Stable, Provisional (
mesh-if,mesh-each,mesh-switch) and Internal.
Compatibility
- Additive for hosts.
render,dispatch,declaredEvents,compile,check,checkProgram,template-v1,render-v1and the manifest format are unchanged, and a program that does not use the new constructs renders exactly as in 0.9.mesh-slotgains an optionalnameprop; a manifest that declares it without one still works for the default slot. - Rust.
Tree::rootandTreeChild::Nodehold anRc<Node>(code that reads a tree is unchanged by auto-deref; code that builds one, which only a render does, is not).update_withtakes the snapshot instead of copying it. - Messages.
assembly-composite-childrennow covers fills as well as children, and its message changed; match on the code. - npm. All packages move to 0.10.0 together. Before 1.0 a minor is a new range: a package that depends on
@valancex/mesh-compileror@valancex/mesh-runtime^0.9.0does not receive 0.10. - WebAssembly. The modules' exports are internal and changed (
mesh_update,mesh_update_changes,mesh_release,mesh_inferand others); a package checks for the exports it needs, so a package is always built with its own module.
Not decided
Recursion in composites, and whether mesh-switch's lowering should become a runtime construct.
MESH v0.9.0
MESH v0.9.0 release notes
Released 2026-10-04 (v0.9.0).
v0.9 gives build tools one operation that turns a program's components into the program's parts. It adds no MPRX syntax, changes no format and changes no runtime behavior: the compiler package gains compileProgram.
Highlights
compileProgram({ model, root, components })in@valancex/mesh-compileris the build-time program assembly boundary. It compiles each listed component ({ component, source, path }) against one manifest (model: { manifest, path }), checks the program they make, and returns a promise of{ components, assembly?, program? }.- The program check is
checkProgram. The assembled parts are given to the existingcheckProgram, so a program is valid forcompileProgramexactly when it is valid formesh check-programand for the runtime's render. There is no second set of checks. A refusal ischeckProgram's own document, inassembly; a component that does not compile iscompile's own diagnostics document, incomponents. - The program is the existing representation.
programis{ model, root, templates }: the manifest's text, the root component, and the templates as text, in the order ofcomponents. It is theProgramInputthatcheckProgramtakes and that the runtime's render takes, and the value a host assembled by hand fromcompilebefore.compileProgramwrites each template out as text itself, so a caller never converts between the objectcompilereturns and the text the program takes. - Present only when nothing failed.
programexists when no component had an error and the program check found no diagnostics. Warnings do not stop it. Every component is compiled even when an earlier one fails, so one run reports every component's errors. - No dependencies, no initialization. The compiler package still depends on nothing: not the runtime, and not VALANCE, NEXUS or PORT.
compileProgramneeds no runtime initialization, ascompileandcheckProgramdo not.
Compatibility
- Additive.
compile,check,checkProgram, the runtime,template-v1,render-v1, the manifest format and model fingerprints are unchanged. The Rust crates and the CLI gain nothing. - A component that is not listed has no template, so it is a primitive, as it always was: nothing in
compileProgramfinds components, so the caller lists those that have a template. - npm:
@valancex/mesh-compilerhascompileProgram()and the typesCompileProgramInput,CompileProgramResultandProgramComponent. All packages move to 0.9.0 together, as before. - Ranges. Before 1.0 a minor version is a new range: a package that depends on
@valancex/mesh-compileror@valancex/mesh-runtime^0.8.0does not receive 0.9. A host that wants one runtime copy needs every package that depends on the runtime to accept 0.9.
Not decided
Whether the CLI should print a program's parts, and whether the runtime package should accept the parts without a caller passing them on.