Skip to main content

BROWSER_RUNTIME_JS

Constant BROWSER_RUNTIME_JS 

Source
pub const BROWSER_RUNTIME_JS: &str = "// The browser host for a compiled LOGOS page: the `env` imports the module calls, the table that\n// names DOM nodes to the module, the patch stream a paint commits through, the event trampoline, the\n// fetch engine, the relay connection and the router.\n//\n// The caller prepends one line, `const WASM_URL = \"<module>\";`; everything else is this file.\n// `globalThis.__logosHostCensus()` reports the size of every table the host keeps, for tests and\n// devtools.\nlet inst;\nconst dv = () => new DataView(inst.exports.memory.buffer);\nconst u8 = () => new Uint8Array(inst.exports.memory.buffer);\nconst dec = new TextDecoder(), enc = new TextEncoder();\n\n// \u{2500}\u{2500} Entries into the module \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// A compiled LOGOS program reports a runtime failure by trapping. `logos_rt_fail`/`logos_rt_fail_at`\n// run immediately before the trap and leave the compiler\'s own sentence in `failMessage`. Every\n// entry into the module \u{2014} `main`, a listener, a fetch completion, a relay message, a timer, the\n// router \u{2014} goes through `guarded`, which counts how deep the entries nest. The first failure of an\n// outermost entry is captured with the message of that moment and reported once, when the\n// outermost entry unwinds; a nested entry the browser fires synchronously (a `blur` raised by a\n// commit) is covered by the entry that caused it. `failMessage` is cleared when an outermost entry\n// begins, so a report only ever carries its own entry\'s words. A failed entry leaves the document as\n// the last commit left it: a paint reaches the host only as a whole commit\'s patch stream.\nlet failMessage = \'\';\nlet depth = 0;\nlet failure = null;\n// Work that waits until no entry is on the stack \u{2014} releasing a finished fetch, and an event the\n// platform raised in the middle of one (the `focus` and `blur` of an `element.focus()` a turn\n// asked for, the `blur` of a focused node a commit removed): it reaches the page once the entry\n// that caused it has committed, and finds its listener as the page stands then.\nconst afterTurn = [];\nconst atTurnEnd = (f) => { if (depth === 0) f(); else afterTurn.push(f); };\n\n// The page clock. The page keeps its own time and decides every timer in compiled LOGOS\n// (`__lgx_tick(ms)` fires what the milliseconds that passed make due, in due order); the host says\n// how much time passed, before every outermost entry \u{2014} so a timer armed during a click counts from\n// the moment of the click \u{2014} and when the page\'s earliest timer comes due (`dom_wake_after`).\n// `clockAt` is null until `main` has run: the page\'s clock starts with the page.\nlet clockAt = null;\nlet wakeTimer = null;\n// Where the pending wake comes due on `clockAt`\'s scale, and, while a wake runs, the time it is owed:\n// the browser runs a timer no earlier than its delay on its own clock, so that much time has passed\n// when the wake runs, however short a coarsened `performance.now()` reads then. A wake `main` asks for\n// comes due its delay after the clock starts.\nlet wakeDue = null;\nlet wakeBeforeClock = null;\nlet owed = null;\nconst passTime = () => {\n  if (clockAt === null || !inst.exports.__lgx_tick) return;\n  const atLeast = owed === null ? 0 : owed - clockAt;\n  owed = null;\n  const elapsed = Math.max(Math.floor(performance.now() - clockAt), atLeast);\n  if (elapsed <= 0) return;\n  clockAt += elapsed;\n  inst.exports.__lgx_tick(BigInt(elapsed));\n  settle();\n};\n\nconst guarded = (fn) => {\n  if (depth === 0) { failMessage = \'\'; failure = null; }\n  depth++;\n  try { if (depth === 1) passTime(); return fn(); } catch (err) {\n    if (failure === null) failure = { err, why: failMessage };\n    throw err;\n  } finally {\n    depth--;\n    if (depth === 0) {\n      const f = failure;\n      failure = null;\n      if (f) reportRuntimeFailure(f.err, f.why);\n      for (const task of afterTurn.splice(0)) task();\n      if (inst && inst.exports.__logos_safepoint) inst.exports.__logos_safepoint();\n    }\n  }\n};\n\n// Call a closure handle the module handed the host, as one entry: `currentEvent` is the event the\n// module reads through `eventValue`/`eventKey`/`eventTarget` while it runs. A handle the host no\n// longer holds (`undefined`) is not called.\nlet currentEvent = null;\nconst dispatch = (handle, ev, arg = 0n) => {\n  if (handle === undefined || handle === null) return;\n  guarded(() => {\n    const prev = currentEvent;\n    currentEvent = ev;\n    try { inst.exports.__logos_call_closure(handle, arg); settle(); } finally { currentEvent = prev; }\n  });\n};\n\n// `turn` run once no entry is on the stack, for an event that may carry a dropped file. A drop\'s\n// first file is taken off its transfer while the event is being dispatched (a trusted transfer is\n// emptied once dispatch ends) and read in full BEFORE the turn runs, so `dom_event_file_*` answer\n// from the file itself, synchronously, inside the turn. A file that cannot be read is reported,\n// and no turn runs for it.\nconst atTurnEndWithFile = (ev, turn) => {\n  const files = ev && ev.dataTransfer && ev.dataTransfer.files;\n  const file = files && files.length ? files[0] : null;\n  if (!file) { atTurnEnd(turn); return; }\n  ev.__logosFile = file;\n  file.arrayBuffer().then(\n    (content) => { ev.__logosFileBytes = new Uint8Array(content); atTurnEnd(turn); },\n    (err) => reportRuntimeFailure(err, `the dropped file ${JSON.stringify(file.name)} could not be read: ${err && err.message ? err.message : err}`),\n  );\n};\n\n// A DOM event reaching one of the page\'s closure listeners: its turn runs against the closure\n// `handle()` names then.\nconst hear = (ev, handle) => atTurnEndWithFile(ev, () => dispatch(handle(), ev));\n\n// A closure handle the host stops holding \u{2014} a replaced or removed listener, a finished fetch, a\n// superseded route handler. A module that counts host references exports `__logos_release`.\nconst releaseHandle = (h) => {\n  if (h !== undefined && h !== null && inst && inst.exports.__logos_release) inst.exports.__logos_release(h);\n};\n\n// Say why the program stopped: a console line (the channel that survives a document too broken to\n// paint into), then the page\'s own `When an action fails` handler, then the failure panel. The\n// message is the module\'s own sentence; a failure the HOST raised (a malformed patch stream, a\n// DOM exception) carries its own message; a bare trap with neither says so.\nconst reportRuntimeFailure = (err, captured) => {\n  const hostMessage = err && !(err instanceof WebAssembly.RuntimeError) && err.message ? String(err.message) : \'\';\n  // What the page was doing when it trapped (a directive\'s block) names the failure, in the same\n  // sentence every tier puts in front of it.\n  let context = \'\';\n  try {\n    if (inst && inst.exports.__lgx_failure_context) context = readText(Number(inst.exports.__lgx_failure_context()));\n  } catch (_) { context = \'\'; }\n  const why = context + (captured || hostMessage || \'the program stopped without saying why\');\n  console.error(\'LOGOS runtime failure: \' + why, err);\n  // A trap leaves the instance callable and the turn\'s state unwritten (a turn writes back only at\n  // its end), so the handler runs against the record the failed turn started from. The module\n  // knows which component was mid-turn; the host only carries the message. A handler that fails\n  // must not replace the failure being reported.\n  try {\n    if (inst && inst.exports.__lgx_on_failure) {\n      inst.exports.__lgx_on_failure(allocText(why));\n      settle();\n    }\n  } catch (_) {\n    console.error(\'LOGOS: the failure handler itself failed\');\n  }\n  showFailure(why);\n};\n\n// One panel per page, reused by every failure: the latest message and a button that dismisses it.\n// `data-logos-host` marks it as the host\'s, so a hydration never claims it as page content.\nlet failurePanel = null;\nconst showFailure = (why) => {\n  try {\n    let panel = failurePanel;\n    if (!panel || !panel.isConnected) {\n      panel = document.createElement(\'div\');\n      panel.setAttribute(\'role\', \'alert\');\n      panel.setAttribute(\'data-logos-host\', \'failure\');\n      panel.style.cssText = \'position:fixed;inset:auto 0 0 0;z-index:2147483647;margin:0;padding:1rem 3rem 1rem 1.25rem;\'\n        + \'font:14px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;\'\n        + \'background:#2b1416;color:#ffd7d7;border-top:2px solid #d9534f;max-height:50vh;overflow:auto\';\n      const close = document.createElement(\'button\');\n      close.type = \'button\';\n      close.setAttribute(\'aria-label\', \'Dismiss\');\n      close.textContent = \'\u{d7}\';\n      close.style.cssText = \'position:absolute;top:.5rem;right:.75rem;background:none;border:none;color:#ffd7d7;\'\n        + \'font:20px/1 sans-serif;cursor:pointer;padding:.25rem .5rem\';\n      close.addEventListener(\'click\', () => panel.remove());\n      const title = document.createElement(\'strong\');\n      title.style.cssText = \'display:block;margin-bottom:.35rem;color:#ff9a9a\';\n      title.textContent = \'LOGOS runtime failure\';\n      const text = document.createElement(\'div\');\n      text.setAttribute(\'data-logos-why\', \'\');\n      panel.append(close, title, text);\n      (document.body || document.documentElement).appendChild(panel);\n      failurePanel = panel;\n    }\n    panel.querySelector(\'[data-logos-why]\').textContent = why;\n  } catch (_) {\n    // The document may be unusable; the console line already carried the message.\n  }\n};\n\n// \u{2500}\u{2500} Text across the host boundary \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\nconst readText = (h) => { const d = dv(); const len = d.getInt32(h, true), dp = d.getInt32(h + 8, true); return dec.decode(u8().subarray(dp, dp + len)); };\n// Read a `Seq of Text` handle: `[len][cap][data_ptr]`, each 8-byte slot holding a Text handle.\nconst readTextSeq = (h) => { const d = dv(); const n = d.getInt32(h, true), dp = d.getInt32(h + 8, true); const out = []; for (let i = 0; i < n; i++) out.push(readText(d.getInt32(dp + i * 8, true))); return out; };\n// Host text is a heap block like any other: `__logos_text_new(len)` allocates it through the\n// module\'s own allocator (a free-list class if one fits, else a bump that grows the module\'s memory\n// on demand \u{2014} see `emit_alloc`), so a string the host hands the module is visible to\n// `__logos_heap_allocated` and reachable by the release protocol exactly as a module-built Text is.\n// `allocText` is the only writer of host text, so this is the one place that matters.\nconst allocText = (s) => {\n  const by = enc.encode(s);\n  const t = inst.exports.__logos_text_new(by.length);\n  u8().set(by, dv().getInt32(t + 8, true));\n  return t;\n};\n// A list the host hands the module is built in the module\'s heap like one the module builds:\n// `__logos_seq_new(len, 0)` allocates a header over `len` zeroed 8-byte slots through the module\'s\n// own allocator, and each slot is written as the module writes an Int. `allocSeq` is the only\n// writer of a host-built list.\nconst allocSeq = (values) => {\n  const h = inst.exports.__logos_seq_new(values.length, 0);\n  const d = dv(), p = d.getInt32(h + 8, true);\n  for (let i = 0; i < values.length; i++) d.setBigInt64(p + 8 * i, BigInt(values[i]), true);\n  return h;\n};\n// The areas a page can store text in, and the only place this host decides what an area IS.\n// `local` survives closing the tab (`persisted as \"key\"`); `session` survives a reload and ends\n// with the tab (`preserved across reloads`); `shared` is the topics every page on them shares.\nconst storageArea = (name) => {\n  if (name === \'local\') return window.localStorage;\n  if (name === \'session\') return window.sessionStorage;\n  if (name === \'shared\') return sharedArea;\n  throw new Error(`I do not know a storage area called \"${name}\". A page can store text in \"local\", which survives closing the tab, \"session\", which survives a reload and ends with the tab, or \"shared\", which every page on a topic shares. Which did you mean?`);\n};\n// The \"shared\" area: `<topic>.<field>` keys every page on the topic shares. A write is kept here and\n// published on the topic (`<key> NUL <frame>`); the first touch of a topic joins it, and a frame\n// another page published there is handed to the module\'s `__lgx_shared(topic, key, frame)`, which\n// merges it and writes the result back through `storageSet`. This host moves frames and never\n// merges them.\nconst sharedCache = new Map();\nconst sharedTopics = new Set();\nconst topicOfKey = (key) => key.split(\'.\')[0];\nconst joinShared = (topic) => {\n  if (sharedTopics.has(topic)) return;\n  sharedTopics.add(topic);\n  relay.subscribe(topic, {\n    deliver(data) {\n      const at = data.indexOf(\'\\u0000\');\n      if (at < 0 || !inst || !inst.exports.__lgx_shared) return;\n      const key = data.slice(0, at), frame = data.slice(at + 1);\n      guarded(() => { inst.exports.__lgx_shared(allocText(topic), allocText(key), allocText(frame)); settle(); });\n    },\n  });\n};\nconst sharedArea = {\n  getItem(key) { joinShared(topicOfKey(key)); return sharedCache.has(key) ? sharedCache.get(key) : null; },\n  setItem(key, value) {\n    const topic = topicOfKey(key);\n    joinShared(topic);\n    sharedCache.set(key, value);\n    relay.send({ Publish: { topic, data: enc.encode(key + \'\\u0000\' + value) } });\n  },\n  removeItem(key) { sharedCache.delete(key); },\n};\n// Write a formatted value into the interpolation buffer at `buf`, returning its\n// byte length \u{2014} the shape every `fmt_*_into` host uses to build an interpolated hole.\nconst writeStr = (buf, s) => { const b = enc.encode(s); u8().set(b, buf); return b.length; };\nconst fmtF = (v) => {\n  if (Number.isNaN(v)) return \'NaN\';\n  if (v === Infinity) return \'inf\';\n  if (v === -Infinity) return \'-inf\';\n  if (v === 0) return Object.is(v, -0) ? \'-0\' : \'0\';\n  const sign = v < 0 ? \'-\' : \'\';\n  const a = Math.abs(v);\n  const s = a.toString();\n  const e = s.indexOf(\'e\');\n  const mantissa = e < 0 ? s : s.slice(0, e);\n  const dot = mantissa.indexOf(\'.\');\n  let digits = dot < 0 ? mantissa : mantissa.slice(0, dot) + mantissa.slice(dot + 1);\n  let point = (dot < 0 ? mantissa.length : dot) + (e < 0 ? 0 : Number(s.slice(e + 1)));\n  const lead = digits.length - digits.replace(/^0+/, \'\').length;\n  digits = digits.slice(lead).replace(/0+$/, \'\');\n  point -= lead;\n  const spell = (d, p) => p <= 0 ? \'0.\' + \'0\'.repeat(-p) + d : p >= d.length ? d + \'0\'.repeat(p - d.length) : d.slice(0, p) + \'.\' + d.slice(p);\n  if (Number(spell(digits + \'5\', point)) === a) {\n    const view = new DataView(new ArrayBuffer(8));\n    view.setFloat64(0, a);\n    const bits = view.getBigUint64(0);\n    const biased = Number(bits >> 52n);\n    const m = biased === 0 ? bits & 0xfffffffffffffn : (bits & 0xfffffffffffffn) | (1n << 52n);\n    const b = biased === 0 ? -1074 : biased - 1075;\n    const q = point - digits.length - 1;\n    const half = BigInt(digits + \'5\') * 10n ** BigInt(Math.max(q, 0)) * 2n ** BigInt(Math.max(-b, 0));\n    const exact = m * 2n ** BigInt(Math.max(b, 0)) * 10n ** BigInt(Math.max(-q, 0));\n    if (half === exact) {\n      const up = (BigInt(digits) + 1n).toString();\n      point += up.length - digits.length;\n      digits = up.replace(/0+$/, \'\');\n    }\n  }\n  return sign + spell(digits, point);\n};\n// A uniform draw from lo..=hi (`randomInt`): 64-bit words from `crypto.getRandomValues`, a word at\n// or above the largest multiple of the span drawn again, so no value is likelier than another.\nconst randomWord = () => { const w = new BigUint64Array(1); crypto.getRandomValues(w); return w[0]; };\nconst randomIntIn = (lo, hi) => {\n  if (lo > hi) throw new Error(`randomInt(${lo}, ${hi}): the low bound is above the high bound`);\n  const span = hi - lo + 1n;\n  if (span === 1n << 64n) return BigInt.asIntN(64, randomWord());\n  const limit = (1n << 64n) - (1n << 64n) % span;\n  for (;;) { const r = randomWord(); if (r < limit) return lo + r % span; }\n};\n\n// \u{2500}\u{2500} Calendar arithmetic \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// `temporal_component`/`temporal_component_date` answer a `Date`/`Moment`\'s calendar/clock\n// components: exactly Howard Hinnant\'s `days_from_civil`/`civil_from_days` (BigInt throughout, so\n// a far-future/far-past day count never loses precision) \u{2014} the identical algorithm\n// `logicaffeine_base::temporal` runs, so a page\'s `the weekday of` a date is bit-identical to the\n// reference tier. `z`/`days` are always a day count since the Unix epoch (1970-01-01 = day 0).\nconst daysFromCivil = (year, month, day) => {\n  const y = month <= 2n ? year - 1n : year;\n  const era = (y >= 0n ? y : y - 399n) / 400n;\n  const yoe = y - era * 400n;\n  const m = month;\n  const doy = (153n * (m > 2n ? m - 3n : m + 9n) + 2n) / 5n + day - 1n;\n  const doe = yoe * 365n + yoe / 4n - yoe / 100n + doy;\n  return era * 146097n + doe - 719468n;\n};\nconst civilFromDays = (z) => {\n  const zz = z + 719468n;\n  const era = (zz >= 0n ? zz : zz - 146096n) / 146097n;\n  const doe = zz - era * 146097n;\n  const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;\n  const y = yoe + era * 400n;\n  const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n);\n  const mp = (5n * doy + 2n) / 153n;\n  const day = doy - (153n * mp + 2n) / 5n + 1n;\n  const month = mp < 10n ? mp + 3n : mp - 9n;\n  const year = month <= 2n ? y + 1n : y;\n  return [year, month, day];\n};\n// ISO-8601 weekday of a day number: 1 = Monday \u{2026} 7 = Sunday.\nconst isoWeekdayFromDays = (z) => ((((z + 3n) % 7n) + 7n) % 7n) + 1n;\nconst isoWeekFromDays = (z) => {\n  const isoDow = isoWeekdayFromDays(z);\n  const thursday = z + (4n - isoDow);\n  const [isoYear] = civilFromDays(thursday);\n  const jan4 = daysFromCivil(isoYear, 1n, 4n);\n  const week1Monday = jan4 - (isoWeekdayFromDays(jan4) - 1n);\n  return (z - week1Monday) / 7n + 1n;\n};\nconst NANOS_PER_DAY = 86400000000000n;\nconst NANOS_PER_SECOND = 1000000000n;\nconst divEuclid = (a, b) => { const q = a / b; return a % b < 0n ? q - 1n : q; };\nconst remEuclid = (a, b) => { const r = a % b; return r < 0n ? r + b : r; };\nconst isGregorianLeap = (year) => (year % 4n === 0n && year % 100n !== 0n) || year % 400n === 0n;\nconst lastDayOfMonth = (year, month) => {\n  if ([1n, 3n, 5n, 7n, 8n, 10n, 12n].includes(month)) return 31n;\n  if ([4n, 6n, 9n, 11n].includes(month)) return 30n;\n  if (month === 2n) return isGregorianLeap(year) ? 29n : 28n;\n  return 0n;\n};\n// `logicaffeine_base::temporal::parse_rfc3339`, ported byte-for-byte: a fixed 19-byte\n// `YYYY-MM-DDTHH:MM:SS` prefix, optional fractional seconds, then `Z` or a `\u{b1}HH:MM` offset.\nconst parseRfc3339 = (s) => {\n  const bad = () => new Error(`invalid RFC 3339 timestamp: ${s}`);\n  if (s.length < 20) throw bad();\n  const digits = (start, len) => {\n    let v = 0n;\n    for (let i = start; i < start + len; i++) {\n      const c = s.charCodeAt(i);\n      if (c < 48 || c > 57) throw bad();\n      v = v * 10n + BigInt(c - 48);\n    }\n    return v;\n  };\n  if (s[4] !== \'-\' || s[7] !== \'-\' || s[10] !== \'T\' || s[13] !== \':\' || s[16] !== \':\') throw bad();\n  const year = digits(0, 4), month = digits(5, 2), day = digits(8, 2);\n  const hour = digits(11, 2), minute = digits(14, 2), second = digits(17, 2);\n  if (month < 1n || month > 12n || day < 1n || day > lastDayOfMonth(year, month)) throw bad();\n  if (hour > 23n || minute > 59n || second > 59n) throw bad();\n  let pos = 19;\n  let nanosecond = 0n;\n  if (s[pos] === \'.\') {\n    pos += 1;\n    let frac = 0n, count = 0;\n    while (pos < s.length && s[pos] >= \'0\' && s[pos] <= \'9\') {\n      if (count < 9) { frac = frac * 10n + BigInt(s.charCodeAt(pos) - 48); count += 1; }\n      pos += 1;\n    }\n    if (count === 0) throw bad();\n    for (let i = count; i < 9; i++) frac *= 10n;\n    nanosecond = frac;\n  }\n  let offsetSeconds = 0n;\n  if (s[pos] === \'Z\') {\n    pos += 1;\n  } else if (s[pos] === \'+\' || s[pos] === \'-\') {\n    const sign = s[pos] === \'+\' ? 1n : -1n;\n    const oh = digits(pos + 1, 2);\n    if (s[pos + 3] !== \':\') throw bad();\n    const om = digits(pos + 4, 2);\n    if (oh > 23n || om > 59n) throw bad();\n    pos += 6;\n    offsetSeconds = sign * (oh * 3600n + om * 60n);\n  } else {\n    throw bad();\n  }\n  if (pos !== s.length) throw bad();\n  const days = daysFromCivil(year, month, day);\n  const nanos = days * NANOS_PER_DAY + (hour * 3600n + minute * 60n + second) * NANOS_PER_SECOND + nanosecond;\n  return nanos - offsetSeconds * NANOS_PER_SECOND;\n};\nconst temporalComponent = (nanos, which) => {\n  const day = divEuclid(nanos, NANOS_PER_DAY);\n  const rem = remEuclid(nanos, NANOS_PER_DAY);\n  const [year, month, d] = civilFromDays(day);\n  const secs = rem / NANOS_PER_SECOND;\n  switch (Number(which)) {\n    case 0: return year;\n    case 1: return month;\n    case 2: return d;\n    case 3: return secs / 3600n;\n    case 4: return (secs % 3600n) / 60n;\n    case 5: return secs % 60n;\n    case 6: return isoWeekdayFromDays(day);\n    case 7: return isoWeekFromDays(day);\n    case 8: return (month - 1n) / 3n + 1n;\n    default: throw new Error(`temporal_component: unknown component selector ${which}`);\n  }\n};\nconst temporalComponentDate = (days, which) => {\n  const z = BigInt(days);\n  const [year, month, d] = civilFromDays(z);\n  switch (Number(which)) {\n    case 0: return year;\n    case 1: return month;\n    case 2: return d;\n    case 6: return isoWeekdayFromDays(z);\n    case 7: return isoWeekFromDays(z);\n    case 8: return (month - 1n) / 3n + 1n;\n    default: throw new Error(`temporal_component_date: unknown component selector ${which}`);\n  }\n};\n\n// A width-`w` field, zero-padded, with the sign OUTSIDE the padding (Rust\'s `{:0w$}` on a signed\n// value: `format!(\"{:04}\", -5)` is `\"-005\"`, not `\"00-5\"`).\nconst padSigned = (n, w) => {\n  const neg = n < 0;\n  const digits = String(neg ? -n : n).padStart(neg ? w - 1 : w, \'0\');\n  return neg ? \'-\' + digits : digits;\n};\nconst pad2 = (n) => String(n).padStart(2, \'0\');\nconst printDate = (days) => {\n  const [y, m, d] = civilFromDays(BigInt(days));\n  return `${padSigned(Number(y), 4)}-${pad2(m)}-${pad2(d)}`;\n};\nconst printMoment = (nanos) => {\n  const totalSeconds = divEuclid(nanos, NANOS_PER_SECOND);\n  const days = divEuclid(totalSeconds, 86400n);\n  const daySeconds = remEuclid(totalSeconds, 86400n);\n  const hours = daySeconds / 3600n, minutes = (daySeconds % 3600n) / 60n;\n  const [y, m, d] = civilFromDays(days);\n  return `${padSigned(Number(y), 4)}-${pad2(m)}-${pad2(d)} ${pad2(hours)}:${pad2(minutes)}`;\n};\n// `logicaffeine_base::temporal::format_time_of_day`: `HH:MM:SS[.frac]`, lossless to the nanosecond.\nconst printTime = (nanosFromMidnight) => {\n  const rem = remEuclid(nanosFromMidnight, NANOS_PER_DAY);\n  const secs = rem / NANOS_PER_SECOND;\n  let s = `${pad2(secs / 3600n)}:${pad2((secs % 3600n) / 60n)}:${pad2(secs % 60n)}`;\n  const nanos = rem % NANOS_PER_SECOND;\n  if (nanos !== 0n) {\n    let frac = nanos.toString().padStart(9, \'0\');\n    while (frac.endsWith(\'0\')) frac = frac.slice(0, -1);\n    s += \'.\' + frac;\n  }\n  return s;\n};\n// `RuntimeValue::Duration::to_display_string`: magnitude-bucketed (h/min/s/ms/\u{3bc}s/ns).\nconst printDuration = (nanos) => {\n  const abs = nanos < 0n ? -nanos : nanos;\n  const sign = nanos < 0n ? \'-\' : \'\';\n  if (abs >= 3600000000000n) return `${sign}${abs / 3600000000000n}h`;\n  if (abs >= 60000000000n) return `${sign}${abs / 60000000000n}min`;\n  if (abs >= 1000000000n) return `${sign}${abs / 1000000000n}s`;\n  if (abs >= 1000000n) return `${sign}${abs / 1000000n}ms`;\n  if (abs >= 1000n) return `${sign}${abs / 1000n}\u{3bc}s`;\n  return `${sign}${abs}ns`;\n};\n// `RuntimeValue::Span::to_display_string`: a `Span`\'s two i32 fields packed months-high/days-low\n// (`vm/wasm/module.rs`\'s `Constant::Span` lowering).\nconst printSpan = (packed) => {\n  const months = Number(BigInt.asIntN(32, packed >> 32n));\n  const days = Number(BigInt.asIntN(32, packed));\n  const parts = [];\n  const years = Math.trunc(months / 12), remMonths = months % 12;\n  if (years !== 0) parts.push(Math.abs(years) === 1 ? `${years} year` : `${years} years`);\n  if (remMonths !== 0) parts.push(Math.abs(remMonths) === 1 ? `${remMonths} month` : `${remMonths} months`);\n  if (days !== 0 || parts.length === 0) parts.push(Math.abs(days) === 1 ? `${days} day` : `${days} days`);\n  return parts.join(\' and \');\n};\n// Every `Seq of <scalar>` / `Set of <scalar>` display sink: read the `[len][cap][data_ptr]` header\n// and each 8-byte slot, matching `RuntimeValue::List`/`Set::to_display_string` exactly (insertion\n// order, `[e0, e1]` / `{e0, e1}`).\nconst seqSlotAddrs = (h) => {\n  const d = dv();\n  const len = d.getInt32(h, true), dp = d.getInt32(h + 8, true);\n  const out = [];\n  for (let i = 0; i < len; i++) out.push(dp + i * 8);\n  return out;\n};\nconst displaySeqI64 = (h) => \'[\' + seqSlotAddrs(h).map((a) => dv().getBigInt64(a, true).toString()).join(\', \') + \']\';\nconst displaySeqBool = (h) => \'[\' + seqSlotAddrs(h).map((a) => (dv().getBigInt64(a, true) !== 0n).toString()).join(\', \') + \']\';\nconst displaySeqWord32 = (h) => \'[\' + seqSlotAddrs(h).map((a) => dv().getUint32(a, true).toString()).join(\', \') + \']\';\nconst displaySeqWord64 = (h) => \'[\' + seqSlotAddrs(h).map((a) => dv().getBigUint64(a, true).toString()).join(\', \') + \']\';\nconst displaySeqF64 = (h) => \'[\' + seqSlotAddrs(h).map((a) => fmtF(dv().getFloat64(a, true))).join(\', \') + \']\';\nconst displaySeqText = (h) => \'[\' + seqSlotAddrs(h).map((a) => readText(dv().getInt32(a, true))).join(\', \') + \']\';\nconst displaySetI64 = (h) => \'{\' + seqSlotAddrs(h).map((a) => dv().getBigInt64(a, true).toString()).join(\', \') + \'}\';\nconst displaySetText = (h) => \'{\' + seqSlotAddrs(h).map((a) => readText(dv().getInt32(a, true))).join(\', \') + \'}\';\n// `apply_format_spec`\'s alignment: right-pad (`<`, 1), left-pad (`>`, 0, also the bare-width\n// default), or split the padding (`^`, 2, the extra column going to the right) \u{2014} space fill,\n// counted in Unicode code points (`Array.from`), matching Rust\'s `char`-counted width.\nconst alignPad = (s, width, align) => {\n  const chars = Array.from(s);\n  const pad = width - chars.length;\n  if (pad <= 0) return s;\n  if (align === 1) return s + \' \'.repeat(pad);\n  if (align === 2) { const l = Math.floor(pad / 2); return \' \'.repeat(l) + s + \' \'.repeat(pad - l); }\n  return \' \'.repeat(pad) + s;\n};\n\n// \u{2500}\u{2500} The node table \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// The module names DOM nodes by integer id (an i64 at the boundary); id 0 is `document.body`. Ids\n// are handed out from a counter and never reused, so a stale id can never name a different node.\n// `nodes` maps an id to its node; `ids` maps a node to its current id, so re-querying a node the\n// host already named returns the same id. A node a patch stream frees is deleted from both, with every\n// node under it; an operation on a deleted id does nothing.\nconst nodes = new Map([[0, document.body]]);\nconst ids = new WeakMap([[document.body, 0]]);\nlet nextId = 1;\nconst nodeId = (n) => { let id = ids.get(n); if (id === undefined) { id = nextId++; nodes.set(id, n); ids.set(n, id); } return BigInt(id); };\nconst node = (h) => nodes.get(Number(h));\n\n// `preventDefault` for one event on one element, held by however many owners asked for it \u{2014} a\n// stream\'s registration, an imperative one \u{2014} through ONE listener, added when the first owner holds\n// it and removed when the last lets go.\nconst preventIt = (e) => e.preventDefault();\nconst holdPrevent = (el, event) => {\n  const held = el.__logosPrevented || (el.__logosPrevented = new Map());\n  const n = held.get(event) || 0;\n  held.set(event, n + 1);\n  if (n === 0) el.addEventListener(event, preventIt);\n};\nconst dropPrevent = (el, event) => {\n  const held = el.__logosPrevented;\n  const n = (held && held.get(event)) || 0;\n  if (n > 1) { held.set(event, n - 1); return; }\n  if (held) held.delete(event);\n  el.removeEventListener(event, preventIt);\n};\n// An imperative registration is permanent and idempotent: asking twice holds it once.\nconst preventAlways = (el, event) => {\n  const always = el.__logosPreventedAlways || (el.__logosPreventedAlways = new Set());\n  if (always.has(event)) return;\n  always.add(event);\n  holdPrevent(el, event);\n};\n\n// A listener\'s flags (`listenFlags(el, event, flags)`, the bits of `semantics::dom::LISTEN_*`):\n// capture and passive are how its DOM listener is attached, stop is what that listener does before\n// the page hears the event, and prevent is held on the element \u{2014} unless the listener is passive,\n// which promises the browser it will not cancel. Once (4) is the lowering\'s own bookkeeping; a host\n// never retires a listener.\nconst LISTEN_PREVENT = 1, LISTEN_STOP = 2, LISTEN_CAPTURE = 8, LISTEN_PASSIVE = 16;\nconst listenOptions = (flags) => ({ capture: (flags & LISTEN_CAPTURE) !== 0, passive: (flags & LISTEN_PASSIVE) !== 0 });\nconst attachedAlike = (a, b) => ((a ^ b) & (LISTEN_CAPTURE | LISTEN_PASSIVE)) === 0;\nconst cancels = (flags) => (flags & LISTEN_PREVENT) !== 0 && (flags & LISTEN_PASSIVE) === 0;\n// The DOM listener behind a registration: it stops the event when the registration\'s CURRENT flags\n// say so, then hands it to the page through `hear`.\nconst heard = (flagsNow, handle) => (ev) => {\n  if (flagsNow() & LISTEN_STOP) ev.stopPropagation();\n  hear(ev, handle);\n};\n\n// Where a browser delivers `event` for `el`: the viewport\'s own scroll is fired at the document,\n// never at `body` (node 0, the page) or the root element, so the page\'s scroll is heard there.\nconst deliveredAt = (el, event) => (event === \'scroll\' && (el === document.body || el === document.documentElement) ? document : el);\n// A closure imperative code attaches (`addEventListener`) is a DOM listener of its own, for the life\n// of the element.\nconst ownListen = (el, event, handler) => {\n  const binding = { target: deliveredAt(el, event), event, handler };\n  binding.listener = heard(() => 0, () => binding.handler);\n  try {\n    binding.target.addEventListener(event, binding.listener);\n  } catch (err) {\n    binding.handler = undefined;\n    releaseHandle(handler);\n    throw err;\n  }\n  (el.__logosOwnListeners || (el.__logosOwnListeners = new Set())).add(binding);\n};\nconst dropOwnListeners = (el) => {\n  const bindings = el.__logosOwnListeners;\n  if (!bindings) return;\n  for (const binding of bindings) {\n    binding.target.removeEventListener(binding.event, binding.listener);\n    const handler = binding.handler;\n    binding.handler = undefined;\n    releaseHandle(handler);\n  }\n  bindings.clear();\n};\n\n// \u{2500}\u{2500} The page\'s listeners are DATA \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// The page keeps which element answers which event in its own tables; a registration\n// (`listenFlags`, a stream\'s `Listen`) tells the host only that something listens there, and how.\n// An element holds one DOM listener per event, under the node id the registration named, and it\n// hands the event to the page\'s one entry, `__lgx_dispatch_node(node, event, value)`. `value` is\n// what the event carries: the name of a dropped file, the key of a keyboard event, the state of a\n// checkbox or radio, the value of any other control. An element no paint holds any more answers\n// nothing. A `drop` that carries a file is also heard as `filedropped`, the event a page\'s `When a\n// file is dropped on \u{2026}:` answers, once the file is read.\nconst FILE_DROPPED = \'filedropped\';\nconst pageListeners = new Set();\nconst eventValueOf = (ev) => {\n  if (ev && ev.__logosFile) return ev.__logosFile.name;\n  if (ev && typeof ev.key === \'string\') return ev.key;\n  const t = ev && ev.target;\n  if (!t) return \'\';\n  const ty = (t.type || \'\').toLowerCase();\n  if (ty === \'checkbox\' || ty === \'radio\') return t.checked ? \'true\' : \'false\';\n  return t.value == null ? \'\' : String(t.value);\n};\n// The page\'s one entry for an element\'s event: its dispatcher hears `event` at node `id`, carrying\n// `value` (both Text handles), and answers whether it heard it. A visitor\'s event reaches it through\n// `hearAtNode`, a program\'s own driving verb through `dom_page_entry`.\nconst dispatchNode = (id, event, value) => inst.exports.__lgx_dispatch_node(id, event, value);\nconst hearAtNode = (el, id, event, ev) => {\n  if (!ids.has(el) || !inst || !inst.exports.__lgx_dispatch_node) return;\n  guarded(() => {\n    const prev = currentEvent;\n    currentEvent = ev;\n    try {\n      dispatchNode(BigInt(id), allocText(event), allocText(eventValueOf(ev)));\n      settle();\n    } finally { currentEvent = prev; }\n  });\n};\nconst pageEntry = (el, event) => (el.__logosListen ? el.__logosListen[event] : undefined);\n// The listener for `event` on `el`, registered under node `id`, attached again only when the flags\n// change HOW it is attached (capture, passive); `.stop` is what it does before the page hears the\n// event.\nconst pageListen = (el, event, flags, id) => {\n  const on = el.__logosListen || (el.__logosListen = {});\n  const had = on[event];\n  if (had) had.id = id;\n  if (had && attachedAlike(had.flags, flags)) { had.flags = flags; return; }\n  if (had) deliveredAt(el, event).removeEventListener(event, had.fn, listenOptions(had.flags));\n  const entry = had || { flags, id, fn: null };\n  entry.flags = flags;\n  entry.fn = (ev) => {\n    if (entry.flags & LISTEN_STOP) ev.stopPropagation();\n    if (event !== \'drop\') { atTurnEnd(() => hearAtNode(el, entry.id, event, ev)); return; }\n    atTurnEndWithFile(ev, () => {\n      hearAtNode(el, entry.id, event, ev);\n      if (ev.__logosFile) hearAtNode(el, entry.id, FILE_DROPPED, ev);\n    });\n  };\n  deliveredAt(el, event).addEventListener(event, entry.fn, listenOptions(flags));\n  on[event] = entry;\n  pageListeners.add(entry);\n};\nconst pageUnlisten = (el, event) => {\n  const entry = pageEntry(el, event);\n  if (!entry) return;\n  deliveredAt(el, event).removeEventListener(event, entry.fn, listenOptions(entry.flags));\n  delete el.__logosListen[event];\n  pageListeners.delete(entry);\n};\n// A patch stream\'s `Listen`/`Unlisten`: the element\'s own listener, holding the event\'s prevent\n// exactly while its flags cancel.\nconst streamListen = (el, event, flags, id) => {\n  const had = pageEntry(el, event);\n  const held = had !== undefined && cancels(had.flags);\n  pageListen(el, event, flags, id);\n  if (cancels(flags) && !held) holdPrevent(el, event);\n  if (!cancels(flags) && held) dropPrevent(el, event);\n};\nconst streamUnlisten = (el, event) => {\n  const had = pageEntry(el, event);\n  if (had && cancels(had.flags)) dropPrevent(el, event);\n  pageUnlisten(el, event);\n};\n\n// \u{2500}\u{2500} The relay: one WebSocket for the page, many topics \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// Opened on the first subscription and shared by every topic. Each message is one\n// `logicaffeine_system::relay_proto::RelayFrame` in bincode, as the system\'s relay and its native\n// and browser clients write it: the variant\'s index as a u32, then each field \u{2014} a string or a\n// byte string as a u64 length and its bytes \u{2014} all little-endian. `Subscribe` and `Publish` go\n// out, `SubAck` and `Event` come in; a page\'s payloads are UTF-8 text. A topic is\n// subscribed when its first handler arrives, and every handler lasts for the life of the page.\nconst RELAY_VARIANTS = [\'Subscribe\', \'Unsubscribe\', \'Publish\', \'SubAck\', \'Event\'];\nconst RELAY_CARRIES_DATA = new Set([\'Publish\', \'Event\']);\nconst strictUtf8 = new TextDecoder(\'utf-8\', { fatal: true });\nconst relayCodec = {\n  // `{ Variant: { topic, data } }`, `data` bytes, to the frame\'s bytes.\n  encode(frame) {\n    const kind = Object.keys(frame)[0];\n    const fields = [enc.encode(frame[kind].topic)];\n    if (RELAY_CARRIES_DATA.has(kind)) fields.push(frame[kind].data);\n    const out = new Uint8Array(4 + fields.reduce((n, f) => n + 8 + f.length, 0));\n    const view = new DataView(out.buffer);\n    view.setUint32(0, RELAY_VARIANTS.indexOf(kind), true);\n    let at = 4;\n    for (const f of fields) {\n      view.setBigUint64(at, BigInt(f.length), true);\n      out.set(f, at + 8);\n      at += 8 + f.length;\n    }\n    return out;\n  },\n  // A frame\'s bytes to `{ Variant: { topic, data } }`, or `null` for bytes that are no frame \u{2014}\n  // an unknown variant, a length past the end, a topic that is not UTF-8. Bytes after the frame\n  // are ignored, as the system\'s reader ignores them.\n  decode(bytes) {\n    const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n    if (bytes.length < 4) return null;\n    const kind = RELAY_VARIANTS[view.getUint32(0, true)];\n    if (!kind) return null;\n    let at = 4;\n    const field = () => {\n      if (at + 8 > bytes.length) return null;\n      const len = view.getBigUint64(at, true);\n      if (len > BigInt(bytes.length - at - 8)) return null;\n      const f = bytes.subarray(at + 8, at + 8 + Number(len));\n      at += 8 + Number(len);\n      return f;\n    };\n    const topicBytes = field();\n    if (!topicBytes) return null;\n    let topic;\n    try { topic = strictUtf8.decode(topicBytes); } catch { return null; }\n    if (!RELAY_CARRIES_DATA.has(kind)) return { [kind]: { topic } };\n    const data = field();\n    if (!data) return null;\n    return { [kind]: { topic, data: data.slice() } };\n  },\n};\nglobalThis.__logosRelayCodec = relayCodec;\nconst relay = {\n  sock: null,\n  subs: new Map(),\n  queue: [],\n  url() {\n    const p = location.protocol === \'https:\' ? \'wss:\' : \'ws:\';\n    return `${p}//${location.host}/_relay`;\n  },\n  open() {\n    if (this.sock) return;\n    const s = new WebSocket(this.url());\n    s.binaryType = \'arraybuffer\';\n    this.sock = s;\n    s.onopen = () => { for (const f of this.queue.splice(0)) s.send(relayCodec.encode(f)); };\n    s.onmessage = (ev) => {\n      if (!(ev.data instanceof ArrayBuffer)) return;\n      const frame = relayCodec.decode(new Uint8Array(ev.data));\n      const e = frame && frame.Event;\n      if (!e) return;\n      // The payload is presented as an event carrying a value, read by `eventValue` like every\n      // other carrying event.\n      const carried = { target: { value: dec.decode(e.data) } };\n      for (const slot of [...(this.subs.get(e.topic) || [])]) {\n        if (slot.deliver) slot.deliver(carried.target.value); else dispatch(slot.handle, carried);\n      }\n    };\n    s.onclose = () => { this.sock = null; };\n  },\n  send(frame) {\n    if (this.sock && this.sock.readyState === 1) this.sock.send(relayCodec.encode(frame));\n    else this.queue.push(frame);\n  },\n  subscribe(topic, slot) {\n    let slots = this.subs.get(topic);\n    if (!slots) {\n      slots = new Set();\n      this.subs.set(topic, slots);\n      this.open();\n      this.send({ Subscribe: { topic } });\n    }\n    slots.add(slot);\n  },\n};\n\n// \u{2500}\u{2500} The fetch engine \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// Every request gets an id; status, headers, body and error land PER REQUEST, and the completion\n// closure receives its own request\'s id through the trampoline, so concurrent fetches cannot\n// misdeliver. A request is finalized exactly once (an aborted or finished request ignores late\n// results) and forgotten when the turn that completed it ends. Mirrors the native recorder\n// (`semantics::dom`).\nconst FETCH_BODY_CAP = 4194304;\nconst FETCH_ALLOWED_HEADERS = [\'accept\', \'accept-language\', \'authorization\', \'cache-control\', \'content-type\', \'idempotency-key\', \'if-match\', \'if-none-match\', \'x-csrf-token\', \'x-expected-version\', \'x-requested-with\'];\n// The headers a request goes out with, `[name, value, \u{2026}]`: the page\'s own, and \u{2014} for a request that\n// changes state (`POST`, `PUT`, `PATCH`, `DELETE`) to the page\'s own server (a URL with no scheme and\n// no host) that sets none \u{2014} the CSRF token the document\'s `<meta name=\"csrf-token\">` holds, the one\n// the server that composed the page wrote there. `semantics::dom`\'s `request_headers` is the same\n// rule in process.\nconst requestHeaders = (method, url, headers) => {\n  const changes = [\'POST\', \'PUT\', \'PATCH\', \'DELETE\'].includes(method.toUpperCase());\n  const own = !url.includes(\'://\') && !url.startsWith(\'//\');\n  const sets = headers.some((h, i) => i % 2 === 0 && h.toLowerCase() === \'x-csrf-token\');\n  const meta = document.head && Array.from(document.head.querySelectorAll(\'meta[name]\')).find((m) => m.getAttribute(\'name\').toLowerCase() === \'csrf-token\');\n  if (!changes || !own || sets || !meta || !meta.hasAttribute(\'content\')) return headers;\n  return [...headers, \'x-csrf-token\', meta.getAttribute(\'content\')];\n};\nconst fetchReqs = new Map();\nlet nextFetch = 1, currentFetch = 0;\nconst forgetFetch = (id) => {\n  const q = fetchReqs.get(id);\n  if (!q) return;\n  fetchReqs.delete(id);\n  releaseHandle(q.handler);\n  if (currentFetch === id) currentFetch = 0;\n};\nconst fetchDone = (id, r) => {\n  const q = fetchReqs.get(id);\n  if (!q || q.done) return;\n  q.done = true;\n  Object.assign(q, r);\n  if (q.timer) { clearTimeout(q.timer); q.timer = 0; }\n  currentFetch = id;\n  afterTurn.push(() => forgetFetch(id));\n  guarded(() => { inst.exports.__logos_call_closure(q.handler, BigInt(id)); settle(); });\n};\nconst fetchSend = (method, url, body, pageHeaders, handler) => {\n  const headers = requestHeaders(method, url, pageHeaders);\n  const id = nextFetch++;\n  const q = { handler, done: false, status: 0, headers: new Map(), body: \'\', error: \'\', ctrl: new AbortController(), timer: 0, timedOut: false };\n  fetchReqs.set(id, q);\n  if (body.length > FETCH_BODY_CAP) { queueMicrotask(() => fetchDone(id, { error: \'toolarge\' })); return BigInt(id); }\n  const bad = headers.filter((h, i) => i % 2 === 0 && !FETCH_ALLOWED_HEADERS.includes(h.toLowerCase()));\n  if (bad.length) { queueMicrotask(() => fetchDone(id, { error: \'network: header not allowed: \' + bad[0].toLowerCase() })); return BigInt(id); }\n  const hs = {};\n  for (let i = 0; i + 1 < headers.length; i += 2) hs[headers[i]] = headers[i + 1];\n  fetch(url, { method, headers: hs, body: method === \'GET\' || method === \'HEAD\' ? undefined : body, signal: q.ctrl.signal })\n    .then(async (r) => {\n      const t = await r.text();\n      if (t.length > FETCH_BODY_CAP) { fetchDone(id, { error: \'toolarge\' }); return; }\n      const rh = new Map();\n      r.headers.forEach((v, k) => rh.set(k.toLowerCase(), v));\n      fetchDone(id, { status: r.status, headers: rh, body: t });\n    })\n    .catch((e) => fetchDone(id, { error: q.timedOut ? \'timeout\' : (e && e.name === \'AbortError\' ? \'abort\' : \'network: \' + (e && e.message ? e.message : String(e))) }));\n  return BigInt(id);\n};\n\n// The settle loop: after every entry into LOGOS, re-run a hand-written top-level `render`\n// function (exported as `__logos_render` \u{2014} the immediate-mode re-render idiom) if the module\n// declares one.\nconst settle = () => { if (inst.exports.__logos_render) inst.exports.__logos_render(); };\n\n// One named operation on one element. `focus`/`blur` raise the browser\'s own events, so a page\n// listening for them is answered by the platform.\nconst elementCommand = (e, c) => { if (c === \'focus\') e.focus(); else if (c === \'blur\') e.blur(); else if (c === \'scrollto\') e.scrollIntoView(); else if (c === \'selecttext\') { if (e.select) e.select(); } else if (c === \'hover\') e.dispatchEvent(new MouseEvent(\'mouseover\', { bubbles: true })); };\n// What makes two head tags THE SAME TAG \u{2014} the identity a page\'s own version replaces (mirrors\n// `web_host.rs`\'s `head_identity`, which computes the SAME keys for the server-rendered shell, so a\n// page merges identically whichever way its head reached the document). `null` means the tag\n// replaces nothing and is simply added.\nconst headIdentity = (el) => {\n  const tag = el.tagName.toLowerCase();\n  if (tag === \'title\') return \'title\';\n  if (tag === \'base\') return \'base\';\n  if (tag === \'meta\') {\n    if (el.hasAttribute(\'charset\')) return \'meta:charset\';\n    const name = el.getAttribute(\'name\');\n    if (name != null) {\n      const media = el.getAttribute(\'media\');\n      return media != null ? `meta:name=${name.toLowerCase()}@${media.toLowerCase()}` : `meta:name=${name.toLowerCase()}`;\n    }\n    const prop = el.getAttribute(\'property\');\n    return prop != null ? `meta:property=${prop.toLowerCase()}` : null;\n  }\n  if (tag === \'link\') {\n    const rel = el.getAttribute(\'rel\');\n    return rel != null ? `link:rel=${rel.toLowerCase()}` : null;\n  }\n  return null;\n};\n// `Head:`\'s rendered `<title>`/`<meta>`/`<link>`/`<base>` fragment, merged into the live\n// `document.head` over whatever is already there (the default set the host shell ships, or an\n// earlier merge): a tag with an identity above REPLACES the existing tag of that identity;\n// everything else is simply added.\nconst mergeIntoHead = (html) => {\n  const tmpl = document.createElement(\'template\');\n  tmpl.innerHTML = html;\n  for (const el of Array.from(tmpl.content.children)) {\n    const id = headIdentity(el);\n    if (id) {\n      for (const existing of Array.from(document.head.children)) {\n        if (headIdentity(existing) === id) existing.remove();\n      }\n    }\n    document.head.appendChild(el);\n  }\n};\n// `on-<event>.prevent`: suppress the browser\'s own action for this event on this element, so a\n// handled form does not also post itself. A property of the registration \u{2014} held once however\n// often it is asked for \u{2014} rather than of any one firing.\nconst preventDefault = (id, name) => {\n  const n = node(id);\n  if (n) preventAlways(n, name);\n};\n// `listenFlags(el, event, flags)`: the page listens for `event` on `el`, with `flags`.\nconst listenFlags = (id, name, flags) => {\n  const n = node(id);\n  if (!n) return;\n  pageListen(n, name, flags, id);\n  if (cancels(flags)) preventAlways(n, name);\n};\n\n// \u{2500}\u{2500} The patch stream (`semantics::patch`), both wire formats \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// ONE decoder over the opcode table (`semantics::patch::OPCODES`), reading operands through the\n// format\'s own readers: the byte format (u32 nodes, length-prefixed text) or wire v2 (a word per\n// operand, text as a 1-based index into a string table). Decoding finishes before anything is\n// applied, so a malformed stream paints nothing.\nconst decodePatches = (read) => {\n  const ops = [];\n  while (read.more()) {\n    const code = read.code();\n    switch (code) {\n      case 0: throw new Error(`patch opcode 0 (BeginPaint) at ${read.where()} is retired: a paint\'s boundary is the commit that emits the stream, and no build since the retained tree writes it.`);\n      case 1: ops.push([\'createElement\', read.node(), read.text()]); break;\n      case 2: ops.push([\'createText\', read.node(), read.text()]); break;\n      case 3: ops.push([\'setText\', read.node(), read.text()]); break;\n      case 4: ops.push([\'setHtml\', read.node(), read.text()]); break;\n      case 5: ops.push([\'clearChildren\', read.node()]); break;\n      case 6: ops.push([\'setAttribute\', read.node(), read.text(), read.text()]); break;\n      case 7: ops.push([\'appendChild\', read.node(), read.node()]); break;\n      case 8: ops.push([\'setValue\', read.node(), read.text()]); break;\n      case 9: ops.push([\'setChecked\', read.node(), read.flag()]); break;\n      case 10: ops.push([\'addEventListener\', read.node(), read.text()]); break;\n      case 11: ops.push([\'setStream\', read.node(), read.i64()]); break;\n      case 12: ops.push([\'preventDefault\', read.node(), read.text()]); break;\n      case 13: throw new Error(`patch opcode 13 (MoveChildren) at ${read.where()} is retired: a slot\'s children are placed by the commit that emits the stream, and no build since the retained tree writes it.`);\n      case 14: ops.push([\'removeNode\', read.node()]); break;\n      case 15: ops.push([\'insertBefore\', read.node(), read.node(), read.node()]); break;\n      case 16: ops.push([\'removeAttribute\', read.node(), read.text()]); break;\n      case 17: ops.push([\'adopt\', read.node(), read.node(), read.text()]); break;\n      case 18: ops.push([\'adoptText\', read.node(), read.node(), read.text()]); break;\n      case 19: ops.push([\'listen\', read.node(), read.text(), read.byte()]); break;\n      case 20: ops.push([\'unlisten\', read.node(), read.text()]); break;\n      case 21: ops.push([\'freeNode\', read.node()]); break;\n      case 22: ops.push([\'hydrate\', read.node()]); break;\n      default: throw new Error(`unknown patch opcode ${code} at ${read.where()}`);\n    }\n  }\n  return ops;\n};\nconst byteOperands = (b) => {\n  let at = 0;\n  const need = (k) => { if (at + k > b.length) throw new Error(`patch stream truncated at byte ${at}`); };\n  const byte = () => { need(1); return b[at++]; };\n  const u32 = () => { need(4); const v = b[at] | (b[at+1]<<8) | (b[at+2]<<16) | (b[at+3]*0x1000000); at += 4; return v; };\n  return {\n    more() { return at < b.length; },\n    code() { return byte(); },\n    where() { return `byte ${at - 1}`; },\n    node() { return u32(); },\n    byte() { return byte(); },\n    flag() { return byte() !== 0; },\n    i64() { need(8); let v = 0n; for (let k = 7; k >= 0; k--) v = (v << 8n) | BigInt(b[at+k]); at += 8; return BigInt.asIntN(64, v); },\n    text() { const len = u32(); need(len); const t = dec.decode(b.subarray(at, at + len)); at += len; return t; },\n  };\n};\nconst wordOperands = (w, s) => {\n  let at = 0;\n  const word = () => { if (at >= w.length) throw new Error(`word patch stream truncated at word ${at}`); return w[at++]; };\n  const within = (v, max, what) => { if (v < 0n || v > max) throw new Error(`a word patch record carries ${v}, which is no ${what}, at word ${at - 1}`); return Number(v); };\n  return {\n    more() { return at < w.length; },\n    code() { return within(word(), 255n, \'opcode\'); },\n    where() { return `word ${at - 1}`; },\n    node() { return within(word(), 0xFFFFFFFFn, \'node id\'); },\n    byte() { return within(word(), 255n, \'flags byte\'); },\n    flag() { return within(word(), 1n, \'flag\') !== 0; },\n    i64() { return word(); },\n    text() {\n      const i = within(word(), BigInt(s.length), \'string index\');\n      if (i < 1) throw new Error(`a word patch record\'s string index 0 is out of range at word ${at - 1}`);\n      return s[i - 1];\n    },\n  };\n};\n// ONE op frees an entire removed subtree: the host walks it, taking every listener the page holds\n// on each node off and forgetting each node\'s id both ways (a freed id is exactly as gone as one\n// that never existed), before detaching the root \u{2014} so an event the removal itself raises (the\n// `blur` of a focused field) finds nothing to call.\nconst freeSubtree = (id) => {\n  const n = node(id);\n  if (!n) { nodes.delete(id); return; }\n  const stack = [n];\n  while (stack.length) {\n    const cur = stack.pop();\n    for (const c of cur.childNodes) stack.push(c);\n    dropOwnListeners(cur);\n    for (const event of Object.keys(cur.__logosListen || {})) streamUnlisten(cur, event);\n    const known = ids.get(cur);\n    if (known !== undefined) { nodes.delete(known); ids.delete(cur); }\n  }\n  if (n.parentNode) n.parentNode.removeChild(n);\n};\n// A node a stream creates takes the id the stream names for it, and keeps it until the stream frees\n// it: the EMITTER is the id authority. A stream id is at or above 2^31, where `nodeId` never mints.\nconst bindNode = (id, el) => {\n  if (nodes.has(id)) throw new Error(`the patch stream creates node ${id}, which is still live`);\n  nodes.set(id, el);\n  ids.set(el, id);\n};\nconst STREAM_APPEND = 0xFFFFFFFF;\n\n// \u{2500}\u{2500} Hydration: a stream adopting the markup a pre-render put in the document \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n//\n// `Hydrate(root)` opens it: the markup under `root` is the page\'s own first paint, drawn before the\n// program ran, and the `data-logos-prerendered` mark saying so goes. `Adopt(parent, node, tag)` binds\n// `node` to the next element of `parent` the markup holds when its tag is `tag`, and counts a\n// mismatch otherwise. `AdoptText(parent, node, text)` takes its place in the run of text before the\n// next element claimed under `parent`: the markup\'s text is split along the claims when they spell\n// it, and rebuilt when they do not \u{2014} a text node carries nothing a visitor did to it. A write the\n// stream makes to an adopted element brings the markup\'s node in line: an attribute already holding\n// the value is left alone (writing it again restarts an image and reloads a frame), a control\'s\n// value and checked state are set as the properties they are, and a control the visitor changed\n// before the program ran keeps the change and is heard as the `input` it raised then. When the\n// stream ends, whatever the markup holds that no claim bound leaves, and an adopted element drops the\n// attributes the stream did not write, among them the `value` and `checked` markup spells a control\'s\n// state with. The stream\'s apply answers the mismatches; the emitter decides what they mean.\nconst PRERENDERED = \'data-logos-prerendered\';\nlet hydration = null;\nconst hostOwn = (n) => n.nodeType === 1 && n.hasAttribute(\'data-logos-host\');\nconst unclaimedFrom = (h, p) => { const last = h.cursors.get(p); return last ? last.nextSibling : p.firstChild; };\nconst nextElement = (c) => { while (c && (c.nodeType !== 1 || hostOwn(c))) c = c.nextSibling; return c; };\nconst openHydration = (op) => {\n  if (!hydration) throw new Error(`the patch stream sends \\`${op}\\` outside a hydration: only a stream that opened one with \\`Hydrate\\` claims markup the document holds`);\n  return hydration;\n};\n// How the text claims `run` tile the markup\'s text nodes `held`, in order: per held node the range\n// of claims whose texts spell it exactly (an empty claim rides in any tile), or null when they do not\n// spell the held text. Claims past the last tile are empty.\nconst textTiles = (held, run) => {\n  const tiles = [];\n  let k = 0;\n  for (const t of held) {\n    const start = k;\n    let spelled = \'\';\n    while (spelled.length < t.data.length && k < run.length) spelled += run[k++].text;\n    if (spelled !== t.data) return null;\n    tiles.push([start, k]);\n  }\n  for (let j = k; j < run.length; j++) if (run[j].text !== \'\') return null;\n  return { tiles, trailing: k };\n};\n// The text under `p` between the child claimed last and `upTo` becomes what the waiting text claims\n// for `p` name; a comment there is no page content and goes.\nconst settleTexts = (h, p, upTo) => {\n  const run = h.texts.get(p) || [];\n  h.texts.delete(p);\n  const held = [];\n  for (let c = unclaimedFrom(h, p); c && c !== upTo; ) {\n    const next = c.nextSibling;\n    if (c.nodeType === 3) held.push(c);\n    else if (c.nodeType !== 1) c.remove();\n    c = next;\n  }\n  const fresh = (r) => { const t = document.createTextNode(r.text); p.insertBefore(t, upTo); bindNode(r.node, t); };\n  const tiled = textTiles(held, run);\n  if (tiled === null) {\n    for (const t of held) t.remove();\n    for (const r of run) fresh(r);\n    return;\n  }\n  held.forEach((t, i) => {\n    const [from, to] = tiled.tiles[i];\n    if (from === to) { t.remove(); return; }\n    let rest = t;\n    for (let j = from; j < to; j++) {\n      const r = run[j];\n      if (r.text === \'\') { const e = document.createTextNode(\'\'); p.insertBefore(e, rest); bindNode(r.node, e); continue; }\n      if (rest.data.length > r.text.length) { const tail = rest.splitText(r.text.length); bindNode(r.node, rest); rest = tail; }\n      else bindNode(r.node, rest);\n    }\n  });\n  for (let j = tiled.trailing; j < run.length; j++) fresh(run[j]);\n};\nconst claimElement = (parent, id, tag) => {\n  const h = openHydration(\'Adopt\');\n  const p = node(parent);\n  if (!p || !h.cursors.has(p)) { h.mismatches++; return; }\n  const el = nextElement(unclaimedFrom(h, p));\n  if (!el || el.localName.toLowerCase() !== tag.toLowerCase()) { h.mismatches++; return; }\n  settleTexts(h, p, el);\n  bindNode(id, el);\n  h.cursors.set(p, el);\n  h.cursors.set(el, null);\n  h.written.set(el, new Set());\n};\nconst claimText = (parent, id, text) => {\n  const h = openHydration(\'AdoptText\');\n  const p = node(parent);\n  if (!p || !h.cursors.has(p)) { h.mismatches++; return; }\n  const run = h.texts.get(p) || [];\n  run.push({ node: id, text });\n  h.texts.set(p, run);\n};\nconst adoptedElement = (e) => hydration !== null && hydration.written.has(e);\nconst toggles = (e) => e instanceof HTMLInputElement && (e.type === \'checkbox\' || e.type === \'radio\');\n// Whether the visitor changed control `e` from what its markup says, before the program ran.\nconst visitorChanged = (e) => {\n  if (toggles(e)) return e.checked !== e.defaultChecked;\n  if (e instanceof HTMLSelectElement) {\n    if (e.multiple) return Array.from(e.options).some((o) => o.selected !== o.defaultSelected);\n    const chosen = Array.from(e.options).find((o) => o.defaultSelected) || e.options[0];\n    return e.selectedIndex !== (chosen ? chosen.index : -1);\n  }\n  return \'defaultValue\' in e && e.value !== e.defaultValue;\n};\nconst keepTheVisitors = (e) => {\n  if (!toggles(e) || e.checked) hydration.heard.push(e);\n};\nconst endHydration = () => {\n  const h = hydration;\n  hydration = null;\n  for (const [p, last] of h.cursors) {\n    if (h.raw.has(p)) continue;\n    const upTo = nextElement(last ? last.nextSibling : p.firstChild);\n    settleTexts(h, p, upTo);\n    for (let c = upTo; c; ) {\n      const next = c.nextSibling;\n      if (!hostOwn(c)) c.remove();\n      c = next;\n    }\n  }\n  for (const [e, written] of h.written) {\n    for (const name of e.getAttributeNames()) if (!written.has(name)) e.removeAttribute(name);\n  }\n  for (const e of h.heard) {\n    e.dispatchEvent(new Event(\'input\', { bubbles: true }));\n    if (toggles(e) || e instanceof HTMLSelectElement) e.dispatchEvent(new Event(\'change\', { bubbles: true }));\n  }\n  return h.mismatches;\n};\n\n// A stream applied by the ids it names; answers its hydration\'s mismatches (0 when it opened none).\nconst applyByIds = (ops) => {\n  try { applyEach(ops); } catch (err) { hydration = null; throw err; }\n  return hydration ? endHydration() : 0;\n};\nconst applyEach = (ops) => {\n  for (const op of ops) {\n    switch (op[0]) {\n      case \'createElement\': bindNode(op[1], document.createElement(op[2])); break;\n      case \'createText\': bindNode(op[1], document.createTextNode(op[2])); break;\n      case \'setText\': { const e = node(op[1]); if (e) e.textContent = op[2]; break; }\n      case \'setHtml\': {\n        const e = node(op[1]);\n        if (!e) break;\n        if (adoptedElement(e)) {\n          hydration.raw.add(e);\n          const t = document.createElement(\'template\');\n          t.innerHTML = op[2];\n          if (t.innerHTML === e.innerHTML) break;\n        }\n        e.innerHTML = op[2];\n        break;\n      }\n      case \'clearChildren\': { const p = node(op[1]); if (p) while (p.firstChild) p.removeChild(p.firstChild); break; }\n      case \'setAttribute\': {\n        const e = node(op[1]);\n        if (!e) break;\n        if (adoptedElement(e)) { hydration.written.get(e).add(op[2]); if (e.getAttribute(op[2]) === op[3]) break; }\n        e.setAttribute(op[2], op[3]);\n        break;\n      }\n      case \'removeAttribute\': { const e = node(op[1]); if (e) e.removeAttribute(op[2]); break; }\n      case \'appendChild\': { const p = node(op[1]), c = node(op[2]); if (p && c) p.appendChild(c); break; }\n      case \'insertBefore\': { const p = node(op[1]), c = node(op[2]); if (p && c) p.insertBefore(c, op[3] === STREAM_APPEND ? null : node(op[3])); break; }\n      case \'removeNode\': { const n = node(op[1]); if (n && n.parentNode) n.parentNode.removeChild(n); break; }\n      case \'setValue\': {\n        const e = node(op[1]);\n        if (!e) break;\n        if (adoptedElement(e)) {\n          if (visitorChanged(e)) { keepTheVisitors(e); break; }\n          e.value = op[2];\n          break;\n        }\n        if (e.value !== op[2]) e.value = op[2];\n        break;\n      }\n      case \'setChecked\': {\n        const e = node(op[1]);\n        if (!e) break;\n        if (adoptedElement(e)) {\n          if (visitorChanged(e)) { keepTheVisitors(e); break; }\n          e.checked = op[2];\n          break;\n        }\n        if (e.checked !== op[2]) e.checked = op[2];\n        break;\n      }\n      case \'preventDefault\': preventDefault(op[1], op[2]); break;\n      case \'listen\': { const e = node(op[1]); if (e) streamListen(e, op[2], op[3], op[1]); break; }\n      case \'unlisten\': { const e = node(op[1]); if (e) streamUnlisten(e, op[2]); break; }\n      case \'freeNode\': freeSubtree(op[1]); break;\n      case \'hydrate\': {\n        if (hydration) throw new Error(`the patch stream hydrates node ${op[1]} while an earlier hydration in it is still open`);\n        const root = node(op[1]);\n        if (root) root.removeAttribute(PRERENDERED);\n        hydration = { cursors: new Map(root ? [[root, null]] : []), texts: new Map(), written: new Map(), raw: new Set(), heard: [], mismatches: root ? 0 : 1 };\n        break;\n      }\n      case \'adopt\': claimElement(op[1], op[2], op[3]); break;\n      case \'adoptText\': claimText(op[1], op[2], op[3]); break;\n      case \'addEventListener\': case \'setStream\': break;\n    }\n  }\n};\n\nconst exactHeld = [], exactFree = [];\nconst exactValue = (t) => ((t & 1n) === 0n ? t >> 1n : exactHeld[Number(t >> 1n)]);\nconst exactWord = (v) => {\n  if (v >= -(1n << 62n) && v < (1n << 62n)) return v << 1n;\n  const h = exactFree.length > 0 ? exactFree.pop() : exactHeld.length;\n  exactHeld[h] = v;\n  return (BigInt(h) << 1n) | 1n;\n};\nconst exactImports = {\n  exact_box: (v) => exactWord(v),\n  exact_narrow: (t) => { const v = exactValue(t); return v >= -(1n << 63n) && v < (1n << 63n) ? v : 0n; },\n  exact_add: (a, b) => exactWord(exactValue(a) + exactValue(b)),\n  exact_sub: (a, b) => exactWord(exactValue(a) - exactValue(b)),\n  exact_mul: (a, b) => exactWord(exactValue(a) * exactValue(b)),\n  exact_div: (a, b) => exactWord(exactValue(a) / exactValue(b)),\n  exact_mod: (a, b) => exactWord(exactValue(a) % exactValue(b)),\n  exact_floordiv: (a, b) => { const x = exactValue(a), y = exactValue(b), q = x / y; return exactWord(x % y !== 0n && (x < 0n) !== (y < 0n) ? q - 1n : q); },\n  exact_pow: (a, e) => exactWord(exactValue(a) ** e),\n  exact_cmp: (a, b) => { const x = exactValue(a), y = exactValue(b); return x < y ? -1 : x > y ? 1 : 0; },\n  exact_f64: (t) => Number(exactValue(t)),\n  exact_clone: (t) => exactWord(exactValue(t)),\n  exact_drop: (t) => { const h = Number(t >> 1n); exactHeld[h] = undefined; exactFree.push(h); },\n};\n\nconst env = {\n  ...exactImports,\n  print_exact: (t) => console.log(exactValue(t).toString()),\n  exact_text: (t) => allocText(exactValue(t).toString()),\n  print_i64: (v) => console.log(v.toString()),\n  print_text: (h) => console.log(readText(h)),\n  print_bool: (v) => console.log(v ? \'true\' : \'false\'),\n  print_f64: (v) => console.log(fmtF(v)),\n  print_char: (v) => console.log(String.fromCodePoint(Number(v))),\n  print_word: (v) => console.log(BigInt.asUintN(64, v).toString()),\n  print_date: (d) => console.log(printDate(d)),\n  print_moment: (n) => console.log(printMoment(n)),\n  print_time: (n) => console.log(printTime(n)),\n  print_duration: (n) => console.log(printDuration(n)),\n  print_span: (packed) => console.log(printSpan(packed)),\n  print_rational: (num, den) => console.log(den === 1n ? num.toString() : `${num}/${den}`),\n  print_nothing: () => console.log(\'nothing\'),\n  print_seq_i64: (h) => console.log(displaySeqI64(h)),\n  print_seq_bool: (h) => console.log(displaySeqBool(h)),\n  print_seq_word32: (h) => console.log(displaySeqWord32(h)),\n  print_seq_word64: (h) => console.log(displaySeqWord64(h)),\n  print_seq_f64: (h) => console.log(displaySeqF64(h)),\n  print_seq_text: (h) => console.log(displaySeqText(h)),\n  print_set_i64: (h) => console.log(displaySetI64(h)),\n  print_set_text: (h) => console.log(displaySetText(h)),\n  fmt_i64_into: (buf, v) => writeStr(buf, v.toString()),\n  fmt_f64_into: (buf, v) => writeStr(buf, fmtF(v)),\n  fmt_bool_into: (buf, v) => writeStr(buf, v ? \'true\' : \'false\'),\n  fmt_f64_prec_into: (buf, v, prec) => writeStr(buf, v.toFixed(Number(prec))),\n  fmt_align_into: (buf, text, width, align) => writeStr(buf, alignPad(readText(text), Number(width), Number(align))),\n  fmt_seq_i64_into: (buf, h) => writeStr(buf, displaySeqI64(h)),\n  fmt_seq_bool_into: (buf, h) => writeStr(buf, displaySeqBool(h)),\n  fmt_set_i64_into: (buf, h) => writeStr(buf, displaySetI64(h)),\n  parse_int: (h) => BigInt(parseInt(readText(h).trim(), 10) || 0),\n  parse_float: (h) => parseFloat(readText(h).trim()),\n  read_reals: () => { throw new Error(\'readReals: a page in the browser has no files to read\'); },\n  parse_timestamp: (h) => parseRfc3339(readText(h)),\n  text_contains: (haystack, needle) => (readText(haystack).includes(readText(needle)) ? 1n : 0n),\n  random_int: (lo, hi) => randomIntIn(lo, hi),\n  pow_ff: (a, b) => Math.pow(a, b),\n  pow_fi: (a, b) => Math.pow(a, Number(b)),\n  // Real wall-clock time: no test-fixed-clock concept applies to a compiled module actually\n  // running in a browser or under node \u{2014} only the Rust `wasmi` test host (which reads the\n  // tree-walker\'s own thread-local fixed clock) needs that determinism.\n  today: () => Math.floor(Date.now() / 86400000),\n  now: () => BigInt(Date.now()) * 1000000n,\n  args: () => 0,\n  // A page has no process environment: every variable `get` names is unset.\n  env_len: (h) => -1,\n  env_into: (buf, h) => 0,\n  time_now: () => BigInt(Date.now()),\n  // `write_wire_residual(data_ptr, len) -> the byte count` \u{2014} the raw bytes written straight out;\n  // a browser has no stdout, so the decoded text is logged (best-effort \u{2014} this protocol is a\n  // native/CLI wire-program mechanism, not a browser-page one).\n  write_wire_residual: (dataPtr, len) => {\n    const bytes = u8().slice(Number(dataPtr), Number(dataPtr) + Number(len));\n    console.log(dec.decode(bytes));\n    return BigInt(len);\n  },\n  // Wave 13 observability: `Op::Obs` is populated only by the OBSERVED compile profile, which the\n  // browser/CLI build never uses \u{2014} declared for ABI completeness, never actually reached today.\n  obs: (_site, _p0, _p1, _p2) => {},\n  temporal_component: (nanos, which) => temporalComponent(nanos, which),\n  temporal_component_date: (days, which) => temporalComponentDate(days, which),\n  dom_body: () => 0n,\n  // The imperative DOM calls act on the document directly. An op on an id the host no longer holds\n  // does nothing.\n  dom_create_element: (tag) => nodeId(document.createElement(readText(tag))),\n  dom_create_text_node: (t) => nodeId(document.createTextNode(readText(t))),\n  dom_set_text: (el, t) => { const n = node(el); if (n) n.textContent = readText(t); },\n  dom_set_html: (el, h) => { const n = node(el); if (n) n.innerHTML = readText(h); },\n  dom_clear_children: (el) => { const n = node(el); if (n) while (n.firstChild) n.removeChild(n.firstChild); },\n  dom_set_attribute: (el, n, v) => { const e = node(el); if (e) e.setAttribute(readText(n), readText(v)); },\n  // A boolean HTML attribute\'s PRESENCE (`disabled={not formValid}`): `on` is the Bool itself\n  // (i64 in the module\'s own value model, arriving here as a JS BigInt from wasm), never text \u{2014}\n  // see `RuntimeFn`/`HostFn::DomSetBoolAttribute`\'s doc in `vm/wasm/module.rs`.\n  dom_set_bool_attribute: (el, n, on) => {\n    const name = readText(n);\n    const e = node(el);\n    if (!e) return;\n    if (on) e.setAttribute(name, \'\'); else e.removeAttribute(name);\n  },\n  dom_append_child: (p, c) => { const a = node(p), b = node(c); if (a && b) a.appendChild(b); },\n  // A relay subscription, for the life of the page.\n  dom_on_message: (topic, handler) => { relay.subscribe(readText(topic), { handle: handler }); },\n  // A publish also reaches this page\'s own subscribers, decided in compiled LOGOS; this sends the\n  // frame. The relay excludes the originating connection, so the two never deliver twice.\n  dom_publish: (topic, message) => { const t = readText(topic); relay.open(); relay.send({ Publish: { topic: t, data: enc.encode(readText(message)) } }); },\n  // `noteTurn(name, key, changes, failure)` \u{2014} the lowered tier\'s journal entry. The reference\n  // tier\'s `the journal`/`Replay` are tree-walker constructs with no browser equivalent, so the\n  // browser keeps nothing; it exists so a lowered turn that journals itself still loads.\n  dom_note_turn: (_name, _key, _changes, _failure) => {},\n  // `journalDetail()` \u{2014} whether a lowered turn records the fields it changed: only under the test\n  // harness, which reads the journal; a shipped page keeps no journal and skips the capture.\n  dom_journal_detail: () => (globalThis.__logosHarness ? 1n : 0n),\n  // Browser storage. The area is validated rather than defaulted: a page written against an area\n  // nobody defines would store its state where nothing reads it. `storage_get` answers handle `0`\n  // for a missing key, which the compiled side reads as `Nothing` (an empty string is a value a\n  // page can store). Storage can throw (private mode, a full quota, blocked site data): a failed\n  // read reads as absent, a failed write is reported and dropped.\n  storage_set: (area, key, value) => { const s = storageArea(readText(area)); try { s.setItem(readText(key), readText(value)); } catch (e) { console.warn(\'storage is unavailable:\', e && e.message); } },\n  storage_get: (area, key) => { const s = storageArea(readText(area)); let v = null; try { v = s.getItem(readText(key)); } catch (e) { v = null; } return v === null ? 0 : allocText(v); },\n  storage_remove: (area, key) => { const s = storageArea(readText(area)); try { s.removeItem(readText(key)); } catch (e) {} },\n  dom_get_text: (el) => { const n = node(el); return allocText((n && n.textContent) || \'\'); },\n  dom_get_value: (el) => { const n = node(el); return allocText((n && n.value) || \'\'); },\n  // A whole paint as ONE buffer, decoded and applied without decisions of its own. The format is\n  // `crates/logicaffeine_compile/src/semantics/patch.rs`: a 1-byte opcode then its operands; nodes\n  // are u32; text is a u32 BYTE length then that many UTF-8 bytes. The whole buffer is decoded\n  // before anything is applied, so a malformed stream is refused whole and never half-paints.\n  // `patch_opcode_parity_lock` holds this decoder to the encoder\'s opcode list.\n  dom_apply: (patches) => {\n    const d = dv(), n = d.getInt32(patches, true), dp = d.getInt32(patches + 8, true);\n    const b = new Uint8Array(n);\n    for (let i = 0; i < n; i++) b[i] = Number(d.getBigInt64(dp + i * 8, true)) & 0xff;\n    const ops = decodePatches(byteOperands(b));\n    for (const op of ops) {\n      switch (op[0]) {\n        case \'createElement\': nodeId(document.createElement(op[2])); break;\n        case \'createText\': nodeId(document.createTextNode(op[2])); break;\n        case \'setText\': { const e = node(op[1]); if (e) e.textContent = op[2]; break; }\n        case \'setHtml\': { const e = node(op[1]); if (e) e.innerHTML = op[2]; break; }\n        case \'clearChildren\': { const p = node(op[1]); if (p) while (p.firstChild) p.removeChild(p.firstChild); break; }\n        case \'setAttribute\': { const e = node(op[1]); if (e) e.setAttribute(op[2], op[3]); break; }\n        case \'appendChild\': { const p = node(op[1]), c = node(op[2]); if (p && c) p.appendChild(c); break; }\n        case \'setValue\': { const e = node(op[1]); if (e) e.value = op[2]; break; }\n        case \'setChecked\': { const e = node(op[1]); if (e) e.checked = op[2]; break; }\n        // A listener\'s handler is a closure in the module, not data: the stream carries only the\n        // registration, and the module attaches the handler through `dom_add_event_listener`.\n        case \'addEventListener\': break;\n        // The same registration `dom_prevent_default` makes, so the two paths cannot disagree\n        // about what a modifier does.\n        case \'preventDefault\': preventDefault(op[1], op[2]); break;\n        // An opaque host MediaStream, which this host does not mint; carried by the format for a\n        // host that does, and ignored here.\n        case \'setStream\': break;\n        // A keyed diff\'s single-row removal \u{2014} the retained node\'s own `.remove()`, never a\n        // wholesale `clearChildren`/rebuild of its parent.\n        case \'removeNode\': { const n = node(op[1]); if (n) n.remove(); break; }\n        // A keyed reorder\'s minimal move: `child` keeps its identity (and whatever focus,\n        // selection or typed value it carries) by being MOVED, never recreated. `before ===\n        // 0xFFFFFFFF` is the \"no such element\" sentinel `patch.rs` documents \u{2014} append.\n        case \'insertBefore\': {\n          const p = node(op[1]), c = node(op[2]);\n          if (p && c) p.insertBefore(c, op[3] === 0xFFFFFFFF ? null : node(op[3]));\n          break;\n        }\n        case \'removeAttribute\': { const e = node(op[1]); if (e) e.removeAttribute(op[2]); break; }\n        // A hydration\'s claims name the ids the retained tree emits them under; they arrive only\n        // in the word stream a commit hands over, which `dom_apply_words` applies by those ids.\n        case \'hydrate\':\n        case \'adopt\':\n        case \'adoptText\':\n          throw new Error(`a byte patch stream sends \\`${op[0]}\\`: a hydration arrives only in the word stream a vdom commit hands over`);\n        // A listener\'s handler is a closure the module attaches directly through\n        // `dom_add_event_listener`, exactly like `addEventListener` above; the stream carries its\n        // flags, applied as `dom_listen_flags` applies them.\n        case \'listen\': listenFlags(op[1], op[2], op[3]); break;\n        case \'unlisten\': break;\n        case \'freeNode\': freeSubtree(op[1]); break;\n      }\n    }\n  },\n  // The word-format (wire v2) stream `vdom.lg`\'s `vdApply` hands over: the first `wordCount` words\n  // of `words`, whose text operands index the first `stringCount` strings, applied by the node ids\n  // they name. Answers the stream\'s hydration mismatches.\n  dom_apply_words: (words, wordCount, strings, stringCount) => {\n    const d = dv(), wp = d.getInt32(words + 8, true), sp = d.getInt32(strings + 8, true);\n    const w = [];\n    for (let i = 0; i < Number(wordCount); i++) w.push(d.getBigInt64(wp + i * 8, true));\n    const s = [];\n    for (let i = 0; i < Number(stringCount); i++) s.push(readText(d.getInt32(sp + i * 8, true)));\n    return BigInt(applyByIds(decodePatches(wordOperands(w, s))));\n  },\n  dom_set_value: (el, v) => { const n = node(el); if (n) n.value = readText(v); },\n  // A program\'s own `Type`/`Choose`: the browser\'s default action on the control, answering whether\n  // it took the value \u{2014} a text field a visitor can edit takes typing, an enabled `<select>` offering\n  // the option (by value, else by text) takes the choice.\n  dom_enter_value: (verb, sel, value) => {\n    const n = document.querySelector(readText(sel));\n    const v = readText(value);\n    if (!n || n.matches(\':disabled\')) return 0n;\n    if (readText(verb) === \'choose\') {\n      const opts = n instanceof HTMLSelectElement ? Array.from(n.options) : [];\n      const opt = opts.find((o) => o.value === v) || opts.find((o) => o.text === v);\n      if (!opt) return 0n;\n      n.value = opt.value;\n      return 1n;\n    }\n    const typed = n.localName === \'textarea\' || (n.localName === \'input\' && [\'text\', \'search\', \'url\', \'tel\', \'email\', \'password\', \'number\'].includes(n.type));\n    if (!typed || !n.matches(\':read-write\')) return 0n;\n    n.value = v;\n    return 1n;\n  },\n  // A driving statement the program runs on its own page (`Click \"#go\".` in `## Main`): the page\n  // hears the event as an entry of its own, as it hears a visitor\'s, so a turn that fails there is\n  // contained to it \u{2014} the failure reported through the page\'s failure door (`reportRuntimeFailure`)\n  // \u{2014} and the program runs on. The dispatcher answers whether the page heard the event; a failed\n  // turn was heard. The enclosing entry\'s captured message is kept aside and put back, so a later\n  // failure of the program reports its own words.\n  dom_page_entry: (node, event, value) => {\n    const outer = failMessage;\n    failMessage = \'\';\n    try {\n      return dispatchNode(node, event, value);\n    } catch (err) {\n      reportRuntimeFailure(err, failMessage);\n      return 1n;\n    } finally {\n      failMessage = outer;\n    }\n  },\n  // The PROPERTY, never `setAttribute(\'checked\', \u{2026})` \u{2014} the attribute is `defaultChecked`, so\n  // writing it leaves the box the visitor sees as it was. `on` arrives as an i64 Bool.\n  dom_set_checked: (el, on) => { const n = node(el); if (n) n.checked = Number(on) !== 0; },\n  dom_prevent_default: (el, ev) => preventDefault(Number(el), readText(ev)),\n  dom_query_selector: (sel) => { const n = document.querySelector(readText(sel)); return n ? nodeId(n) : -1n; },\n  // A DOM listener of the element\'s own, for the life of the element.\n  dom_add_event_listener: (el, event, handler) => {\n    const n = node(el);\n    if (n) ownListen(n, readText(event), handler);\n    else releaseHandle(handler);\n  },\n  dom_listen_flags: (el, event, flags) => listenFlags(Number(el), readText(event), Number(flags)),\n  // A control\'s reported state, as text. A checkbox\'s `value` is its markup\'s `\"on\"` whether it is\n  // ticked or not, so a bound checkbox hears `checked` as `\"true\"`/`\"false\"` \u{2014} what `bound_value`\n  // in the lowering converts back to a Bool, and what the native tiers report.\n  dom_event_value: (_ev) => {\n    const t = currentEvent && currentEvent.target;\n    if (!t) return allocText(\'\');\n    const ty = (t.type || \'\').toLowerCase();\n    if (ty === \'checkbox\' || ty === \'radio\') return allocText(t.checked ? \'true\' : \'false\');\n    return allocText(t.value || \'\');\n  },\n  // The key of a keyboard event as the browser names it (`\"Enter\"`, `\"Escape\"`, `\"a\"`); a key\n  // modifier compares it case-insensitively. Not a keyboard event: the empty string.\n  dom_event_key: (_ev) => allocText((currentEvent && currentEvent.key) || \'\'),\n  dom_event_target: (_ev) => currentEvent && currentEvent.target ? nodeId(currentEvent.target) : -1n,\n  dom_fetch_send: (method, url, body, headers, handler) => fetchSend(readText(method), readText(url), readText(body), readTextSeq(headers), handler),\n  dom_fetch_start: (url, handler) => { fetchSend(\'GET\', readText(url), \'\', [], handler); },\n  dom_fetch_abort: (req) => { const id = Number(req), q = fetchReqs.get(id); if (!q || q.done) return; q.done = true; q.error = \'abort\'; if (q.timer) { clearTimeout(q.timer); q.timer = 0; } q.ctrl.abort(); atTurnEnd(() => forgetFetch(id)); },\n  dom_fetch_set_timeout: (req, millis) => { const q = fetchReqs.get(Number(req)); if (!q || q.done) return; if (q.timer) { clearTimeout(q.timer); q.timer = 0; } const ms = Number(millis); if (ms > 0) q.timer = setTimeout(() => { q.timedOut = true; q.ctrl.abort(); }, ms); },\n  dom_response_status: (req) => { const q = fetchReqs.get(Number(req)); return BigInt(q && q.done ? q.status : 0); },\n  dom_response_body: (req) => { const q = fetchReqs.get(Number(req)); return allocText(q && q.done ? q.body : \'\'); },\n  dom_response_header: (req, name) => { const q = fetchReqs.get(Number(req)); return allocText((q && q.done && q.headers.get(readText(name).toLowerCase())) || \'\'); },\n  dom_response_error: (req) => { const q = fetchReqs.get(Number(req)); return allocText(q && q.done ? q.error : \'\'); },\n  dom_response_text: () => { const q = fetchReqs.get(currentFetch); return allocText(q ? q.body : \'\'); },\n  // The history/location router: the path and query read `window.location`; navigating is\n  // `history.pushState`/`replaceState`, which fire nothing \u{2014} the page delivers its own move to its\n  // own listeners; `dom_on_route` adds a route handler, and a browser back/forward (`popstate`)\n  // runs every one of them.\n  dom_route_path: () => allocText(window.location.pathname || \'/\'),\n  dom_route_query: (name) => { const v = new URLSearchParams(window.location.search).get(readText(name)); return allocText(v == null ? \'\' : v); },\n  dom_navigate: (path) => { window.history.pushState({}, \'\', readText(path)); },\n  dom_replace_route: (path) => { window.history.replaceState({}, \'\', readText(path)); },\n  // Another document at `url`: the browser leaves this page for it.\n  dom_assign_location: (url) => { window.location.assign(readText(url)); },\n  dom_on_route: (handler) => { routeHandlers.push(handler); },\n  // The page\'s own title. `document.title` is the only place that can hold it.\n  dom_set_title: (title) => { document.title = readText(title); },\n  // `Head:`\'s rendered fragment, merged into `document.head` (see `mergeIntoHead`).\n  dom_set_head_html: (html) => { mergeIntoHead(readText(html)); },\n  dom_reload: () => { window.location.reload(); },\n  // The browser raises `popstate`, which the router already listens for.\n  dom_go_back: () => { window.history.back(); },\n  // The read half of the attribute pair; a missing attribute is empty, never null.\n  dom_get_attribute: (el, name) => { const n = node(el); return allocText((n && n.getAttribute(readText(name))) || \'\'); },\n  // Three capabilities, three permission models. Each is attempted and each may be refused by the\n  // browser or the viewer; a refusal is not the page\'s failure, so none of them traps.\n  dom_clipboard_write: (t) => { try { navigator.clipboard.writeText(readText(t)); } catch (_) {} },\n  dom_download_file: (content, name) => { try { const b = new Blob([readText(content)]); const u = URL.createObjectURL(b); const a = document.createElement(\'a\'); a.href = u; a.download = readText(name); a.click(); URL.revokeObjectURL(u); } catch (_) {} },\n  dom_notify: (message, tone) => { try { const m = readText(message); if (window.Notification && Notification.permission === \'granted\') new Notification(m); else console.info(\'LOGOS notify (\' + readText(tone) + \'): \' + m); } catch (_) {} },\n  dom_element_command: (el, cmd) => { const e = node(el); if (e) elementCommand(e, readText(cmd)); },\n  // The first file a drop carried \u{2014} its name, size, type and content \u{2014} as `hear` took it off the\n  // transfer and read it before the turn began; nothing, empty and zero when the event carried none.\n  dom_event_file_name: (_ev) => { const f = currentEvent && currentEvent.__logosFile; return allocText(f ? f.name : \'\'); },\n  dom_event_file_size: (_ev) => { const f = currentEvent && currentEvent.__logosFile; return BigInt(f ? f.size : 0); },\n  dom_event_file_type: (_ev) => { const f = currentEvent && currentEvent.__logosFile; return allocText(f ? f.type : \'\'); },\n  dom_event_file_bytes: (_ev) => allocSeq((currentEvent && currentEvent.__logosFileBytes) || []),\n  // An interval a program arms itself: its handler, every `ms` milliseconds.\n  dom_set_interval: (ms, handler) => { setInterval(() => dispatch(handler, null), Number(ms)); },\n  // Wake the page when its earliest timer comes due: one pending wake at a time, replaced by every\n  // request, cancelled by a negative delay. Waking is an entry like any other, which passes the\n  // time that went by to the page \u{2014} at least the time to the timer it woke for.\n  dom_wake_after: (ms) => {\n    if (wakeTimer !== null) clearTimeout(wakeTimer);\n    wakeTimer = null;\n    wakeDue = null;\n    wakeBeforeClock = null;\n    const delay = Number(ms);\n    if (delay < 0) return;\n    if (clockAt === null) wakeBeforeClock = delay;\n    else wakeDue = clockAt + delay;\n    wakeTimer = setTimeout(() => {\n      wakeTimer = null;\n      owed = wakeDue;\n      wakeDue = null;\n      guarded(() => {});\n    }, delay);\n  },\n  // Pixels of content below what is visible. Node 0 reads the document and the viewport; any other\n  // node reads its own scroll box. The threshold is decided in compiled LOGOS.\n  dom_scroll_gap: (el) => { const n = Number(el); if (n === 0) { const d = document.documentElement; return BigInt(Math.max(0, d.scrollHeight - (window.scrollY + window.innerHeight))); } const e = node(el); return e ? BigInt(Math.max(0, e.scrollHeight - (e.scrollTop + e.clientHeight))) : 0n; },\n  // An event that happens to the PAGE (`online`, `offline`, `resize`), for the life of the page.\n  dom_on_window_event: (event, handler) => {\n    window.addEventListener(readText(event), (e) => hear(e, () => handler));\n  },\n  // A runtime failure hands its message over immediately before the module traps; without it the\n  // trap reads `RuntimeError: unreachable`.\n  logos_rt_fail: (h) => { failMessage = readText(h); },\n  // An INLINE check (a bounds test, a division guard) has no message constant \u{2014} its sentence\n  // needs a value that only exists now. The module reports the code and the value; these\n  // cases are held byte-identical to `semantics::failcode::message` by `fail_code_lock`.\n  logos_rt_fail_at: (code, value) => {\n    const v = typeof value === \'bigint\' ? value.toString() : String(value);\n    switch (code) {\n      case 1: failMessage = \'Index \' + v + \' out of bounds\'; break;\n      case 2: failMessage = \'Division by zero\'; break;\n      case 3: failMessage = \'text_from_bytes(): invalid UTF-8\'; break;\n      case 4: failMessage = \'Out of memory\'; break;\n      case 5: failMessage = \'Cannot index List with Int\'; break;\n      case 6: failMessage = \'Cannot index Text with Int\'; break;\n      case 7: failMessage = \'List index must be an integer\'; break;\n      case 8: failMessage = \'integer value out of range for this int64 context\'; break;\n      default: failMessage = \'the program failed with an unrecognized code \' + code;\n    }\n  },\n};\n// The router\'s route handlers (LOGOS closure handles), in the order the page registered them: a\n// back/forward runs each, and each reads the new path through `routePath()`.\nconst routeHandlers = [];\nwindow.addEventListener(\'popstate\', () => { for (const handler of [...routeHandlers]) dispatch(handler, null); });\n// A click that follows a link to a path this page answers itself is client navigation: the location\n// moves in place and the route handlers run, with no document load. The page decides which paths\n// are its own (`__lgx_owns`, the same `matchRoute` its `Navigate to` branches on). Everything else\n// \u{2014} another page\'s path, a cancelled click, a modified or non-primary click, a download, a link to\n// another window or origin \u{2014} is the browser\'s to carry out. Heard at the document after the page\'s\n// own listeners, so a listener\'s `.prevent` comes first.\ndocument.addEventListener(\'click\', (e) => {\n  if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n  const a = e.target instanceof Element ? e.target.closest(\'a[href]\') : null;\n  if (!a || a.hasAttribute(\'download\') || ![\'\', \'_self\', \'_top\', \'_parent\'].includes((a.getAttribute(\'target\') || \'\').trim().toLowerCase())) return;\n  const url = new URL(a.href, location.href);\n  if (url.origin !== location.origin || !inst || !inst.exports.__lgx_owns) return;\n  if (Number(guarded(() => inst.exports.__lgx_owns(allocText(url.pathname)))) === 0) return;\n  e.preventDefault();\n  window.history.pushState({}, \'\', url.pathname + url.search + url.hash);\n  for (const handler of [...routeHandlers]) dispatch(handler, null);\n});\nglobalThis.__logosHostCensus = () => ({\n  nodes: nodes.size,\n  listeners: pageListeners.size,\n  fetches: fetchReqs.size,\n  topics: relay.subs.size,\n});\n// The identity `mergeIntoHead` replaces head elements by, for tests and devtools to read.\nglobalThis.__logosHeadIdentity = headIdentity;\n// The headers a request the page makes goes out with, for tests and devtools to read.\nglobalThis.__logosRequestHeaders = requestHeaders;\n// Mount a component under the element `selector` names (the page\'s body when it names none), as one\n// guarded entry that commits its paint \u{2014} what `main` does for the page\'s own mount. Only a module\n// built for a host that mounts components on demand exports `__mount_<C>`; a shipped page has none.\nglobalThis.__logosMount = (component, selector) => {\n  const mount = inst && inst.exports[\'__mount_\' + component];\n  if (typeof mount !== \'function\') throw new Error(`this page was not built to mount ${component} on demand`);\n  const parent = selector == null ? document.body : document.querySelector(selector);\n  if (!parent) throw new Error(`nothing on the page matches ${JSON.stringify(selector)}, so ${component} has nowhere to mount`);\n  guarded(() => { mount(nodeId(parent)); settle(); });\n};\nconst bytes = await (await fetch(WASM_URL)).arrayBuffer();\nconst { instance } = await WebAssembly.instantiate(bytes, { env });\ninst = instance;\n// Through `guarded`, like every other entry. A trap still throws: nothing here pretends the program\n// completed.\nguarded(() => {\n  instance.exports.main();\n  clockAt = performance.now();\n  if (wakeBeforeClock !== null) wakeDue = clockAt + wakeBeforeClock;\n  wakeBeforeClock = null;\n});\n";
Expand description

The browser host runtime, verbatim. Kept as a file so it can be executed by a test rather than only string-matched — see runtime_js_lock.