/*!
* Statum - client-side state library.
*
* An HTMX-inspired state layer. The library manages signed "snapshots" of
* application state (one per slot type), keeps them fresh via the server's
* `transmit_after` / `invalid_after` timestamps, and binds them to the DOM
* through `stm-*` attributes. See model.md for the full specification.
*
* Distribution: a single IIFE that attaches `window.statum` (no dependencies,
* no build step). Targets modern evergreen browsers (uses fetch,
* BroadcastChannel, queueMicrotask, URLSearchParams, FormData).
*/
(function () {
'use strict';
/** Content-Type used by every Statum directive response. */
var STATUM_CONTENT_TYPE = 'application/vnd.statum+json';
/** Request/response header carrying one slot's reference (or base64 frame). */
var SLOT_HEADER = 'X-Statum-Slot';
/** Request header carrying an action's opaque `private` blob. */
var PRIVATE_HEADER = 'X-Statum-Private';
/** Session-cookie marker used to detect a fresh browser session. */
var SESSION_COOKIE = '_statum_sdc';
/** BroadcastChannel name used to sync session/device state across tabs. */
var CHANNEL_NAME = 'statum';
/** localStorage / sessionStorage key prefix for persisted frames. */
var STORAGE_PREFIX = 'statum:';
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
/**
* Endpoint URLs. These defaults may be overridden per-page with the
* `stm-entrypoint` and `stm-slots` attributes on the `
` element.
*
* @type {{entrypointUrl: string, slotsUrl: string}}
*/
var config = {
entrypointUrl: '/_statum/entrypoint',
slotsUrl: '/_statum/slots',
channelUrl: '/_statum/channel',
// The realtime worker is embedded in the library as a default resource
// (served at /_statum/resource/statum-worker.js); override with stm-worker.
workerUrl: '/_statum/resource/statum-worker.js'
};
// ---------------------------------------------------------------------------
// State store
// ---------------------------------------------------------------------------
/**
* In-memory store, keyed by slot type. Each value is `{frame, snapshot}`
* where `snapshot` is the parsed `frame.content`. This map is the single
* source of truth for rendering; persistent backends merely hydrate it.
*
* @type {Map}
*/
var store = new Map();
/** Listeners, keyed by type name. @type {Map>} */
var listeners = new Map();
/**
* Idempotency tracking: slot key -> last-applied frame `signature`. Held in
* memory only (per tab), so a frame already persisted by another tab still
* re-draws a tab whose tracking lags. Entries are dropped on clear so a
* replayed frame re-applies.
* @type {Map}
*/
var signatures = new Map();
/** Optional UI handlers (set via `statum.on*Handler`). */
var handlers = { onConfirm: null, onNotify: null, onError: null };
/** @type {BroadcastChannel|null} */
var channel = null;
/** Whether {@link init} has already run. */
var initialized = false;
/** Whether the entrypoint has been fetched (guards without pre-entrypoint
* only evaluate after this). */
var entrypointLoaded = false;
/** Set when a guard has triggered a redirect, so further work is skipped. */
var guardRedirected = false;
// ---------------------------------------------------------------------------
// Small utilities
// ---------------------------------------------------------------------------
/**
* Parse an ISO 8601 timestamp to epoch millis, or `undefined`.
* @param {string|undefined} s
* @returns {number|undefined}
*/
function parseTime(s) {
if (!s) return undefined;
var t = new Date(s).getTime();
return isNaN(t) ? undefined : t;
}
/**
* Base64-encode a UTF-8 string (safe for frames containing non-Latin1 data).
* @param {string} str
* @returns {string}
*/
function b64encode(str) {
var bytes = new TextEncoder().encode(str);
var bin = '';
var chunk = 0x8000;
for (var i = 0; i < bytes.length; i += chunk) {
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
}
return btoa(bin);
}
/** Read a cookie value, or `null` if absent. @param {string} name */
function getCookie(name) {
var escaped = name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1');
var match = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : null;
}
/** Set a session cookie (no expiry => cleared when the browser closes). */
function setCookie(name, value) {
document.cookie = name + '=' + encodeURIComponent(value) + '; path=/; SameSite=Lax';
}
// ---------------------------------------------------------------------------
// Expression evaluation
// ---------------------------------------------------------------------------
/**
* Compiled-expression cache. Expressions are evaluated as JavaScript against
* a scope object (the page's HTML is trusted, per the model).
* @type {Map}
*/
var exprCache = new Map();
/**
* Wrap a scope for expression evaluation so bare identifiers resolve in a
* controlled order: names on the scope (slot types, loop variables,
* `stm-def-*` locals) win; names missing from the scope but present on
* `globalThis` (`Math`, `parseInt`, `String`, …) forward to that global;
* anything else resolves to `undefined` instead of throwing a
* `ReferenceError` or leaking an unintended global. The `has` trap claims
* every name so `with` never falls through around the proxy, and the inner
* try/catch of the compiled expression still turns property access on a
* resolved `undefined`/`null` into `undefined` for that expression alone.
* @param {object} scope
* @returns {Proxy}
*/
function evalScope(scope) {
return new Proxy(scope, {
has: function () { return true; },
get: function (target, name) {
if (name in target) return target[name];
if (typeof name === 'string' && name in globalThis) return globalThis[name];
return undefined;
}
});
}
/**
* Compile (and cache) an expression string into a function of `scope`.
* Uses `with` so that arbitrary type names and loop variables are resolved
* as bare identifiers. Returns a function that yields `undefined` on any
* runtime or compile error.
* @param {string} expr
* @returns {Function}
*/
function compileExpr(expr) {
var cached = exprCache.get(expr);
if (cached !== undefined) return cached;
var fn;
try {
// Function-constructor bodies are non-strict by default, so `with` is
// permitted. The inner try/catch turns runtime errors (property access
// on an unset slot's `undefined`) into `undefined` so a missing path
// simply yields no value; missing identifiers already resolve to
// `undefined` via the evalScope proxy.
fn = new Function('scope', 'with(scope){try{return (' + expr + ');}catch(e){return undefined;}}');
} catch (e) {
fn = function () { return undefined; };
}
exprCache.set(expr, fn);
return fn;
}
/**
* Evaluate an expression against a scope.
* @param {string} expr
* @param {object} [scope]
* @returns {*} `undefined` if the expression is empty or fails.
*/
function evalExpr(expr, scope) {
if (expr == null || expr === '') return undefined;
try {
return compileExpr(expr)(evalScope(scope || {}));
} catch (e) {
return undefined;
}
}
/**
* Build a child scope that inherits the parent (so type bindings remain
* visible) and adds one own loop variable.
* @param {object} parent
* @param {string} name
* @param {*} value
* @returns {object}
*/
function childScope(parent, name, value) {
var scope = Object.create(parent || null);
scope[name] = value;
return scope;
}
// ---------------------------------------------------------------------------
// StatumError
// ---------------------------------------------------------------------------
/**
* Error type thrown for HTTP failures and `error` directives.
* @param {string} [message]
* @param {string} [code]
* @param {object} [data]
*/
function StatumError(message, code, data) {
this.name = 'StatumError';
this.message = message || '';
this.code = code || undefined;
this.data = data || undefined;
}
StatumError.prototype = Object.create(Error.prototype);
// ---------------------------------------------------------------------------
// UI handlers (confirm / notify / error)
// ---------------------------------------------------------------------------
/**
* Run the `notify` directive's default behaviour or the custom
* `statum.onNotifyHandler`. The default surfaces the message via `alert()`.
* @param {string} [kind] Application-defined category (freeform).
* @param {string} [message]
*/
function triggerNotify(kind, message) {
var fn = handlers.onNotify;
try {
if (fn) fn(kind, message);
else if (message != null) window.alert(message);
} catch (e) { console.error('[statum] notify handler error', e); }
}
/**
* Run the `stm-confirm` gate: the custom `statum.onConfirmHandler` if set,
* otherwise `window.confirm`. Supports async handlers (custom modals).
* @param {string} message
* @param {HTMLElement} element
* @returns {Promise} truthy if the action should proceed.
*/
async function triggerConfirm(message, element) {
var fn = handlers.onConfirm;
try {
if (fn) return !!(await fn(message, element));
return !!window.confirm(message);
} catch (e) {
console.error('[statum] confirm handler error', e);
return false;
}
}
/**
* Run the custom `statum.onErrorHandler`, if set. Returns whether the default
* behaviour (promise rejection + `stm-on-error` classes) should proceed: the
* handler suppresses the default by returning (or resolving) `false`.
* @param {StatumError} error
* @param {HTMLElement|null} element
* @returns {Promise}
*/
async function triggerError(error, element) {
var fn = handlers.onError;
if (!fn) return true;
try {
var suppress = await fn(error, element);
return suppress !== false;
} catch (e) {
console.error('[statum] error handler error', e);
return true;
}
}
/**
* Surface a failure: run `triggerError`, and if the default is not
* suppressed, apply `stm-on-error` classes (when an origin element exists)
* and re-throw so the originating promise rejects.
* @param {StatumError} error
* @param {HTMLElement|null} element
*/
async function reportFailure(error, element) {
if (await triggerError(error, element)) {
if (element) applyErrorClasses(element, true);
throw error;
}
}
// ---------------------------------------------------------------------------
// Persistence
// ---------------------------------------------------------------------------
/**
* Pick the storage backend for a scope.
* @param {string} scope
* @returns {Storage|null} `null` for in-memory scopes.
*/
function storageFor(scope) {
if (scope === 'device' || scope === 'session') return localStorage;
if (scope === 'flow') return sessionStorage;
return null; // page / transient
}
/**
* Persist a slot's frame to the backend matching its scope, removing any
* stale copy from the other backend first (in case the scope changed).
* @param {string} type
* @param {{frame: object, snapshot: object}} entry
*/
function persistEntry(type, entry) {
var scope = entry.snapshot.slot && entry.snapshot.slot.scope;
removePersisted(type);
var storage = storageFor(scope);
if (!storage) return;
try {
storage.setItem(STORAGE_PREFIX + type, JSON.stringify(entry.frame));
} catch (e) {
/* storage full or unavailable; remain in-memory only */
}
}
/** Remove a persisted frame from both backends. @param {string} type */
function removePersisted(type) {
try { localStorage.removeItem(STORAGE_PREFIX + type); } catch (e) {}
try { sessionStorage.removeItem(STORAGE_PREFIX + type); } catch (e) {}
}
/** Load every persisted frame from a backend into the in-memory store. */
function loadFrom(storage) {
for (var i = 0; i < storage.length; i++) {
var key = storage.key(i);
if (!key || key.indexOf(STORAGE_PREFIX) !== 0) continue;
try {
var frame = JSON.parse(storage.getItem(key));
var snapshot = JSON.parse(frame.content);
var type = snapshot.slot && snapshot.slot.type;
if (type && !store.has(type)) store.set(type, { frame: frame, snapshot: snapshot });
} catch (e) {
/* corrupt entry: ignore */
}
}
}
/** Hydrate the store from localStorage and sessionStorage. */
function hydrate() {
try { loadFrom(localStorage); } catch (e) {}
try { loadFrom(sessionStorage); } catch (e) {}
}
// ---------------------------------------------------------------------------
// Session-cookie initialization
// ---------------------------------------------------------------------------
/**
* Detect a fresh browser session using the `_statum_sdc` session cookie.
* On a fresh session, clear all `session`-scoped persisted frames, then set
* the marker. Session cookies are shared across tabs, so a tab opened
* mid-session sees the marker and leaves session data intact.
*/
function initSession() {
if (getCookie(SESSION_COOKIE) !== null) return;
clearSessionSlotsFromStorage();
setCookie(SESSION_COOKIE, '1');
}
/** Remove all `session`-scoped frames from localStorage. */
function clearSessionSlotsFromStorage() {
var toRemove = [];
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key || key.indexOf(STORAGE_PREFIX) !== 0) continue;
try {
var frame = JSON.parse(localStorage.getItem(key));
var snapshot = JSON.parse(frame.content);
if (snapshot.slot && snapshot.slot.scope === 'session') toRemove.push(key);
} catch (e) {}
}
for (var j = 0; j < toRemove.length; j++) localStorage.removeItem(toRemove[j]);
}
// ---------------------------------------------------------------------------
// Listeners & cross-tab broadcast
// ---------------------------------------------------------------------------
/**
* Fire all listeners for a type. (Renamed from `notify` to avoid confusion
* with the `notify` directive.)
* @param {string} type
* @param {object|null} frame
* @param {object|null} snapshot
*/
function fireListeners(type, frame, snapshot) {
var set = listeners.get(type);
if (!set) return;
var event = { type: type, frame: frame, snapshot: snapshot };
set.forEach(function (cb) {
try { cb(event); } catch (e) { console.error('[statum] listener error', e); }
});
}
/**
* Broadcast a state change to other tabs. Only `session`/`device` scopes are
* shared (flow/page/transient are per-tab).
* @param {object} message
* @param {string} [scope]
*/
function broadcast(message, scope) {
if (!channel) return;
if (scope !== 'session' && scope !== 'device') return;
try { channel.postMessage(message); } catch (e) {}
}
/**
* Broadcast a message to other tabs regardless of slot scope. Used to relay
* realtime (live) events so they reach every tab, not just session/device
* ones.
*/
function broadcastAlways(message) {
if (!channel) return;
try { channel.postMessage(message); } catch (e) {}
}
/** Open the BroadcastChannel and wire inbound messages. */
function setupChannel() {
if (typeof BroadcastChannel === 'undefined') return;
try {
channel = new BroadcastChannel(CHANNEL_NAME);
} catch (e) {
channel = null;
return;
}
channel.onmessage = function (ev) {
var msg = ev.data;
if (!msg) return;
if (msg.kind === 'set' && msg.frame) {
try {
var snapshot = JSON.parse(msg.frame.content);
var type = snapshot.slot && snapshot.slot.type;
if (type && commitSet(msg.frame, snapshot, type, false)) renderAll();
} catch (e) { /* ignore malformed */ }
} else if (msg.kind === 'clear') {
var t = msg.type;
if (!t && msg.key != null) {
store.forEach(function (entry, type) {
if (entry.snapshot.slot && entry.snapshot.slot.key === msg.key) t = type;
});
}
if (t) clearType(t, { broadcast: false });
else if (msg.key != null) signatures.delete(msg.key);
renderAll();
}
};
}
// ---------------------------------------------------------------------------
// State mutation
// ---------------------------------------------------------------------------
/**
* Remove a type from the store, un-persist it, drop its idempotency entry,
* fire listeners, and (optionally) broadcast the clear to other tabs.
* @param {string} type
* @param {{broadcast?: boolean}} [opts]
*/
function clearType(type, opts) {
var entry = store.get(type);
if (!entry) return;
var scope = entry.snapshot.slot && entry.snapshot.slot.scope;
var key = entry.snapshot.slot && entry.snapshot.slot.key;
store.delete(type);
removePersisted(type);
if (key) signatures.delete(key);
fireListeners(type, null, null);
if (opts && opts.broadcast) broadcast({ kind: 'clear', key: key, type: type }, scope);
}
/**
* Resolve a slot key to its type and clear it. Used by the `clear` directive
* (keyed) and by live `clear` events.
* @param {string} key
* @param {boolean} [broadcast]
*/
function clearByKey(key, broadcast) {
var typeToRemove = null;
store.forEach(function (entry, type) {
if (entry.snapshot.slot && entry.snapshot.slot.key === key) typeToRemove = type;
});
if (typeToRemove) clearType(typeToRemove, { broadcast: !!broadcast });
else if (key) signatures.delete(key);
}
/**
* Resolve a frame signature to its slot type and clear it. Used by the
* `invalidate` directive, which references a signature (not a key) because the
* server reports a bad signature without trusting the frame's content.
* @param {string} signature
* @param {boolean} [broadcast]
*/
function clearBySignature(signature, broadcast) {
var typeToRemove = null;
store.forEach(function (entry, type) {
if (entry.frame && entry.frame.signature === signature) typeToRemove = type;
});
if (typeToRemove) clearType(typeToRemove, { broadcast: !!broadcast });
else if (signature) signatures.delete(signature);
}
/**
* Commit a `set` to the in-memory store, persist it, and fire listeners.
* Idempotent: if the frame's `signature` matches the last-applied signature
* for this slot (tracked in {@link signatures}), nothing happens and `false`
* is returned. When `doBroadcast` is true the set is forwarded to other tabs
* via the scope-gated broadcast (session/device only).
* @param {object} frame
* @param {object} snapshot
* @param {string} type
* @param {boolean} doBroadcast
* @returns {boolean} `true` if state changed.
*/
function commitSet(frame, snapshot, type, doBroadcast) {
var key = snapshot.slot && snapshot.slot.key;
var sig = frame.signature;
if (key && sig !== undefined && signatures.get(key) === sig) return false;
var entry = { frame: frame, snapshot: snapshot };
store.set(type, entry);
persistEntry(type, entry);
fireListeners(type, frame, snapshot);
if (key && sig !== undefined) signatures.set(key, sig);
if (doBroadcast) {
var scope = snapshot.slot && snapshot.slot.scope;
broadcast({ kind: 'set', frame: frame }, scope);
}
return true;
}
/**
* Apply a `set` directive: store/refresh a slot's snapshot (idempotently),
* broadcasting to other tabs.
* @param {object} directive `{ snapshot_frame }`
*/
function applySet(directive) {
var frame = directive.snapshot_frame;
if (!frame || !frame.content) return;
var snapshot;
try { snapshot = JSON.parse(frame.content); } catch (e) { return; }
var type = snapshot.slot && snapshot.slot.type;
if (!type) return;
commitSet(frame, snapshot, type, true);
}
/**
* Apply a `clear` directive. The directive carries a slot key, so the type
* holding that key is resolved and removed.
* @param {object} directive `{ key }`
*/
function applyClear(directive) {
if (directive.key == null) return;
clearByKey(directive.key, true);
}
/**
* Apply a live `set` event received over the realtime channel. The frame is
* committed (idempotently) and, when new, broadcast to sibling tabs via the
* scope-agnostic {@link broadcastAlways} so every tab sees it regardless of
* which (or how many) tabs hold a subscription.
* @param {object} frame
*/
function applyRemoteSet(frame) {
try {
var snapshot = JSON.parse(frame.content);
var type = snapshot.slot && snapshot.slot.type;
if (!type) return;
if (commitSet(frame, snapshot, type, false)) {
broadcastAlways({ kind: 'set', frame: frame });
renderAll();
}
} catch (e) { /* malformed frame */ }
}
/** Apply a live `clear` event received over the realtime channel. */
function applyRemoteClear(key) {
if (key == null) return;
signatures.delete(key);
clearByKey(key, false);
broadcastAlways({ kind: 'clear', key: key });
renderAll();
}
// ---------------------------------------------------------------------------
// Snapshot freshness & request headers
// ---------------------------------------------------------------------------
/**
* Categorize a held snapshot relative to the current time.
* @returns {'silent'|'always'|'invalid'|'stale'|'fresh'}
* - `silent`: unidirectional (`transmit_after` undefined) - never sent.
* - `always`: `transmit_after === as_at` - send the base64 frame each time.
* - `invalid`: past `invalid_after` - drop and refetch.
* - `stale`: past `transmit_after` but not yet invalid - pre-flight first.
* - `fresh`: still cached server-side - send the `as_at` reference.
*/
function entryCategory(entry) {
var snap = entry.snapshot;
var transmit = snap.transmit_after;
if (transmit === undefined || transmit === null) return 'silent';
if (transmit === snap.as_at) return 'always';
var now = Date.now();
var invalidAt = parseTime(snap.invalid_after);
var transmitAt = parseTime(transmit);
if (invalidAt !== undefined && now >= invalidAt) return 'invalid';
if (transmitAt !== undefined && now >= transmitAt) return 'stale';
return 'fresh';
}
/**
* Drop locally-invalid snapshots and pre-flight any stale ones. Pre-flighting
* posts the stale frames to the slot endpoint and applies the returned `set`
* directives, refreshing their `as_at` values before the real request runs.
*
* Single-flight: concurrent callers share one in-flight preparation.
* @returns {Promise}
*/
var preparePromise = null;
function prepareSlots() {
if (preparePromise) return preparePromise;
preparePromise = doPrepareSlots().then(function () {
preparePromise = null;
}, function () {
preparePromise = null;
});
return preparePromise;
}
async function doPrepareSlots() {
var stale = [];
store.forEach(function (entry, type) {
var cat = entryCategory(entry);
if (cat === 'invalid') {
clearType(type, { broadcast: false });
} else if (cat === 'stale') {
stale.push(entry.frame);
}
});
if (stale.length) {
var directives = await postSlots(stale);
var pfResult = applyDirectives(directives, { originElement: null });
executeTerminal(pfResult);
}
}
/**
* Build the `X-Statum-Slot` header values for every held slot, using the
* freshness rules. Assumes {@link prepareSlots} has just run.
* @returns {string[]}
*/
function buildSlotHeaders() {
var headers = [];
store.forEach(function (entry) {
var snap = entry.snapshot;
var cat = entryCategory(entry);
if (cat === 'silent' || cat === 'invalid') return;
if (cat === 'always') {
headers.push(b64encode(JSON.stringify(entry.frame)));
} else {
headers.push('key=' + snap.slot.key + '; as_at=' + snap.as_at);
}
});
return headers;
}
/**
* Collect the current frames held for the given slot keys, in the order the
* keys are first supplied. Used by the `transmit` directive (which forces a
* slot's frame to be posted as if its `transmit_after` had elapsed). Keys the
* client does not currently hold are skipped.
* @param {string[]} keys
* @returns {object[]}
*/
function collectFramesForKeys(keys) {
var keySet = Object.create(null);
for (var i = 0; i < keys.length; i++) keySet[keys[i]] = true;
var frames = [];
store.forEach(function (entry) {
var key = entry.snapshot.slot && entry.snapshot.slot.key;
if (key && keySet[key]) {
frames.push(entry.frame);
delete keySet[key];
}
});
return frames;
}
// ---------------------------------------------------------------------------
// HTTP helpers
// ---------------------------------------------------------------------------
/** True if a response is a Statum directive response. */
function isStatum(res) {
return (res.headers.get('Content-Type') || '').indexOf(STATUM_CONTENT_TYPE) !== -1;
}
/**
* POST an array of frames to the slot endpoint (the pre-flight target) and
* return its directive array. This request intentionally carries no slot
* headers (it is itself the refresh).
* @param {object[]} frames
* @returns {Promise}
*/
async function postSlots(frames) {
var headers = new Headers();
headers.set('Accept', STATUM_CONTENT_TYPE);
headers.set('Content-Type', 'application/json');
var res = await fetch(config.slotsUrl, {
method: 'POST',
headers: headers,
body: JSON.stringify(frames),
credentials: 'same-origin'
});
if (!isStatum(res)) return [];
try { return await res.json(); } catch (e) { return []; }
}
/**
* Apply the non-terminal directives from a response (set/clear/subscribe/
* unsubscribe/notify/transmit-collection), then `render`.
*
* Terminal directives (`navigate`, `post`), error directives, and any
* transmit/retry intent are collected on the returned result rather than
* executed here: {@link handleResponse} performs the asynchronous
* transmission (and optional retry) and then runs terminal navigation, so
* that a `transmit` with `retry` can re-issue the request before the page is
* navigated away.
* @param {object[]} directives
* @param {{originElement?: HTMLElement}} [ctx]
* @returns {{errors: StatumError[], transmitKeys: string[], retry: boolean, navigate: object|null, post: object|null}}
*/
function applyDirectives(directives, ctx) {
var result = { errors: [], transmitKeys: [], retry: false, navigate: null, post: null, formSubmits: [], formResets: [] };
var list = Array.isArray(directives) ? directives : [];
for (var i = 0; i < list.length; i++) {
var d = list[i];
if (!d || typeof d !== 'object') continue;
switch (d.type) {
case 'set': applySet(d); break;
case 'clear': applyClear(d); break;
case 'invalidate':
if (d.signature != null) {
console.warn('[statum] server could not verify slot frame ' + d.signature + '; clearing it');
clearBySignature(d.signature, true);
}
break;
case 'subscribe': channelSubscribe(d.key); break;
case 'unsubscribe': channelUnsubscribe(d.key); break;
case 'notify': triggerNotify(d.kind, d.message); break;
case 'transmit':
if (d.key != null) {
if (result.transmitKeys.indexOf(d.key) === -1) result.transmitKeys.push(d.key);
if (d.retry) result.retry = true;
}
break;
case 'error':
{
var err = new StatumError(d.message, d.code, d.data);
var detail = d.code ? (d.message + ' [' + d.code + ']') : d.message;
console.error('[statum] error directive: ' + (detail || '(no message)'));
result.errors.push(err);
}
break;
case 'navigate': result.navigate = d; break;
case 'post': result.post = d; break;
case 'form-submit': result.formSubmits.push(d); break;
case 'form-reset': result.formResets.push(d); break;
}
}
renderAll();
return result;
}
/** Execute the terminal directives collected by {@link applyDirectives}. */
function executeTerminal(result) {
result.formResets.forEach(doFormReset);
result.formSubmits.forEach(doFormSubmit);
if (result.navigate) doNavigate(result.navigate);
if (result.post) doPost(result.post);
}
/** Execute a `form-submit` directive: optionally override action/method, then submit. */
function doFormSubmit(d) {
var form = document.getElementById(d.form);
if (form) {
if (d.action) form.setAttribute('action', d.action);
if (d.method) form.setAttribute('method', d.method);
form.submit();
}
}
/** Execute a `form-reset` directive. */
function doFormReset(d) {
var form = document.getElementById(d.form);
if (form) form.reset();
}
/** Execute a `navigate` directive as a full page load. */
function doNavigate(d) {
if (d.uri) window.location.href = d.uri;
}
/** Execute a `post` directive as a real (navigating) form POST. */
function doPost(d) {
if (!d.uri) return;
var form = document.createElement('form');
form.method = 'POST';
form.action = d.uri;
form.style.display = 'none';
var data = d.data || {};
Object.keys(data).forEach(function (name) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = data[name] == null ? '' : String(data[name]);
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
/**
* Handle a fetch response: if it is a Statum response, apply its directives;
* otherwise throw on HTTP errors and no-op on other successful responses.
*
* Directive ordering: non-terminal directives are applied and the page
* rendered; then any `transmit` directives force their slot frames to be
* posted (applying the directives the slot endpoint returns); then, if a
* transmit requested a retry, the originating request (`ctx.retry`) is
* re-issued and this response's terminal directives and errors are skipped.
* Otherwise terminal navigation runs, then error directives are surfaced.
* @param {Response} res
* @param {{originElement?: HTMLElement, retry?: Function}} [ctx]
*/
async function handleResponse(res, ctx) {
var el = ctx && ctx.originElement;
if (!isStatum(res)) {
if (!res.ok) await reportFailure(new StatumError('HTTP ' + res.status), el);
return;
}
var directives;
try { directives = await res.json(); } catch (e) { directives = []; }
var result = applyDirectives(directives, ctx);
if (result.transmitKeys && result.transmitKeys.length) {
var frames = collectFramesForKeys(result.transmitKeys);
if (frames.length) {
var txDirectives = await postSlots(frames);
applyDirectives(txDirectives, { originElement: el });
}
}
if (result.retry && ctx && typeof ctx.retry === 'function') {
await ctx.retry();
return;
}
executeTerminal(result);
var errors = result.errors || [];
for (var i = 0; i < errors.length; i++) {
await reportFailure(errors[i], el);
}
}
// ---------------------------------------------------------------------------
// Entrypoint & action requests
// ---------------------------------------------------------------------------
/**
* Fetch the entrypoint for the current URL, applying its directives. Carries
* slot headers (and pre-flights) like any other request. The request is
* wrapped in an `attempt` closure passed as the response `retry` callback, so
* a `transmit` directive with `retry` can re-issue it after transmission.
* @returns {Promise}
*/
function loadEntrypoint() {
if (guardRedirected) return Promise.resolve();
// From this point on guards are "post-entrypoint" (all of them evaluate).
entrypointLoaded = true;
var attempt = async function () {
await prepareSlots();
var headers = new Headers();
headers.set('Accept', STATUM_CONTENT_TYPE);
buildSlotHeaders().forEach(function (v) { headers.append(SLOT_HEADER, v); });
var url = config.entrypointUrl + '?uri=' + encodeURIComponent(window.location.href);
var res = await fetch(url, { headers: headers, credentials: 'same-origin' });
await handleResponse(res, { originElement: null, retry: attempt });
};
return attempt();
}
/**
* Perform an action request.
*
* The request is wrapped in an `attempt` closure passed as the response
* `retry` callback, so a `transmit` directive with `retry` can re-issue the
* same action after the transmission (rebuilding headers from current state).
*
* @param {object} action `{ uri, method, private }`
* @param {object} [data] Key/value parameters. Encoded as query string for
* GET/HEAD, otherwise as `application/x-www-form-urlencoded`.
* @param {HTMLElement} [originElement] Element that triggered the action, so
* that `stm-on-error` classes can be applied on an `error` directive.
* @returns {Promise} Resolves once the request completes and its
* directives have run; rejects on HTTP failure or an `error` directive.
*/
function performAction(action, data, originElement) {
var attempt = async function () {
await prepareSlots();
var headers = new Headers();
headers.set('Accept', STATUM_CONTENT_TYPE);
buildSlotHeaders().forEach(function (v) { headers.append(SLOT_HEADER, v); });
if (action.private) headers.set(PRIVATE_HEADER, action.private);
var method = (action.method || 'GET').toUpperCase();
var params = data || {};
var init = { method: method, headers: headers, credentials: 'same-origin' };
var url = action.uri;
if (method === 'GET' || method === 'HEAD') {
var qs = new URLSearchParams(params).toString();
if (qs) url += (url.indexOf('?') !== -1 ? '&' : '?') + qs;
} else {
init.body = new URLSearchParams(params).toString();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
}
var res;
try {
res = await fetch(url, init);
} catch (networkErr) {
await reportFailure(new StatumError('Network error'), originElement);
return;
}
await handleResponse(res, { originElement: originElement, retry: attempt });
};
return attempt();
}
// ---------------------------------------------------------------------------
// Realtime channel (subscribe/unsubscribe directives)
// ---------------------------------------------------------------------------
/**
* Per-tab fallback engine, used when `SharedWorker` is unavailable. Owns an
* `EventSource`, reads the channel id from the first `event: channel`,
* reference-counts slot keys within this tab, PATCHes subscription changes,
* applies incoming set/clear events (broadcasting them to siblings), and
* closes the stream once it has no subscriptions.
*/
function createFallbackEngine() {
var es = null;
var channelId = null;
var refs = new Map(); // slotKey -> count
var pending = { subscribe: [], unsubscribe: [] };
var patchScheduled = false;
function openStream() {
if (es) return;
try { es = new EventSource(config.channelUrl); }
catch (e) { es = null; return; }
es.addEventListener('channel', function (ev) { channelId = ev.data; flushPatch(); });
es.addEventListener('set', function (ev) {
try { applyRemoteSet(JSON.parse(ev.data)); } catch (e) {}
});
es.addEventListener('clear', function (ev) { applyRemoteClear(ev.data); });
es.onerror = function () { /* EventSource auto-reconnects */ };
}
function closeStream() {
if (es) { try { es.close(); } catch (e) {} es = null; }
channelId = null;
}
function schedulePatch() {
if (patchScheduled) return;
patchScheduled = true;
Promise.resolve().then(function () { patchScheduled = false; flushPatch(); });
}
function flushPatch() {
if (!channelId) return;
var sub = pending.subscribe, uns = pending.unsubscribe;
if (!sub.length && !uns.length) return;
pending = { subscribe: [], unsubscribe: [] };
var body = {};
if (sub.length) body.subscribe = sub;
if (uns.length) body.unsubscribe = uns;
try {
fetch(config.channelUrl + '/' + encodeURIComponent(channelId), {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify(body)
}).catch(function () {});
} catch (e) {}
}
return {
subscribe: function (key) {
var c = refs.get(key) || 0;
refs.set(key, c + 1);
if (c === 0) { openStream(); pending.subscribe.push(key); schedulePatch(); }
},
unsubscribe: function (key) {
var c = refs.get(key) || 0;
if (c <= 0) return;
if (c - 1 <= 0) {
refs.delete(key);
pending.unsubscribe.push(key);
schedulePatch();
if (refs.size === 0) setTimeout(function () { if (refs.size === 0) closeStream(); }, 0);
} else {
refs.set(key, c - 1);
}
}
};
}
/**
* Channel manager. Prefers a `SharedWorker` (which owns the single SSE
* connection, reference-counts subscriptions across tabs, and relays events
* through the cross-tab `BroadcastChannel`); falls back to a per-tab
* `EventSource` engine when `SharedWorker` is unavailable or the worker
* script fails to load. Tracks the true per-slot subscription count so it can
* replay active subscriptions into the fallback on a mid-session switch.
*/
function createChannelManager() {
var port = null;
var workerObj = null;
var fallback = null;
var mode = null; // 'worker' | 'fallback' | null
var counts = new Map(); // slotKey -> count (true subscription state)
function switchToFallback() {
if (mode === 'fallback') return;
mode = 'fallback';
port = null;
workerObj = null;
fallback = createFallbackEngine();
counts.forEach(function (c, key) {
for (var i = 0; i < c; i++) fallback.subscribe(key);
});
}
function ensure() {
if (mode !== null) return;
if (typeof SharedWorker !== 'undefined') {
try {
workerObj = new SharedWorker(config.workerUrl);
port = workerObj.port;
port.start();
port.postMessage({ op: 'init', url: config.channelUrl });
// The worker delivers events via the BroadcastChannel, so nothing is
// required on this port beyond command messages.
port.onmessage = function () {};
workerObj.onerror = function () {
console.warn('[statum] realtime worker failed; falling back to per-tab channel');
switchToFallback();
};
window.addEventListener('pagehide', function () {
if (mode === 'worker' && port) {
try { port.postMessage({ op: 'disconnect' }); } catch (e) {}
}
});
mode = 'worker';
return;
} catch (e) { workerObj = null; port = null; }
}
switchToFallback();
}
return {
subscribe: function (key) {
if (key == null) return;
ensure();
counts.set(key, (counts.get(key) || 0) + 1);
if (mode === 'worker') { try { port.postMessage({ op: 'subscribe', key: key }); } catch (e) {} }
else fallback.subscribe(key);
},
unsubscribe: function (key) {
if (key == null) return;
var c = (counts.get(key) || 0) - 1;
if (c <= 0) counts.delete(key); else counts.set(key, c);
ensure();
if (mode === 'worker') { try { port.postMessage({ op: 'unsubscribe', key: key }); } catch (e) {} }
else fallback.unsubscribe(key);
}
};
}
var channelManager = createChannelManager();
function channelSubscribe(key) { channelManager.subscribe(key); }
function channelUnsubscribe(key) { channelManager.unsubscribe(key); }
// ---------------------------------------------------------------------------
// Rendering: loops, conditionals, bindings
// ---------------------------------------------------------------------------
/** Templates captured from loop holders. @type {WeakMap} */
var templates = new WeakMap();
/** Instance nodes managed by a loop holder. @type {WeakMap} */
var instancesByHolder = new WeakMap();
/** Loop instance nodes; skipped by the render walk (owned by their holder). */
var instanceNodes = new WeakSet();
/** Elements whose `stm-action` has been wired, to avoid double-binding. */
var wired = new WeakSet();
/**
* Find a `stm-for-{name}-in` attribute on an element.
* @returns {{varName: string, expr: string}|null}
*/
function getStmFor(el) {
var attrs = el.attributes;
for (var i = 0; i < attrs.length; i++) {
var match = attrs[i].name.match(/^stm-for-(.+)-in$/);
if (match) return { varName: match[1], expr: attrs[i].value };
}
return null;
}
/** Remove any `stm-for-*` and `stm-key` attributes from an element. */
function stripLoopAttrs(el) {
var toRemove = [];
for (var i = 0; i < el.attributes.length; i++) {
var name = el.attributes[i].name;
if (/^stm-for-(.+)-in$/.test(name) || name === 'stm-key') toRemove.push(name);
}
toRemove.forEach(function (n) { el.removeAttribute(n); });
}
/**
* Evaluate an `stm-if` / `stm-else-if` / `stm-else` chain beginning at `el`,
* showing the matching branch and hiding the rest.
*
* Branches are toggled with the `hidden` attribute rather than detached, so
* every branch stays in the DOM and is re-evaluated on each render. (Detaching
* the `stm-if` element would remove it from the render walk, leaving an
* already-attached `stm-else` branch skipped and never refreshed.)
* @returns {Element|null} The active branch element (or `null`).
*/
function handleIfChain(el, scope) {
var branches = [{ el: el, expr: el.getAttribute('stm-if') }];
var cur = el.nextElementSibling;
while (cur) {
if (cur.hasAttribute('stm-else-if')) {
branches.push({ el: cur, expr: cur.getAttribute('stm-else-if') });
cur = cur.nextElementSibling;
} else if (cur.hasAttribute('stm-else')) {
branches.push({ el: cur, expr: null });
break;
} else {
break;
}
}
var activeEl = null;
for (var i = 0; i < branches.length; i++) {
var b = branches[i];
if (b.expr === null || !!evalExpr(b.expr, scope)) { activeEl = b.el; break; }
}
branches.forEach(function (b) {
if (b.el === activeEl) b.el.removeAttribute('hidden');
else b.el.setAttribute('hidden', '');
});
return activeEl;
}
/** Toggle `stm-class.{name}` classes from their predicate expressions. */
function handleClass(el, scope) {
var attrs = el.attributes;
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (attr.name.indexOf('stm-class.') === 0) {
var cls = attr.name.slice('stm-class.'.length);
if (cls && !!evalExpr(attr.value, scope)) el.classList.add(cls);
else el.classList.remove(cls);
}
}
}
/**
* Bind `stm-attribute.{name}`: a boolean result toggles the attribute, any
* other defined value sets it, and `undefined` removes it.
*
* For booleans, prefer the element's IDL property when one exists (e.g.
* `checked`, `disabled`, `hidden`) — setting the content attribute is
* unreliable for these once the control is "dirty".
*/
function handleAttribute(el, scope) {
var attrs = el.attributes;
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (attr.name.indexOf('stm-attribute.') === 0) {
var name = attr.name.slice('stm-attribute.'.length);
if (!name) continue;
var value = evalExpr(attr.value, scope);
if (value === undefined) {
el.removeAttribute(name);
} else if (typeof value === 'boolean') {
if (name in el && typeof el[name] === 'boolean') el[name] = value;
else if (value) el.setAttribute(name, '');
else el.removeAttribute(name);
} else {
el.setAttribute(name, value === null ? '' : String(value));
}
}
}
}
/**
* Apply `stm-disabled`: when the predicate is truthy, disable the element and
* its descendant form controls; otherwise enable them.
*/
function handleDisabled(el, scope) {
if (!el.hasAttribute('stm-disabled')) return;
setDisabledCascade(el, !!evalExpr(el.getAttribute('stm-disabled'), scope));
}
/** Set the `disabled` attribute on an element and its descendant controls. */
function setDisabledCascade(el, on) {
var nodes = [el].concat(Array.prototype.slice.call(
el.querySelectorAll('button,input,select,textarea,fieldset')));
nodes.forEach(function (n) {
if (on) n.setAttribute('disabled', ''); else n.removeAttribute('disabled');
});
}
// ---------------------------------------------------------------------------
// Markdown rendering (CommonMark 0.31.2)
// ---------------------------------------------------------------------------
//
// A two-pass CommonMark engine: block structure first (paragraphs, ATX and
// setext headings, thematic breaks, indented and fenced code blocks, block
// quotes with lazy continuation, lists with tight/loose rendering and link
// reference definitions), then inline parsing inside the leaf blocks
// (delimiter-run emphasis, links and images, code spans, autolinks,
// backslash escapes, entity references, hard/soft line breaks).
//
// SECURITY: the markdown source is untrusted text. Raw HTML is never passed
// through — inline HTML and HTML-block-looking constructs render as escaped
// text, which intentionally diverges from the CommonMark HTML output for
// those examples. Link and image destinations are scheme-filtered by
// safeMarkdownUrl() and everything is escaped before it reaches the output.
function safeMarkdownUrl(url) {
var u = url.trim();
var m = u.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
if (!m) return u;
var scheme = m[1].toLowerCase();
if (scheme === 'http' || scheme === 'https' || scheme === 'mailto' || scheme === 'ftp') return u;
return '#';
}
/**
* Packed HTML5 named-entity table (WHATWG `entities.json`, 2125 entries).
* Each record is "name cp[,cp...]" on one line; multi-codepoint entities
* carry comma-separated codepoints. Line breaks are escaped so the whole
* table stays one string literal. Parsed lazily into a name -> text map the
* first time an entity reference is looked up.
*/
var MD_ENTITIES_PACKED = ''
+ "AElig 198\nAMP 38\nAacute 193\nAbreve 258\nAcirc 194\nAcy 1040\nAfr 120068\nAgrave 192\n"
+ "Alpha 913\nAmacr 256\nAnd 10835\nAogon 260\nAopf 120120\nApplyFunction 8289\nAring 197\n"
+ "Ascr 119964\nAssign 8788\nAtilde 195\nAuml 196\nBackslash 8726\nBarv 10983\nBarwed 8966\n"
+ "Bcy 1041\nBecause 8757\nBernoullis 8492\nBeta 914\nBfr 120069\nBopf 120121\nBreve 728\n"
+ "Bscr 8492\nBumpeq 8782\nCHcy 1063\nCOPY 169\nCacute 262\nCap 8914\n"
+ "CapitalDifferentialD 8517\nCayleys 8493\nCcaron 268\nCcedil 199\nCcirc 264\nCconint 8752\n"
+ "Cdot 266\nCedilla 184\nCenterDot 183\nCfr 8493\nChi 935\nCircleDot 8857\n"
+ "CircleMinus 8854\nCirclePlus 8853\nCircleTimes 8855\nClockwiseContourIntegral 8754\n"
+ "CloseCurlyDoubleQuote 8221\nCloseCurlyQuote 8217\nColon 8759\nColone 10868\n"
+ "Congruent 8801\nConint 8751\nContourIntegral 8750\nCopf 8450\nCoproduct 8720\n"
+ "CounterClockwiseContourIntegral 8755\nCross 10799\nCscr 119966\nCup 8915\nCupCap 8781\n"
+ "DD 8517\nDDotrahd 10513\nDJcy 1026\nDScy 1029\nDZcy 1039\nDagger 8225\nDarr 8609\n"
+ "Dashv 10980\nDcaron 270\nDcy 1044\nDel 8711\nDelta 916\nDfr 120071\nDiacriticalAcute 180\n"
+ "DiacriticalDot 729\nDiacriticalDoubleAcute 733\nDiacriticalGrave 96\n"
+ "DiacriticalTilde 732\nDiamond 8900\nDifferentialD 8518\nDopf 120123\nDot 168\n"
+ "DotDot 8412\nDotEqual 8784\nDoubleContourIntegral 8751\nDoubleDot 168\n"
+ "DoubleDownArrow 8659\nDoubleLeftArrow 8656\nDoubleLeftRightArrow 8660\n"
+ "DoubleLeftTee 10980\nDoubleLongLeftArrow 10232\nDoubleLongLeftRightArrow 10234\n"
+ "DoubleLongRightArrow 10233\nDoubleRightArrow 8658\nDoubleRightTee 8872\n"
+ "DoubleUpArrow 8657\nDoubleUpDownArrow 8661\nDoubleVerticalBar 8741\nDownArrow 8595\n"
+ "DownArrowBar 10515\nDownArrowUpArrow 8693\nDownBreve 785\nDownLeftRightVector 10576\n"
+ "DownLeftTeeVector 10590\nDownLeftVector 8637\nDownLeftVectorBar 10582\n"
+ "DownRightTeeVector 10591\nDownRightVector 8641\nDownRightVectorBar 10583\nDownTee 8868\n"
+ "DownTeeArrow 8615\nDownarrow 8659\nDscr 119967\nDstrok 272\nENG 330\nETH 208\nEacute 201\n"
+ "Ecaron 282\nEcirc 202\nEcy 1069\nEdot 278\nEfr 120072\nEgrave 200\nElement 8712\n"
+ "Emacr 274\nEmptySmallSquare 9723\nEmptyVerySmallSquare 9643\nEogon 280\nEopf 120124\n"
+ "Epsilon 917\nEqual 10869\nEqualTilde 8770\nEquilibrium 8652\nEscr 8496\nEsim 10867\n"
+ "Eta 919\nEuml 203\nExists 8707\nExponentialE 8519\nFcy 1060\nFfr 120073\n"
+ "FilledSmallSquare 9724\nFilledVerySmallSquare 9642\nFopf 120125\nForAll 8704\n"
+ "Fouriertrf 8497\nFscr 8497\nGJcy 1027\nGT 62\nGamma 915\nGammad 988\nGbreve 286\n"
+ "Gcedil 290\nGcirc 284\nGcy 1043\nGdot 288\nGfr 120074\nGg 8921\nGopf 120126\n"
+ "GreaterEqual 8805\nGreaterEqualLess 8923\nGreaterFullEqual 8807\nGreaterGreater 10914\n"
+ "GreaterLess 8823\nGreaterSlantEqual 10878\nGreaterTilde 8819\nGscr 119970\nGt 8811\n"
+ "HARDcy 1066\nHacek 711\nHat 94\nHcirc 292\nHfr 8460\nHilbertSpace 8459\nHopf 8461\n"
+ "HorizontalLine 9472\nHscr 8459\nHstrok 294\nHumpDownHump 8782\nHumpEqual 8783\nIEcy 1045\n"
+ "IJlig 306\nIOcy 1025\nIacute 205\nIcirc 206\nIcy 1048\nIdot 304\nIfr 8465\nIgrave 204\n"
+ "Im 8465\nImacr 298\nImaginaryI 8520\nImplies 8658\nInt 8748\nIntegral 8747\n"
+ "Intersection 8898\nInvisibleComma 8291\nInvisibleTimes 8290\nIogon 302\nIopf 120128\n"
+ "Iota 921\nIscr 8464\nItilde 296\nIukcy 1030\nIuml 207\nJcirc 308\nJcy 1049\nJfr 120077\n"
+ "Jopf 120129\nJscr 119973\nJsercy 1032\nJukcy 1028\nKHcy 1061\nKJcy 1036\nKappa 922\n"
+ "Kcedil 310\nKcy 1050\nKfr 120078\nKopf 120130\nKscr 119974\nLJcy 1033\nLT 60\nLacute 313\n"
+ "Lambda 923\nLang 10218\nLaplacetrf 8466\nLarr 8606\nLcaron 317\nLcedil 315\nLcy 1051\n"
+ "LeftAngleBracket 10216\nLeftArrow 8592\nLeftArrowBar 8676\nLeftArrowRightArrow 8646\n"
+ "LeftCeiling 8968\nLeftDoubleBracket 10214\nLeftDownTeeVector 10593\nLeftDownVector 8643\n"
+ "LeftDownVectorBar 10585\nLeftFloor 8970\nLeftRightArrow 8596\nLeftRightVector 10574\n"
+ "LeftTee 8867\nLeftTeeArrow 8612\nLeftTeeVector 10586\nLeftTriangle 8882\n"
+ "LeftTriangleBar 10703\nLeftTriangleEqual 8884\nLeftUpDownVector 10577\n"
+ "LeftUpTeeVector 10592\nLeftUpVector 8639\nLeftUpVectorBar 10584\nLeftVector 8636\n"
+ "LeftVectorBar 10578\nLeftarrow 8656\nLeftrightarrow 8660\nLessEqualGreater 8922\n"
+ "LessFullEqual 8806\nLessGreater 8822\nLessLess 10913\nLessSlantEqual 10877\n"
+ "LessTilde 8818\nLfr 120079\nLl 8920\nLleftarrow 8666\nLmidot 319\nLongLeftArrow 10229\n"
+ "LongLeftRightArrow 10231\nLongRightArrow 10230\nLongleftarrow 10232\n"
+ "Longleftrightarrow 10234\nLongrightarrow 10233\nLopf 120131\nLowerLeftArrow 8601\n"
+ "LowerRightArrow 8600\nLscr 8466\nLsh 8624\nLstrok 321\nLt 8810\nMap 10501\nMcy 1052\n"
+ "MediumSpace 8287\nMellintrf 8499\nMfr 120080\nMinusPlus 8723\nMopf 120132\nMscr 8499\n"
+ "Mu 924\nNJcy 1034\nNacute 323\nNcaron 327\nNcedil 325\nNcy 1053\n"
+ "NegativeMediumSpace 8203\nNegativeThickSpace 8203\nNegativeThinSpace 8203\n"
+ "NegativeVeryThinSpace 8203\nNestedGreaterGreater 8811\nNestedLessLess 8810\nNewLine 10\n"
+ "Nfr 120081\nNoBreak 8288\nNonBreakingSpace 160\nNopf 8469\nNot 10988\nNotCongruent 8802\n"
+ "NotCupCap 8813\nNotDoubleVerticalBar 8742\nNotElement 8713\nNotEqual 8800\n"
+ "NotEqualTilde 8770,824\nNotExists 8708\nNotGreater 8815\nNotGreaterEqual 8817\n"
+ "NotGreaterFullEqual 8807,824\nNotGreaterGreater 8811,824\nNotGreaterLess 8825\n"
+ "NotGreaterSlantEqual 10878,824\nNotGreaterTilde 8821\nNotHumpDownHump 8782,824\n"
+ "NotHumpEqual 8783,824\nNotLeftTriangle 8938\nNotLeftTriangleBar 10703,824\n"
+ "NotLeftTriangleEqual 8940\nNotLess 8814\nNotLessEqual 8816\nNotLessGreater 8824\n"
+ "NotLessLess 8810,824\nNotLessSlantEqual 10877,824\nNotLessTilde 8820\n"
+ "NotNestedGreaterGreater 10914,824\nNotNestedLessLess 10913,824\nNotPrecedes 8832\n"
+ "NotPrecedesEqual 10927,824\nNotPrecedesSlantEqual 8928\nNotReverseElement 8716\n"
+ "NotRightTriangle 8939\nNotRightTriangleBar 10704,824\nNotRightTriangleEqual 8941\n"
+ "NotSquareSubset 8847,824\nNotSquareSubsetEqual 8930\nNotSquareSuperset 8848,824\n"
+ "NotSquareSupersetEqual 8931\nNotSubset 8834,8402\nNotSubsetEqual 8840\nNotSucceeds 8833\n"
+ "NotSucceedsEqual 10928,824\nNotSucceedsSlantEqual 8929\nNotSucceedsTilde 8831,824\n"
+ "NotSuperset 8835,8402\nNotSupersetEqual 8841\nNotTilde 8769\nNotTildeEqual 8772\n"
+ "NotTildeFullEqual 8775\nNotTildeTilde 8777\nNotVerticalBar 8740\nNscr 119977\nNtilde 209\n"
+ "Nu 925\nOElig 338\nOacute 211\nOcirc 212\nOcy 1054\nOdblac 336\nOfr 120082\nOgrave 210\n"
+ "Omacr 332\nOmega 937\nOmicron 927\nOopf 120134\nOpenCurlyDoubleQuote 8220\n"
+ "OpenCurlyQuote 8216\nOr 10836\nOscr 119978\nOslash 216\nOtilde 213\nOtimes 10807\n"
+ "Ouml 214\nOverBar 8254\nOverBrace 9182\nOverBracket 9140\nOverParenthesis 9180\n"
+ "PartialD 8706\nPcy 1055\nPfr 120083\nPhi 934\nPi 928\nPlusMinus 177\nPoincareplane 8460\n"
+ "Popf 8473\nPr 10939\nPrecedes 8826\nPrecedesEqual 10927\nPrecedesSlantEqual 8828\n"
+ "PrecedesTilde 8830\nPrime 8243\nProduct 8719\nProportion 8759\nProportional 8733\n"
+ "Pscr 119979\nPsi 936\nQUOT 34\nQfr 120084\nQopf 8474\nQscr 119980\nRBarr 10512\nREG 174\n"
+ "Racute 340\nRang 10219\nRarr 8608\nRarrtl 10518\nRcaron 344\nRcedil 342\nRcy 1056\n"
+ "Re 8476\nReverseElement 8715\nReverseEquilibrium 8651\nReverseUpEquilibrium 10607\n"
+ "Rfr 8476\nRho 929\nRightAngleBracket 10217\nRightArrow 8594\nRightArrowBar 8677\n"
+ "RightArrowLeftArrow 8644\nRightCeiling 8969\nRightDoubleBracket 10215\n"
+ "RightDownTeeVector 10589\nRightDownVector 8642\nRightDownVectorBar 10581\n"
+ "RightFloor 8971\nRightTee 8866\nRightTeeArrow 8614\nRightTeeVector 10587\n"
+ "RightTriangle 8883\nRightTriangleBar 10704\nRightTriangleEqual 8885\n"
+ "RightUpDownVector 10575\nRightUpTeeVector 10588\nRightUpVector 8638\n"
+ "RightUpVectorBar 10580\nRightVector 8640\nRightVectorBar 10579\nRightarrow 8658\n"
+ "Ropf 8477\nRoundImplies 10608\nRrightarrow 8667\nRscr 8475\nRsh 8625\nRuleDelayed 10740\n"
+ "SHCHcy 1065\nSHcy 1064\nSOFTcy 1068\nSacute 346\nSc 10940\nScaron 352\nScedil 350\n"
+ "Scirc 348\nScy 1057\nSfr 120086\nShortDownArrow 8595\nShortLeftArrow 8592\n"
+ "ShortRightArrow 8594\nShortUpArrow 8593\nSigma 931\nSmallCircle 8728\nSopf 120138\n"
+ "Sqrt 8730\nSquare 9633\nSquareIntersection 8851\nSquareSubset 8847\n"
+ "SquareSubsetEqual 8849\nSquareSuperset 8848\nSquareSupersetEqual 8850\nSquareUnion 8852\n"
+ "Sscr 119982\nStar 8902\nSub 8912\nSubset 8912\nSubsetEqual 8838\nSucceeds 8827\n"
+ "SucceedsEqual 10928\nSucceedsSlantEqual 8829\nSucceedsTilde 8831\nSuchThat 8715\n"
+ "Sum 8721\nSup 8913\nSuperset 8835\nSupersetEqual 8839\nSupset 8913\nTHORN 222\n"
+ "TRADE 8482\nTSHcy 1035\nTScy 1062\nTab 9\nTau 932\nTcaron 356\nTcedil 354\nTcy 1058\n"
+ "Tfr 120087\nTherefore 8756\nTheta 920\nThickSpace 8287,8202\nThinSpace 8201\nTilde 8764\n"
+ "TildeEqual 8771\nTildeFullEqual 8773\nTildeTilde 8776\nTopf 120139\nTripleDot 8411\n"
+ "Tscr 119983\nTstrok 358\nUacute 218\nUarr 8607\nUarrocir 10569\nUbrcy 1038\nUbreve 364\n"
+ "Ucirc 219\nUcy 1059\nUdblac 368\nUfr 120088\nUgrave 217\nUmacr 362\nUnderBar 95\n"
+ "UnderBrace 9183\nUnderBracket 9141\nUnderParenthesis 9181\nUnion 8899\nUnionPlus 8846\n"
+ "Uogon 370\nUopf 120140\nUpArrow 8593\nUpArrowBar 10514\nUpArrowDownArrow 8645\n"
+ "UpDownArrow 8597\nUpEquilibrium 10606\nUpTee 8869\nUpTeeArrow 8613\nUparrow 8657\n"
+ "Updownarrow 8661\nUpperLeftArrow 8598\nUpperRightArrow 8599\nUpsi 978\nUpsilon 933\n"
+ "Uring 366\nUscr 119984\nUtilde 360\nUuml 220\nVDash 8875\nVbar 10987\nVcy 1042\n"
+ "Vdash 8873\nVdashl 10982\nVee 8897\nVerbar 8214\nVert 8214\nVerticalBar 8739\n"
+ "VerticalLine 124\nVerticalSeparator 10072\nVerticalTilde 8768\nVeryThinSpace 8202\n"
+ "Vfr 120089\nVopf 120141\nVscr 119985\nVvdash 8874\nWcirc 372\nWedge 8896\nWfr 120090\n"
+ "Wopf 120142\nWscr 119986\nXfr 120091\nXi 926\nXopf 120143\nXscr 119987\nYAcy 1071\n"
+ "YIcy 1031\nYUcy 1070\nYacute 221\nYcirc 374\nYcy 1067\nYfr 120092\nYopf 120144\n"
+ "Yscr 119988\nYuml 376\nZHcy 1046\nZacute 377\nZcaron 381\nZcy 1047\nZdot 379\n"
+ "ZeroWidthSpace 8203\nZeta 918\nZfr 8488\nZopf 8484\nZscr 119989\naacute 225\nabreve 259\n"
+ "ac 8766\nacE 8766,819\nacd 8767\nacirc 226\nacute 180\nacy 1072\naelig 230\naf 8289\n"
+ "afr 120094\nagrave 224\nalefsym 8501\naleph 8501\nalpha 945\namacr 257\namalg 10815\n"
+ "amp 38\nand 8743\nandand 10837\nandd 10844\nandslope 10840\nandv 10842\nang 8736\n"
+ "ange 10660\nangle 8736\nangmsd 8737\nangmsdaa 10664\nangmsdab 10665\nangmsdac 10666\n"
+ "angmsdad 10667\nangmsdae 10668\nangmsdaf 10669\nangmsdag 10670\nangmsdah 10671\n"
+ "angrt 8735\nangrtvb 8894\nangrtvbd 10653\nangsph 8738\nangst 197\nangzarr 9084\n"
+ "aogon 261\naopf 120146\nap 8776\napE 10864\napacir 10863\nape 8778\napid 8779\napos 39\n"
+ "approx 8776\napproxeq 8778\naring 229\nascr 119990\nast 42\nasymp 8776\nasympeq 8781\n"
+ "atilde 227\nauml 228\nawconint 8755\nawint 10769\nbNot 10989\nbackcong 8780\n"
+ "backepsilon 1014\nbackprime 8245\nbacksim 8765\nbacksimeq 8909\nbarvee 8893\nbarwed 8965\n"
+ "barwedge 8965\nbbrk 9141\nbbrktbrk 9142\nbcong 8780\nbcy 1073\nbdquo 8222\nbecaus 8757\n"
+ "because 8757\nbemptyv 10672\nbepsi 1014\nbernou 8492\nbeta 946\nbeth 8502\nbetween 8812\n"
+ "bfr 120095\nbigcap 8898\nbigcirc 9711\nbigcup 8899\nbigodot 10752\nbigoplus 10753\n"
+ "bigotimes 10754\nbigsqcup 10758\nbigstar 9733\nbigtriangledown 9661\nbigtriangleup 9651\n"
+ "biguplus 10756\nbigvee 8897\nbigwedge 8896\nbkarow 10509\nblacklozenge 10731\n"
+ "blacksquare 9642\nblacktriangle 9652\nblacktriangledown 9662\nblacktriangleleft 9666\n"
+ "blacktriangleright 9656\nblank 9251\nblk12 9618\nblk14 9617\nblk34 9619\nblock 9608\n"
+ "bne 61,8421\nbnequiv 8801,8421\nbnot 8976\nbopf 120147\nbot 8869\nbottom 8869\n"
+ "bowtie 8904\nboxDL 9559\nboxDR 9556\nboxDl 9558\nboxDr 9555\nboxH 9552\nboxHD 9574\n"
+ "boxHU 9577\nboxHd 9572\nboxHu 9575\nboxUL 9565\nboxUR 9562\nboxUl 9564\nboxUr 9561\n"
+ "boxV 9553\nboxVH 9580\nboxVL 9571\nboxVR 9568\nboxVh 9579\nboxVl 9570\nboxVr 9567\n"
+ "boxbox 10697\nboxdL 9557\nboxdR 9554\nboxdl 9488\nboxdr 9484\nboxh 9472\nboxhD 9573\n"
+ "boxhU 9576\nboxhd 9516\nboxhu 9524\nboxminus 8863\nboxplus 8862\nboxtimes 8864\n"
+ "boxuL 9563\nboxuR 9560\nboxul 9496\nboxur 9492\nboxv 9474\nboxvH 9578\nboxvL 9569\n"
+ "boxvR 9566\nboxvh 9532\nboxvl 9508\nboxvr 9500\nbprime 8245\nbreve 728\nbrvbar 166\n"
+ "bscr 119991\nbsemi 8271\nbsim 8765\nbsime 8909\nbsol 92\nbsolb 10693\nbsolhsub 10184\n"
+ "bull 8226\nbullet 8226\nbump 8782\nbumpE 10926\nbumpe 8783\nbumpeq 8783\ncacute 263\n"
+ "cap 8745\ncapand 10820\ncapbrcup 10825\ncapcap 10827\ncapcup 10823\ncapdot 10816\n"
+ "caps 8745,65024\ncaret 8257\ncaron 711\nccaps 10829\nccaron 269\nccedil 231\nccirc 265\n"
+ "ccups 10828\nccupssm 10832\ncdot 267\ncedil 184\ncemptyv 10674\ncent 162\ncenterdot 183\n"
+ "cfr 120096\nchcy 1095\ncheck 10003\ncheckmark 10003\nchi 967\ncir 9675\ncirE 10691\n"
+ "circ 710\ncirceq 8791\ncirclearrowleft 8634\ncirclearrowright 8635\ncircledR 174\n"
+ "circledS 9416\ncircledast 8859\ncircledcirc 8858\ncircleddash 8861\ncire 8791\n"
+ "cirfnint 10768\ncirmid 10991\ncirscir 10690\nclubs 9827\nclubsuit 9827\ncolon 58\n"
+ "colone 8788\ncoloneq 8788\ncomma 44\ncommat 64\ncomp 8705\ncompfn 8728\ncomplement 8705\n"
+ "complexes 8450\ncong 8773\ncongdot 10861\nconint 8750\ncopf 120148\ncoprod 8720\n"
+ "copy 169\ncopysr 8471\ncrarr 8629\ncross 10007\ncscr 119992\ncsub 10959\ncsube 10961\n"
+ "csup 10960\ncsupe 10962\nctdot 8943\ncudarrl 10552\ncudarrr 10549\ncuepr 8926\n"
+ "cuesc 8927\ncularr 8630\ncularrp 10557\ncup 8746\ncupbrcap 10824\ncupcap 10822\n"
+ "cupcup 10826\ncupdot 8845\ncupor 10821\ncups 8746,65024\ncurarr 8631\ncurarrm 10556\n"
+ "curlyeqprec 8926\ncurlyeqsucc 8927\ncurlyvee 8910\ncurlywedge 8911\ncurren 164\n"
+ "curvearrowleft 8630\ncurvearrowright 8631\ncuvee 8910\ncuwed 8911\ncwconint 8754\n"
+ "cwint 8753\ncylcty 9005\ndArr 8659\ndHar 10597\ndagger 8224\ndaleth 8504\ndarr 8595\n"
+ "dash 8208\ndashv 8867\ndbkarow 10511\ndblac 733\ndcaron 271\ndcy 1076\ndd 8518\n"
+ "ddagger 8225\nddarr 8650\nddotseq 10871\ndeg 176\ndelta 948\ndemptyv 10673\ndfisht 10623\n"
+ "dfr 120097\ndharl 8643\ndharr 8642\ndiam 8900\ndiamond 8900\ndiamondsuit 9830\n"
+ "diams 9830\ndie 168\ndigamma 989\ndisin 8946\ndiv 247\ndivide 247\ndivideontimes 8903\n"
+ "divonx 8903\ndjcy 1106\ndlcorn 8990\ndlcrop 8973\ndollar 36\ndopf 120149\ndot 729\n"
+ "doteq 8784\ndoteqdot 8785\ndotminus 8760\ndotplus 8724\ndotsquare 8865\n"
+ "doublebarwedge 8966\ndownarrow 8595\ndowndownarrows 8650\ndownharpoonleft 8643\n"
+ "downharpoonright 8642\ndrbkarow 10512\ndrcorn 8991\ndrcrop 8972\ndscr 119993\ndscy 1109\n"
+ "dsol 10742\ndstrok 273\ndtdot 8945\ndtri 9663\ndtrif 9662\nduarr 8693\nduhar 10607\n"
+ "dwangle 10662\ndzcy 1119\ndzigrarr 10239\neDDot 10871\neDot 8785\neacute 233\n"
+ "easter 10862\necaron 283\necir 8790\necirc 234\necolon 8789\necy 1101\nedot 279\nee 8519\n"
+ "efDot 8786\nefr 120098\neg 10906\negrave 232\negs 10902\negsdot 10904\nel 10905\n"
+ "elinters 9191\nell 8467\nels 10901\nelsdot 10903\nemacr 275\nempty 8709\nemptyset 8709\n"
+ "emptyv 8709\nemsp 8195\nemsp13 8196\nemsp14 8197\neng 331\nensp 8194\neogon 281\n"
+ "eopf 120150\nepar 8917\neparsl 10723\neplus 10865\nepsi 949\nepsilon 949\nepsiv 1013\n"
+ "eqcirc 8790\neqcolon 8789\neqsim 8770\neqslantgtr 10902\neqslantless 10901\nequals 61\n"
+ "equest 8799\nequiv 8801\nequivDD 10872\neqvparsl 10725\nerDot 8787\nerarr 10609\n"
+ "escr 8495\nesdot 8784\nesim 8770\neta 951\neth 240\neuml 235\neuro 8364\nexcl 33\n"
+ "exist 8707\nexpectation 8496\nexponentiale 8519\nfallingdotseq 8786\nfcy 1092\n"
+ "female 9792\nffilig 64259\nfflig 64256\nffllig 64260\nffr 120099\nfilig 64257\n"
+ "fjlig 102,106\nflat 9837\nfllig 64258\nfltns 9649\nfnof 402\nfopf 120151\nforall 8704\n"
+ "fork 8916\nforkv 10969\nfpartint 10765\nfrac12 189\nfrac13 8531\nfrac14 188\nfrac15 8533\n"
+ "frac16 8537\nfrac18 8539\nfrac23 8532\nfrac25 8534\nfrac34 190\nfrac35 8535\nfrac38 8540\n"
+ "frac45 8536\nfrac56 8538\nfrac58 8541\nfrac78 8542\nfrasl 8260\nfrown 8994\nfscr 119995\n"
+ "gE 8807\ngEl 10892\ngacute 501\ngamma 947\ngammad 989\ngap 10886\ngbreve 287\ngcirc 285\n"
+ "gcy 1075\ngdot 289\nge 8805\ngel 8923\ngeq 8805\ngeqq 8807\ngeqslant 10878\nges 10878\n"
+ "gescc 10921\ngesdot 10880\ngesdoto 10882\ngesdotol 10884\ngesl 8923,65024\ngesles 10900\n"
+ "gfr 120100\ngg 8811\nggg 8921\ngimel 8503\ngjcy 1107\ngl 8823\nglE 10898\ngla 10917\n"
+ "glj 10916\ngnE 8809\ngnap 10890\ngnapprox 10890\ngne 10888\ngneq 10888\ngneqq 8809\n"
+ "gnsim 8935\ngopf 120152\ngrave 96\ngscr 8458\ngsim 8819\ngsime 10894\ngsiml 10896\ngt 62\n"
+ "gtcc 10919\ngtcir 10874\ngtdot 8919\ngtlPar 10645\ngtquest 10876\ngtrapprox 10886\n"
+ "gtrarr 10616\ngtrdot 8919\ngtreqless 8923\ngtreqqless 10892\ngtrless 8823\ngtrsim 8819\n"
+ "gvertneqq 8809,65024\ngvnE 8809,65024\nhArr 8660\nhairsp 8202\nhalf 189\nhamilt 8459\n"
+ "hardcy 1098\nharr 8596\nharrcir 10568\nharrw 8621\nhbar 8463\nhcirc 293\nhearts 9829\n"
+ "heartsuit 9829\nhellip 8230\nhercon 8889\nhfr 120101\nhksearow 10533\nhkswarow 10534\n"
+ "hoarr 8703\nhomtht 8763\nhookleftarrow 8617\nhookrightarrow 8618\nhopf 120153\n"
+ "horbar 8213\nhscr 119997\nhslash 8463\nhstrok 295\nhybull 8259\nhyphen 8208\niacute 237\n"
+ "ic 8291\nicirc 238\nicy 1080\niecy 1077\niexcl 161\niff 8660\nifr 120102\nigrave 236\n"
+ "ii 8520\niiiint 10764\niiint 8749\niinfin 10716\niiota 8489\nijlig 307\nimacr 299\n"
+ "image 8465\nimagline 8464\nimagpart 8465\nimath 305\nimof 8887\nimped 437\nin 8712\n"
+ "incare 8453\ninfin 8734\ninfintie 10717\ninodot 305\nint 8747\nintcal 8890\n"
+ "integers 8484\nintercal 8890\nintlarhk 10775\nintprod 10812\niocy 1105\niogon 303\n"
+ "iopf 120154\niota 953\niprod 10812\niquest 191\niscr 119998\nisin 8712\nisinE 8953\n"
+ "isindot 8949\nisins 8948\nisinsv 8947\nisinv 8712\nit 8290\nitilde 297\niukcy 1110\n"
+ "iuml 239\njcirc 309\njcy 1081\njfr 120103\njmath 567\njopf 120155\njscr 119999\n"
+ "jsercy 1112\njukcy 1108\nkappa 954\nkappav 1008\nkcedil 311\nkcy 1082\nkfr 120104\n"
+ "kgreen 312\nkhcy 1093\nkjcy 1116\nkopf 120156\nkscr 120000\nlAarr 8666\nlArr 8656\n"
+ "lAtail 10523\nlBarr 10510\nlE 8806\nlEg 10891\nlHar 10594\nlacute 314\nlaemptyv 10676\n"
+ "lagran 8466\nlambda 955\nlang 10216\nlangd 10641\nlangle 10216\nlap 10885\nlaquo 171\n"
+ "larr 8592\nlarrb 8676\nlarrbfs 10527\nlarrfs 10525\nlarrhk 8617\nlarrlp 8619\n"
+ "larrpl 10553\nlarrsim 10611\nlarrtl 8610\nlat 10923\nlatail 10521\nlate 10925\n"
+ "lates 10925,65024\nlbarr 10508\nlbbrk 10098\nlbrace 123\nlbrack 91\nlbrke 10635\n"
+ "lbrksld 10639\nlbrkslu 10637\nlcaron 318\nlcedil 316\nlceil 8968\nlcub 123\nlcy 1083\n"
+ "ldca 10550\nldquo 8220\nldquor 8222\nldrdhar 10599\nldrushar 10571\nldsh 8626\nle 8804\n"
+ "leftarrow 8592\nleftarrowtail 8610\nleftharpoondown 8637\nleftharpoonup 8636\n"
+ "leftleftarrows 8647\nleftrightarrow 8596\nleftrightarrows 8646\nleftrightharpoons 8651\n"
+ "leftrightsquigarrow 8621\nleftthreetimes 8907\nleg 8922\nleq 8804\nleqq 8806\n"
+ "leqslant 10877\nles 10877\nlescc 10920\nlesdot 10879\nlesdoto 10881\nlesdotor 10883\n"
+ "lesg 8922,65024\nlesges 10899\nlessapprox 10885\nlessdot 8918\nlesseqgtr 8922\n"
+ "lesseqqgtr 10891\nlessgtr 8822\nlesssim 8818\nlfisht 10620\nlfloor 8970\nlfr 120105\n"
+ "lg 8822\nlgE 10897\nlhard 8637\nlharu 8636\nlharul 10602\nlhblk 9604\nljcy 1113\nll 8810\n"
+ "llarr 8647\nllcorner 8990\nllhard 10603\nlltri 9722\nlmidot 320\nlmoust 9136\n"
+ "lmoustache 9136\nlnE 8808\nlnap 10889\nlnapprox 10889\nlne 10887\nlneq 10887\nlneqq 8808\n"
+ "lnsim 8934\nloang 10220\nloarr 8701\nlobrk 10214\nlongleftarrow 10229\n"
+ "longleftrightarrow 10231\nlongmapsto 10236\nlongrightarrow 10230\nlooparrowleft 8619\n"
+ "looparrowright 8620\nlopar 10629\nlopf 120157\nloplus 10797\nlotimes 10804\nlowast 8727\n"
+ "lowbar 95\nloz 9674\nlozenge 9674\nlozf 10731\nlpar 40\nlparlt 10643\nlrarr 8646\n"
+ "lrcorner 8991\nlrhar 8651\nlrhard 10605\nlrm 8206\nlrtri 8895\nlsaquo 8249\nlscr 120001\n"
+ "lsh 8624\nlsim 8818\nlsime 10893\nlsimg 10895\nlsqb 91\nlsquo 8216\nlsquor 8218\n"
+ "lstrok 322\nlt 60\nltcc 10918\nltcir 10873\nltdot 8918\nlthree 8907\nltimes 8905\n"
+ "ltlarr 10614\nltquest 10875\nltrPar 10646\nltri 9667\nltrie 8884\nltrif 9666\n"
+ "lurdshar 10570\nluruhar 10598\nlvertneqq 8808,65024\nlvnE 8808,65024\nmDDot 8762\n"
+ "macr 175\nmale 9794\nmalt 10016\nmaltese 10016\nmap 8614\nmapsto 8614\nmapstodown 8615\n"
+ "mapstoleft 8612\nmapstoup 8613\nmarker 9646\nmcomma 10793\nmcy 1084\nmdash 8212\n"
+ "measuredangle 8737\nmfr 120106\nmho 8487\nmicro 181\nmid 8739\nmidast 42\nmidcir 10992\n"
+ "middot 183\nminus 8722\nminusb 8863\nminusd 8760\nminusdu 10794\nmlcp 10971\nmldr 8230\n"
+ "mnplus 8723\nmodels 8871\nmopf 120158\nmp 8723\nmscr 120002\nmstpos 8766\nmu 956\n"
+ "multimap 8888\nmumap 8888\nnGg 8921,824\nnGt 8811,8402\nnGtv 8811,824\nnLeftarrow 8653\n"
+ "nLeftrightarrow 8654\nnLl 8920,824\nnLt 8810,8402\nnLtv 8810,824\nnRightarrow 8655\n"
+ "nVDash 8879\nnVdash 8878\nnabla 8711\nnacute 324\nnang 8736,8402\nnap 8777\n"
+ "napE 10864,824\nnapid 8779,824\nnapos 329\nnapprox 8777\nnatur 9838\nnatural 9838\n"
+ "naturals 8469\nnbsp 160\nnbump 8782,824\nnbumpe 8783,824\nncap 10819\nncaron 328\n"
+ "ncedil 326\nncong 8775\nncongdot 10861,824\nncup 10818\nncy 1085\nndash 8211\nne 8800\n"
+ "neArr 8663\nnearhk 10532\nnearr 8599\nnearrow 8599\nnedot 8784,824\nnequiv 8802\n"
+ "nesear 10536\nnesim 8770,824\nnexist 8708\nnexists 8708\nnfr 120107\nngE 8807,824\n"
+ "nge 8817\nngeq 8817\nngeqq 8807,824\nngeqslant 10878,824\nnges 10878,824\nngsim 8821\n"
+ "ngt 8815\nngtr 8815\nnhArr 8654\nnharr 8622\nnhpar 10994\nni 8715\nnis 8956\nnisd 8954\n"
+ "niv 8715\nnjcy 1114\nnlArr 8653\nnlE 8806,824\nnlarr 8602\nnldr 8229\nnle 8816\n"
+ "nleftarrow 8602\nnleftrightarrow 8622\nnleq 8816\nnleqq 8806,824\nnleqslant 10877,824\n"
+ "nles 10877,824\nnless 8814\nnlsim 8820\nnlt 8814\nnltri 8938\nnltrie 8940\nnmid 8740\n"
+ "nopf 120159\nnot 172\nnotin 8713\nnotinE 8953,824\nnotindot 8949,824\nnotinva 8713\n"
+ "notinvb 8951\nnotinvc 8950\nnotni 8716\nnotniva 8716\nnotnivb 8958\nnotnivc 8957\n"
+ "npar 8742\nnparallel 8742\nnparsl 11005,8421\nnpart 8706,824\nnpolint 10772\nnpr 8832\n"
+ "nprcue 8928\nnpre 10927,824\nnprec 8832\nnpreceq 10927,824\nnrArr 8655\nnrarr 8603\n"
+ "nrarrc 10547,824\nnrarrw 8605,824\nnrightarrow 8603\nnrtri 8939\nnrtrie 8941\nnsc 8833\n"
+ "nsccue 8929\nnsce 10928,824\nnscr 120003\nnshortmid 8740\nnshortparallel 8742\nnsim 8769\n"
+ "nsime 8772\nnsimeq 8772\nnsmid 8740\nnspar 8742\nnsqsube 8930\nnsqsupe 8931\nnsub 8836\n"
+ "nsubE 10949,824\nnsube 8840\nnsubset 8834,8402\nnsubseteq 8840\nnsubseteqq 10949,824\n"
+ "nsucc 8833\nnsucceq 10928,824\nnsup 8837\nnsupE 10950,824\nnsupe 8841\nnsupset 8835,8402\n"
+ "nsupseteq 8841\nnsupseteqq 10950,824\nntgl 8825\nntilde 241\nntlg 8824\n"
+ "ntriangleleft 8938\nntrianglelefteq 8940\nntriangleright 8939\nntrianglerighteq 8941\n"
+ "nu 957\nnum 35\nnumero 8470\nnumsp 8199\nnvDash 8877\nnvHarr 10500\nnvap 8781,8402\n"
+ "nvdash 8876\nnvge 8805,8402\nnvgt 62,8402\nnvinfin 10718\nnvlArr 10498\nnvle 8804,8402\n"
+ "nvlt 60,8402\nnvltrie 8884,8402\nnvrArr 10499\nnvrtrie 8885,8402\nnvsim 8764,8402\n"
+ "nwArr 8662\nnwarhk 10531\nnwarr 8598\nnwarrow 8598\nnwnear 10535\noS 9416\noacute 243\n"
+ "oast 8859\nocir 8858\nocirc 244\nocy 1086\nodash 8861\nodblac 337\nodiv 10808\nodot 8857\n"
+ "odsold 10684\noelig 339\nofcir 10687\nofr 120108\nogon 731\nograve 242\nogt 10689\n"
+ "ohbar 10677\nohm 937\noint 8750\nolarr 8634\nolcir 10686\nolcross 10683\noline 8254\n"
+ "olt 10688\nomacr 333\nomega 969\nomicron 959\nomid 10678\nominus 8854\noopf 120160\n"
+ "opar 10679\noperp 10681\noplus 8853\nor 8744\norarr 8635\nord 10845\norder 8500\n"
+ "orderof 8500\nordf 170\nordm 186\norigof 8886\noror 10838\norslope 10839\norv 10843\n"
+ "oscr 8500\noslash 248\nosol 8856\notilde 245\notimes 8855\notimesas 10806\nouml 246\n"
+ "ovbar 9021\npar 8741\npara 182\nparallel 8741\nparsim 10995\nparsl 11005\npart 8706\n"
+ "pcy 1087\npercnt 37\nperiod 46\npermil 8240\nperp 8869\npertenk 8241\npfr 120109\n"
+ "phi 966\nphiv 981\nphmmat 8499\nphone 9742\npi 960\npitchfork 8916\npiv 982\nplanck 8463\n"
+ "planckh 8462\nplankv 8463\nplus 43\nplusacir 10787\nplusb 8862\npluscir 10786\n"
+ "plusdo 8724\nplusdu 10789\npluse 10866\nplusmn 177\nplussim 10790\nplustwo 10791\npm 177\n"
+ "pointint 10773\npopf 120161\npound 163\npr 8826\nprE 10931\nprap 10935\nprcue 8828\n"
+ "pre 10927\nprec 8826\nprecapprox 10935\npreccurlyeq 8828\npreceq 10927\n"
+ "precnapprox 10937\nprecneqq 10933\nprecnsim 8936\nprecsim 8830\nprime 8242\nprimes 8473\n"
+ "prnE 10933\nprnap 10937\nprnsim 8936\nprod 8719\nprofalar 9006\nprofline 8978\n"
+ "profsurf 8979\nprop 8733\npropto 8733\nprsim 8830\nprurel 8880\npscr 120005\npsi 968\n"
+ "puncsp 8200\nqfr 120110\nqint 10764\nqopf 120162\nqprime 8279\nqscr 120006\n"
+ "quaternions 8461\nquatint 10774\nquest 63\nquesteq 8799\nquot 34\nrAarr 8667\nrArr 8658\n"
+ "rAtail 10524\nrBarr 10511\nrHar 10596\nrace 8765,817\nracute 341\nradic 8730\n"
+ "raemptyv 10675\nrang 10217\nrangd 10642\nrange 10661\nrangle 10217\nraquo 187\nrarr 8594\n"
+ "rarrap 10613\nrarrb 8677\nrarrbfs 10528\nrarrc 10547\nrarrfs 10526\nrarrhk 8618\n"
+ "rarrlp 8620\nrarrpl 10565\nrarrsim 10612\nrarrtl 8611\nrarrw 8605\nratail 10522\n"
+ "ratio 8758\nrationals 8474\nrbarr 10509\nrbbrk 10099\nrbrace 125\nrbrack 93\nrbrke 10636\n"
+ "rbrksld 10638\nrbrkslu 10640\nrcaron 345\nrcedil 343\nrceil 8969\nrcub 125\nrcy 1088\n"
+ "rdca 10551\nrdldhar 10601\nrdquo 8221\nrdquor 8221\nrdsh 8627\nreal 8476\nrealine 8475\n"
+ "realpart 8476\nreals 8477\nrect 9645\nreg 174\nrfisht 10621\nrfloor 8971\nrfr 120111\n"
+ "rhard 8641\nrharu 8640\nrharul 10604\nrho 961\nrhov 1009\nrightarrow 8594\n"
+ "rightarrowtail 8611\nrightharpoondown 8641\nrightharpoonup 8640\nrightleftarrows 8644\n"
+ "rightleftharpoons 8652\nrightrightarrows 8649\nrightsquigarrow 8605\n"
+ "rightthreetimes 8908\nring 730\nrisingdotseq 8787\nrlarr 8644\nrlhar 8652\nrlm 8207\n"
+ "rmoust 9137\nrmoustache 9137\nrnmid 10990\nroang 10221\nroarr 8702\nrobrk 10215\n"
+ "ropar 10630\nropf 120163\nroplus 10798\nrotimes 10805\nrpar 41\nrpargt 10644\n"
+ "rppolint 10770\nrrarr 8649\nrsaquo 8250\nrscr 120007\nrsh 8625\nrsqb 93\nrsquo 8217\n"
+ "rsquor 8217\nrthree 8908\nrtimes 8906\nrtri 9657\nrtrie 8885\nrtrif 9656\nrtriltri 10702\n"
+ "ruluhar 10600\nrx 8478\nsacute 347\nsbquo 8218\nsc 8827\nscE 10932\nscap 10936\n"
+ "scaron 353\nsccue 8829\nsce 10928\nscedil 351\nscirc 349\nscnE 10934\nscnap 10938\n"
+ "scnsim 8937\nscpolint 10771\nscsim 8831\nscy 1089\nsdot 8901\nsdotb 8865\nsdote 10854\n"
+ "seArr 8664\nsearhk 10533\nsearr 8600\nsearrow 8600\nsect 167\nsemi 59\nseswar 10537\n"
+ "setminus 8726\nsetmn 8726\nsext 10038\nsfr 120112\nsfrown 8994\nsharp 9839\nshchcy 1097\n"
+ "shcy 1096\nshortmid 8739\nshortparallel 8741\nshy 173\nsigma 963\nsigmaf 962\nsigmav 962\n"
+ "sim 8764\nsimdot 10858\nsime 8771\nsimeq 8771\nsimg 10910\nsimgE 10912\nsiml 10909\n"
+ "simlE 10911\nsimne 8774\nsimplus 10788\nsimrarr 10610\nslarr 8592\nsmallsetminus 8726\n"
+ "smashp 10803\nsmeparsl 10724\nsmid 8739\nsmile 8995\nsmt 10922\nsmte 10924\n"
+ "smtes 10924,65024\nsoftcy 1100\nsol 47\nsolb 10692\nsolbar 9023\nsopf 120164\n"
+ "spades 9824\nspadesuit 9824\nspar 8741\nsqcap 8851\nsqcaps 8851,65024\nsqcup 8852\n"
+ "sqcups 8852,65024\nsqsub 8847\nsqsube 8849\nsqsubset 8847\nsqsubseteq 8849\nsqsup 8848\n"
+ "sqsupe 8850\nsqsupset 8848\nsqsupseteq 8850\nsqu 9633\nsquare 9633\nsquarf 9642\n"
+ "squf 9642\nsrarr 8594\nsscr 120008\nssetmn 8726\nssmile 8995\nsstarf 8902\nstar 9734\n"
+ "starf 9733\nstraightepsilon 1013\nstraightphi 981\nstrns 175\nsub 8834\nsubE 10949\n"
+ "subdot 10941\nsube 8838\nsubedot 10947\nsubmult 10945\nsubnE 10955\nsubne 8842\n"
+ "subplus 10943\nsubrarr 10617\nsubset 8834\nsubseteq 8838\nsubseteqq 10949\n"
+ "subsetneq 8842\nsubsetneqq 10955\nsubsim 10951\nsubsub 10965\nsubsup 10963\nsucc 8827\n"
+ "succapprox 10936\nsucccurlyeq 8829\nsucceq 10928\nsuccnapprox 10938\nsuccneqq 10934\n"
+ "succnsim 8937\nsuccsim 8831\nsum 8721\nsung 9834\nsup 8835\nsup1 185\nsup2 178\nsup3 179\n"
+ "supE 10950\nsupdot 10942\nsupdsub 10968\nsupe 8839\nsupedot 10948\nsuphsol 10185\n"
+ "suphsub 10967\nsuplarr 10619\nsupmult 10946\nsupnE 10956\nsupne 8843\nsupplus 10944\n"
+ "supset 8835\nsupseteq 8839\nsupseteqq 10950\nsupsetneq 8843\nsupsetneqq 10956\n"
+ "supsim 10952\nsupsub 10964\nsupsup 10966\nswArr 8665\nswarhk 10534\nswarr 8601\n"
+ "swarrow 8601\nswnwar 10538\nszlig 223\ntarget 8982\ntau 964\ntbrk 9140\ntcaron 357\n"
+ "tcedil 355\ntcy 1090\ntdot 8411\ntelrec 8981\ntfr 120113\nthere4 8756\ntherefore 8756\n"
+ "theta 952\nthetasym 977\nthetav 977\nthickapprox 8776\nthicksim 8764\nthinsp 8201\n"
+ "thkap 8776\nthksim 8764\nthorn 254\ntilde 732\ntimes 215\ntimesb 8864\ntimesbar 10801\n"
+ "timesd 10800\ntint 8749\ntoea 10536\ntop 8868\ntopbot 9014\ntopcir 10993\ntopf 120165\n"
+ "topfork 10970\ntosa 10537\ntprime 8244\ntrade 8482\ntriangle 9653\ntriangledown 9663\n"
+ "triangleleft 9667\ntrianglelefteq 8884\ntriangleq 8796\ntriangleright 9657\n"
+ "trianglerighteq 8885\ntridot 9708\ntrie 8796\ntriminus 10810\ntriplus 10809\ntrisb 10701\n"
+ "tritime 10811\ntrpezium 9186\ntscr 120009\ntscy 1094\ntshcy 1115\ntstrok 359\ntwixt 8812\n"
+ "twoheadleftarrow 8606\ntwoheadrightarrow 8608\nuArr 8657\nuHar 10595\nuacute 250\n"
+ "uarr 8593\nubrcy 1118\nubreve 365\nucirc 251\nucy 1091\nudarr 8645\nudblac 369\n"
+ "udhar 10606\nufisht 10622\nufr 120114\nugrave 249\nuharl 8639\nuharr 8638\nuhblk 9600\n"
+ "ulcorn 8988\nulcorner 8988\nulcrop 8975\nultri 9720\numacr 363\numl 168\nuogon 371\n"
+ "uopf 120166\nuparrow 8593\nupdownarrow 8597\nupharpoonleft 8639\nupharpoonright 8638\n"
+ "uplus 8846\nupsi 965\nupsih 978\nupsilon 965\nupuparrows 8648\nurcorn 8989\n"
+ "urcorner 8989\nurcrop 8974\nuring 367\nurtri 9721\nuscr 120010\nutdot 8944\nutilde 361\n"
+ "utri 9653\nutrif 9652\nuuarr 8648\nuuml 252\nuwangle 10663\nvArr 8661\nvBar 10984\n"
+ "vBarv 10985\nvDash 8872\nvangrt 10652\nvarepsilon 1013\nvarkappa 1008\nvarnothing 8709\n"
+ "varphi 981\nvarpi 982\nvarpropto 8733\nvarr 8597\nvarrho 1009\nvarsigma 962\n"
+ "varsubsetneq 8842,65024\nvarsubsetneqq 10955,65024\nvarsupsetneq 8843,65024\n"
+ "varsupsetneqq 10956,65024\nvartheta 977\nvartriangleleft 8882\nvartriangleright 8883\n"
+ "vcy 1074\nvdash 8866\nvee 8744\nveebar 8891\nveeeq 8794\nvellip 8942\nverbar 124\n"
+ "vert 124\nvfr 120115\nvltri 8882\nvnsub 8834,8402\nvnsup 8835,8402\nvopf 120167\n"
+ "vprop 8733\nvrtri 8883\nvscr 120011\nvsubnE 10955,65024\nvsubne 8842,65024\n"
+ "vsupnE 10956,65024\nvsupne 8843,65024\nvzigzag 10650\nwcirc 373\nwedbar 10847\n"
+ "wedge 8743\nwedgeq 8793\nweierp 8472\nwfr 120116\nwopf 120168\nwp 8472\nwr 8768\n"
+ "wreath 8768\nwscr 120012\nxcap 8898\nxcirc 9711\nxcup 8899\nxdtri 9661\nxfr 120117\n"
+ "xhArr 10234\nxharr 10231\nxi 958\nxlArr 10232\nxlarr 10229\nxmap 10236\nxnis 8955\n"
+ "xodot 10752\nxopf 120169\nxoplus 10753\nxotime 10754\nxrArr 10233\nxrarr 10230\n"
+ "xscr 120013\nxsqcup 10758\nxuplus 10756\nxutri 9651\nxvee 8897\nxwedge 8896\nyacute 253\n"
+ "yacy 1103\nycirc 375\nycy 1099\nyen 165\nyfr 120118\nyicy 1111\nyopf 120170\nyscr 120014\n"
+ "yucy 1102\nyuml 255\nzacute 378\nzcaron 382\nzcy 1079\nzdot 380\nzeetrf 8488\nzeta 950\n"
+ "zfr 120119\nzhcy 1078\nzigrarr 8669\nzopf 120171\nzscr 120015\nzwj 8205\nzwnj 8204\n"
;
// ---------------------------------------------------------------------------
// Markdown block structure (CommonMark 0.31.2, section 3)
// ---------------------------------------------------------------------------
//
// A cmark-style line scanner: each line is matched against the stack of
// open "container" blocks (document > blockquote > list > item > ...),
// consuming their prefixes, then new blocks are attempted in order
// (blockquote, ATX heading, fenced code, setext underline, thematic break,
// list item, indented code). Whatever remains is appended to the deepest
// open leaf, or becomes a lazy paragraph continuation.
//
// We do not implement HTML blocks as containers: raw HTML is a security
// hazard for `innerHTML`, so HTML-block-looking paragraphs stay paragraphs
// and get escaped by the inline pass like any other text. That also means
// `