Language reference

The Mere spec.

Everything the format supports, nothing it doesn't. Version 0.1 — stable.

File structure

A Mere workbook is an HTML document saved with the .mp.html extension. The .mp conveys semantic identity (Mere package); the .html conveys technical format (HTML document). Both are required. The <workbook> element is the root content. Everything lives inside it.

<workbook theme="classic-light">
  <state>   …state declarations…   </state>
  <computed>…computed values…  </computed>
  <actions> …named action blocks… </actions>
  <screen name="home"> …elements… </screen>
  <screen name="detail">  </screen>
</workbook>

The theme attribute is optional (defaults to classic-light). Sections may appear in any order. Only <screen> elements are rendered.

Sigils

Sigils are attribute prefixes that wire elements to state and actions. They replace event handlers, data attributes, and binding frameworks.

Sigil Name Usage Description
@name read <heading @title></heading> One-way read binding. On a scalar value, element content updates when state changes. Also works inline in text: <heading>Hello, @name</heading>. On a list container (<card-list @items>), iterates the array — one child rendered per item. Per-item access via @item.field.
~name two-way <field ~email></field> Bidirectional sync. Input value updates state; state changes update the input.
!name action <button !submit> Invokes a named action on click. Pass arguments with with: !delete with item.id.
? intent <screen ?"Show a summary"> AI compositor annotation. Ignored at runtime; used for generation.

State

Declared in the <state> block. Each value has a name, type, and optional default.

TypeEmpty valueDescription
text""A string value.
number0A numeric value. value= is parsed with Number().
booleanfalseA true/false value. value= is true only for the exact string "true".
list[]An ordered collection of records. value= is parsed as JSON; malformed JSON falls back to an empty list.
map{}A key/value record, read with dotted paths (@selected-message.subject). Parsed as JSON; malformed JSON falls back to an empty map.
record-list[]A list with a declared field schema — <field name= type= default=> children. Enables add-to key validation (MPD-009) and per-field type coercion.

Persistence and travel

A value's modifier decides where it lives — and whether it leaves this machine. The default is transient, so data ships only where an author wrote travel deliberately.

ModifierLeaves the machine?Meaning
(none)NoTransient. Lives for the session and is gone when the workbook closes.
persistNoSaved locally to OPFS (localStorage fallback). Origin-scoped, so it does not travel with the file — "remember this on this device".
travelYes — ships with the fileSerialized into the workbook’s own <value> attributes when a save statement runs. This data IS the document and ships wherever the file is sent.
<value name="draft" type="text" value=""></value>              <!-- transient -->
<value name="theme" type="text" value="light" persist></value>  <!-- this device only -->
<value name="tasks" type="list" value="[]" travel></value>      <!-- ships with the file -->

Persisted data does not travel with the file. OPFS is origin-scoped — the same workbook opened from file://, served from a domain, or forwarded to a colleague sees different persisted state.

travel closes that gap. On save, every travel value is written back into the workbook's own <value> attributes, so the data is the document — readable in a text editor, diffable in git, and present wherever the file is sent. Run mere check --travel to see exactly what would leave the machine before sending it.

Computed values

Declared in <computed>. Derived from state, lazy and memoised, invalidated when a source changes. Read-only — two-way binding to a computed value is an error (MPD-007).

<computed>
  <value name="unread" from="messages" where="read = false" op="count"></value>
  <value name="total" from="expenses" op="sum" field="amount"></value>
  <value name="by-category" from="expenses" op="group-by" field="amount" by="category"></value>
</computed>

op= selects the aggregation. Omitting it with a where= filter yields the filtered list itself. Missing a required attribute is an error (MPD-013).

OperatorSourceRequiresOptionalDescription
addfrom="a,b"a + b, where from="a,b" names two number state values.
subtractfrom="a,b"a − b, where from="a,b" names two number state values.
percentfrom="a,b"a ÷ b × 100, rounded to a whole number. Returns 0 when b is 0.
percent-offrom="a,b"a × b ÷ 100 — b percent of a.
countlistwhereNumber of items remaining after the where filter.
sumlistfieldwhereAdds field across every matching item.
avglistfieldwhere, windowMean of field, rounded to a whole number. window="N" averages only the last N items.
minlistfieldwhere, windowSmallest value of field. window="N" considers only the last N items.
maxlistfieldwhere, windowLargest value of field. window="N" considers only the last N items.
sum-productlistfield, bywhereSum of field × by across matching items — line-item totals in one declaration.
group-bylistfield, byGroups items by the by field, summing field within each group. Returns a list of { key, value } sorted by value descending — feeds <chart from="..." field="value" label="key">.
streaklistfieldby, whereCounts consecutive items, from the most recent backwards, whose field is truthy. by= names a date field to sort by (descending) before counting.

Actions

Named blocks of statements declared in <actions>. Invoked by ! sigil elements.

Action bodies are plain text — one statement per line inside the <action> element.

StatementGrammarDescription
setset <target> to <value> [where <condition>]Assign a value. With where, updates the matching field on every matching record (set habits.done to "true" where id = hid), or selects a matching record into a map value.
clearclear <target>Reset a state value to the empty value for its type.
go-togo-to <screen> [with <key> = <value> ...]Navigate to a screen, optionally passing parameters. Parameters must be declared in the target screen’s takes= (MPD-010).
add-toadd-to <list> <key> <value> [<key> <value> ...]Append a record to a list. Keys are validated against the record-list schema when one is declared (MPD-009).
remove-fromremove-from <list> where <condition>Remove every record in the list matching the condition.
incrementincrement <target> [by <n>]Add to a number state value. Step defaults to 1.
decrementdecrement <target> [by <n>]Subtract from a number state value. Step defaults to 1.
savesaveWrite every travel value back into the workbook file. Explicit by design — a workbook opened read-only from an attachment must not autosave. No effect if nothing is declared travel.
<action name="save-note">
  add-to notes body @draft date "today"
  clear draft
  go-to notes
</action>

<action name="open-message" takes="id">
  set selected-message to messages where id = id
  go-to message-detail
</action>

Use takes to declare parameters. Pass arguments with with: <button !open-message with item.id>.

Element vocabulary

40 semantic elements. No HTML passthrough. Every element has a specific role.

ElementSigilsPassthrough attrsDescription
screen?nameA full screen. Entry point for navigation.
header?Top zone of a screen or card.
footer?Bottom zone of a screen.
form?Structural grouping for inputs. No implicit submit.
toolbar?Flex row wrapper for a search-bar plus inline actions, with padding and gap.
heading@ ?Primary text — title or name.
subtitle@ ?Secondary text — description or metadata.
paragraph@ ?Body text. Supports multiline content.
timestamp@ ?Date/time display. Formatted relative to now.
badge@ ?Numeric or short text indicator. Hidden when value is 0 or empty.
avatar@ ?Circular image or initials. Renders image if value is a URL.
icon?Named icon glyph.
tab-bar~ ?Horizontal tab switcher. Binds to a text state value via ~.
tab?A single tab inside a tab-bar. First positional attr is its value.
navigation-bar?Bottom or top navigation bar. First positional attr is position.
nav-item! ?Navigation action. First positional attr is the target screen name.
message-list@ ?Renders a list of messages from a list state value via @.
card-list@ ?Renders a list of cards from a list state value via @.
list@ ?Generic list. Renders items from a list state value via @.
message-card! ?Tappable message row. Use inside message-list.
card! ?Content container with border and padding.
field~ ?placeholder, type, required, min, max, pattern, autocomplete, nameText input. Binds two-way to state via ~.
button! ?typeAction trigger. Invokes an action via !.
toggle~ ?Boolean switch. Binds two-way to a boolean state via ~.
camera~ ?facing, namePhoto capture. Opens the device camera via the OS picker (no live preview stream). Binds two-way to a map state value via ~ — writes { dataUrl, capturedAt }. facing=user|environment hints front vs back camera.
kv@ ?label, formatKey/value row. label= sets the label, @ binds the value. format=currency|percent for numeric formatting.
chart@ ?type, from, field, label, whereInline SVG chart. type=bar|line|pie. from= binds to a list state, field= is the numeric value, label= is the category label.
modal?Full-screen overlay dialog.
toast?Transient notification. Text content only.
banner?Persistent inline notification strip.
sidebar?Left navigation rail for layout="full". Container for sidebar-brand and sidebar-section.
sidebar-brand?Sidebar header/logo text.
sidebar-section?labelGrouped sidebar nav items under an optional label=.
data-table@ ! ?Table from a list state via @. column children define fields; as=status-badge|name-url|contact|currency|product sets a special cell renderer. Optional ! binds a row-click action.
column?field, label, as, by, editableColumn definition inside data-table or spreadsheet. Declarative only — not rendered directly.
search-bar~ ?Text filter input with a search icon. Binds two-way via ~.
spreadsheet@ ?Editable grid from a list state via @. column children define fields; editable on a column allows inline edits.
metric@ ?formatSingle KPI value with label. format=currency|percent for numeric formatting.
metric-group?Layout container for multiple metric cards.
bar@ ?labelHorizontal progress/comparison bar. label= sets the caption, @ binds a 0-100 value.

Themes

Set with theme="..." on <workbook>. Themes are personalities, not palette swaps — each changes layout rhythm, typography, borders, and density.

classic-light

Neutral baseline. Clean cards, comfortable spacing. The default.

theme="classic-light"

proton-mail

Purple accent, underline tabs, 14px base type.

theme="proton-mail"

brutalist

Zero radius, 3px black borders, inverted header, red accent.

theme="brutalist"

warm-brutalist

Parchment and ink, restrained indigo accent, generous radius — brutalist’s warmer sibling.

theme="warm-brutalist"

Security and trust model

A workbook is an ordinary HTML document containing the Mere runtime. Opening one executes its JavaScript, exactly like any other HTML file.

The vocabulary is not a security boundary. The element registry and the MPD diagnostics constrain what you author with this toolchain. They constrain nothing about a file that arrives by other means: a hostile workbook can embed a modified runtime, or arbitrary script, and will still open in a browser. A .mp.html received from a third party carries exactly the trust model of any HTML attachment from that source — the small vocabulary does not make it safe.

What the runtime does guarantee for the workbooks it renders:

mere validate confirms that a packed workbook still matches the source embedded in it at pack time. That is a tamper-detection check, not an authorship check.

Diagnostic codes

Reported by mere check. All codes are stable — they will not be renumbered.

CodeCategorySeverityDescription
MPD-001structuralerrorWorkbook root element missing, unreadable, or invalid.
MPD-002unknown-elementerrorTag is not in the element registry.
MPD-003unknown-identifiererrorA sigil references state, a computed value, or an action that is not declared.
MPD-004syntaxerrorMalformed sigil — @, ~, or ! with no identifier after it.
MPD-005type-mismatcherror (reserved)Binding to an incompatible state type.
MPD-006structuralerror (reserved)Action invoked with the wrong number of arguments.
MPD-007structuralerrorTwo-way binding (~) targets a computed value, which is read-only.
MPD-008structuralerrorCircular computed value dependency.
MPD-009type-mismatcherroradd-to uses a key that is not declared in the record-list schema.
MPD-010unknown-identifiererrorgo-to passes a parameter the target screen does not declare in takes=.
MPD-011type-mismatcherror<chart from="..."> does not reference a list or record-list state value.
MPD-012type-mismatchwarning<chart field="..."> is not declared in the record-list schema (warning).
MPD-013structuralerrorA computed op is missing a field= or by= attribute it requires.
MPD-014syntaxerrorSelf-closing tag — no Mere tag is an HTML void element, so /> silently nests what follows.
MPD-015unknown-identifiererrortheme= names a theme that does not exist; the runtime would silently fall back to classic-light.
MPD-016structuralerrorA value is declared both persist and travel. They mean opposite things — local-only versus ships-with-the-file — so one of them is a mistake.
MPD-017structuralwarningA save statement runs but no value is declared travel, so saving would write nothing (warning).