a73x

web/verify.js

Ref:   Size: 189.9 KiB   History

#!/usr/bin/env node
// Fast non-browser smoke for the wasm core (M-web Task 4): drives
// mux_core.wasm through the page's real call sequence — attach payload,
// snapshot frame, delta frame, flat viewport readout, key encoding —
// and asserts styled cells, damage rows, and resume coordinates.
//
// THE DISCIPLINE THIS FILE DEMONSTRATES: every wasm call can grow linear
// memory, and growth detaches every cached ArrayBuffer view. Views are
// re-read from exports.memory.buffer at every access, never cached.
//
// Usage: node web/verify.js [path/to/mux_core.wasm]

'use strict';
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const wasmPath = process.argv[2] ||
  path.join(__dirname, '..', 'zig-out', 'bin', 'mux_core.wasm');

let passed = 0, failed = 0;
const clientAction = Object.freeze({
  ignored: 0,
  terminalModes: 1,
  clipboard: 2,
  bell: 3,
  selection: 4,
});

function check(name, got, want) {
  const ok = Object.is(got, want); // SameValue compares BigInts by value
  if (ok) { passed++; }
  else { failed++; console.error(`FAIL ${name}: got ${got}, want ${want}`); }
}

// Reduce JavaScript to executable tokens: comments and literal contents are
// spaces, while punctuation, identifiers, numbers, and newlines stay put.
// Template expressions recurse back into code. This is intentionally a
// lexer, not a comment-shaped regex: the verifier must not mistake a route
// written in a string/regex for behavior, nor `//` inside a regex for a
// comment. The context tracking covers every form used by mux.js and the
// control-flow/division mutation fixtures below.
function executableJsTokens(source) {
  const out = source.split('');
  let i = 0;

  const mask = (start, end) => {
    for (let j = start; j < end; j++) {
      if (out[j] !== '\n' && out[j] !== '\r') out[j] = ' ';
    }
  };

  const skipQuoted = (quote) => {
    const contentStart = ++i; // preserve the opening quote
    while (i < source.length) {
      if (source[i] === '\\') { i += 2; continue; }
      const c = source[i];
      if (c === quote) {
        mask(contentStart, i);
        i++; // preserve the closing quote
        return;
      }
      if (c === '\n' || c === '\r') {
        mask(contentStart, i);
        return;
      }
      i++;
    }
    mask(contentStart, i);
  };

  const skipRegex = () => {
    const contentStart = ++i; // preserve the opening slash
    let inClass = false;
    while (i < source.length) {
      if (source[i] === '\\') { i += 2; continue; }
      const c = source[i];
      if (c === '[') inClass = true;
      else if (c === ']') inClass = false;
      else if (c === '/' && !inClass) {
        mask(contentStart, i);
        i++; // preserve the closing slash
        const flagsStart = i;
        while (i < source.length && /[a-z]/i.test(source[i])) i++;
        mask(flagsStart, i);
        return;
      } else if (c === '\n' || c === '\r') {
        mask(contentStart, i);
        return;
      }
      i++;
    }
    mask(contentStart, i);
  };

  const expressionKeywords = new Set([
    'await', 'case', 'delete', 'do', 'else', 'in', 'instanceof', 'new',
    'of', 'return', 'throw', 'typeof', 'void', 'yield',
  ]);
  const controlKeywords = new Set(['catch', 'for', 'if', 'switch', 'while', 'with']);
  const statementKeywords = new Set(['do', 'else', 'finally', 'try']);

  let scanCode;
  const skipTemplate = () => {
    i++; // preserve the opening backtick
    let rawStart = i;
    while (i < source.length) {
      if (source[i] === '\\') { i += 2; continue; }
      if (source[i] === '`') {
        mask(rawStart, i);
        i++; // preserve the closing backtick
        return;
      }
      if (source[i] === '$' && source[i + 1] === '{') {
        mask(rawStart, i);
        i += 2;
        scanCode(true);
        rawStart = i;
        continue;
      }
      i++;
    }
    mask(rawStart, i);
  };

  scanCode = (templateExpression) => {
    let expressionAllowed = true;
    let statementExpected = !templateExpression;
    let pendingControl = false;
    let previousToken = null;
    const parenKinds = [];
    const braceKinds = [];

    while (i < source.length) {
      const c = source[i];
      const next = source[i + 1];

      if (/\s/.test(c)) { i++; continue; }
      if (templateExpression && c === '}' && braceKinds.length === 0) { i++; return; }

      if (c === '/' && next === '/') {
        const start = i;
        i += 2;
        while (i < source.length && source[i] !== '\n' && source[i] !== '\r') i++;
        mask(start, i);
        continue;
      }
      if (c === '/' && next === '*') {
        const start = i;
        i += 2;
        while (i < source.length && !(source[i] === '*' && source[i + 1] === '/')) i++;
        if (i < source.length) i += 2;
        mask(start, i);
        continue;
      }
      if (c === '"' || c === "'") {
        skipQuoted(c);
        expressionAllowed = false;
        statementExpected = false;
        previousToken = 'literal';
        continue;
      }
      if (c === '`') {
        skipTemplate();
        expressionAllowed = false;
        statementExpected = false;
        previousToken = 'literal';
        continue;
      }
      if (c === '/' && expressionAllowed) {
        skipRegex();
        expressionAllowed = false;
        statementExpected = false;
        previousToken = 'literal';
        continue;
      }

      if (/[A-Za-z_$]/.test(c)) {
        const start = i++;
        while (i < source.length && /[A-Za-z0-9_$]/.test(source[i])) i++;
        const word = source.slice(start, i);
        pendingControl = controlKeywords.has(word);
        expressionAllowed = expressionKeywords.has(word);
        statementExpected = statementKeywords.has(word);
        previousToken = word;
        continue;
      }
      if (/[0-9]/.test(c)) {
        i++;
        while (i < source.length && /[A-Za-z0-9_.]/.test(source[i])) i++;
        expressionAllowed = false;
        statementExpected = false;
        previousToken = 'number';
        continue;
      }

      if (c === '(') {
        parenKinds.push(pendingControl ? 'control' : 'expression');
        pendingControl = false;
        i++;
        expressionAllowed = true;
        statementExpected = false;
        previousToken = '(';
      } else if (c === ')') {
        const kind = parenKinds.pop() ?? 'expression';
        i++;
        expressionAllowed = kind === 'control';
        statementExpected = kind === 'control';
        previousToken = kind === 'control' ? 'control-close' : ')';
      } else if (c === '{') {
        const kind = statementExpected || !expressionAllowed || previousToken === '=>'
          ? 'block'
          : 'object';
        braceKinds.push(kind);
        i++;
        expressionAllowed = true;
        statementExpected = kind === 'block';
        previousToken = '{';
      } else if (c === '}') {
        const kind = braceKinds.pop() ?? 'block';
        i++;
        expressionAllowed = kind === 'block';
        statementExpected = kind === 'block';
        previousToken = kind === 'block' ? 'block-close' : 'object-close';
      } else if (c === '[') {
        i++;
        expressionAllowed = true;
        statementExpected = false;
        previousToken = '[';
      } else if (c === ']') {
        i++;
        expressionAllowed = false;
        statementExpected = false;
        previousToken = ']';
      } else if (c === '.' || ((c === '+' || c === '-') && next === c)) {
        i += next === c ? 2 : 1;
        expressionAllowed = false;
        statementExpected = false;
        previousToken = c === '.' ? '.' : c + next;
      } else if (c === '/') {
        i += next === '=' ? 2 : 1;
        expressionAllowed = true;
        statementExpected = false;
        previousToken = '/';
      } else if (c === ';') {
        i++;
        expressionAllowed = true;
        statementExpected = true;
        previousToken = ';';
      } else if (c === ',') {
        i++;
        expressionAllowed = true;
        statementExpected = false;
        previousToken = ',';
      } else if (c === ':' || c === '?') {
        i++;
        expressionAllowed = true;
        statementExpected = c === ':' && braceKinds.at(-1) === 'block';
        previousToken = c;
      } else if (c === '=' && next === '>') {
        i += 2;
        expressionAllowed = true;
        statementExpected = true;
        previousToken = '=>';
      } else {
        i++;
        expressionAllowed = true;
        statementExpected = false;
        previousToken = c;
      }
    }
  };

  scanCode(false);
  return out.join('');
}

function balancedBodiesAfter(source, headerPattern) {
  return [...source.matchAll(headerPattern)].map((match) => {
    const open = match.index + match[0].lastIndexOf('{');
    let depth = 1;
    for (let at = open + 1; at < source.length; at++) {
      if (source[at] === '{') depth++;
      else if (source[at] === '}' && --depth === 0) {
        return { body: source.slice(open + 1, at), start: open, end: at + 1 };
      }
    }
    return { body: null, start: open, end: source.length };
  });
}

function wasmCalls(source) {
  const executable = executableJsTokens(source);
  return [...new Set(
    [...executable.matchAll(/\bcore\.(mux_[a-z_0-9]+)/g)].map((m) => m[1]),
  )].sort();
}

function deferred() {
  let resolve, reject;
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
  return { promise, resolve, reject };
}

function executableCss(source) {
  return source.replace(/\/\*[\s\S]*?\*\//g, (comment) =>
    comment.replace(/[^\n\r]/g, ' '));
}

// opts.storage models what a browser is allowed to do with localStorage:
// a Map is a working store (prefill it to test what survives a reload),
// 'throws' is Chrome with cookies blocked — which throws on ACCESS, not
// just on write — and 'none' is no localStorage on window at all.
function browserShell(source, opts = {}) {
  let activeElement = null;
  class FakeClassList {
    constructor(owner) { this.owner = owner; }
    add(...names) {
      const values = new Set(this.owner.className.split(/\s+/).filter(Boolean));
      for (const name of names) values.add(name);
      this.owner.className = [...values].join(' ');
    }
    remove(...names) {
      const gone = new Set(names);
      this.owner.className = this.owner.className.split(/\s+/)
        .filter((name) => name && !gone.has(name)).join(' ');
    }
    contains(name) { return this.owner.className.split(/\s+/).includes(name); }
  }

  class FakeElement {
    constructor(tagName) {
      this.tagName = tagName.toUpperCase();
      this.className = '';
      this.classList = new FakeClassList(this);
      this.children = [];
      this.listeners = new Map();
      this.style = {};
      this.textContent = '';
      this.value = '';
      this.clientWidth = 640;
      this.type = '';
      this.rect = { left: 0, top: 0, width: 640, height: 480 };
      this.capturedPointers = new Set();
      // Real DOM nodes carry one; Tile stamps its id here so reorder can
      // read the wall's order back out of the document.
      this.dataset = {};
    }
    set innerHTML(value) {
      this._innerHTML = value;
      this.children = [];
      if (!value.includes('<header>')) return;
      const header = new FakeElement('header');
      const label = new FakeElement('span');
      label.className = 'label';
      const badge = new FakeElement('span');
      badge.className = 'badge connecting';
      badge.textContent = 'connecting';
      header.appendChild(label);
      // Every button in the markup, classes read from the markup: a
      // hardcoded single .copy-request is how the .close/.spawn buttons
      // arrived unconstructable and Tile broke only under this harness.
      for (const m of value.matchAll(/<button([^>]*)>([^<]*)<\/button>/g)) {
        const button = new FakeElement('button');
        button.className = /\bclass="([^"]+)"/.exec(m[1])?.[1] ?? '';
        button.type = /\btype="([^"]+)"/.exec(m[1])?.[1] ?? '';
        button.textContent = m[2];
        header.appendChild(button);
      }
      header.appendChild(badge);
      this.appendChild(header);
    }
    get innerHTML() { return this._innerHTML ?? ''; }
    appendChild(child) { this.children.push(child); return child; }
    // Real nodes contain themselves, and the page asks this of the add
    // tile to decide whether a key belongs to a form field or a terminal.
    contains(node) {
      if (node === this) return true;
      return this.children.some((child) => child.contains(node));
    }
    removeAttribute() {}
    querySelector(selector) {
      const match = (el) => selector.startsWith('.')
        ? el.className.split(/\s+/).includes(selector.slice(1))
        : el.tagName.toLowerCase() === selector.toLowerCase();
      const visit = (el) => {
        if (match(el)) return el;
        for (const child of el.children) {
          const found = visit(child);
          if (found) return found;
        }
        return null;
      };
      for (const child of this.children) {
        const found = visit(child);
        if (found) return found;
      }
      return null;
    }
    // Keyed by type, ONE listener each: the page registers exactly one per
    // (element, type) today, and silently keeping only the last would mean
    // a future double-registration was tested in name only.
    addEventListener(type, fn) {
      if (this.listeners.has(type))
        throw new Error(`FakeElement: second ${type} listener on <${this.tagName}>`);
      this.listeners.set(type, fn);
    }
    dispatchEvent(type, event) { return this.listeners.get(type)?.(event); }
    getBoundingClientRect() { return this.rect; }
    setPointerCapture(pointerId) { this.capturedPointers.add(pointerId); }
    hasPointerCapture(pointerId) { return this.capturedPointers.has(pointerId); }
    releasePointerCapture(pointerId) {
      this.capturedPointers.delete(pointerId);
      this.dispatchEvent('lostpointercapture', { pointerId });
    }
    getContext() {
      // measureText answers the font it was ASKED about: a fixed 8/11/3
      // made every font assertion vacuous, since the page's whole font
      // feature is "the cell changed size". Scaled from the 14px default
      // so the geometry every other test pins stays exactly 8/11/3.
      const ctx = {
        font: '',
        measureText() {
          const px = parseFloat(this.font) || 14;
          return {
            width: 8 * px / 14,
            fontBoundingBoxAscent: 11 * px / 14,
            fontBoundingBoxDescent: 3 * px / 14,
          };
        },
        setTransform() {}, clearRect() {}, fillRect() {}, fillText() {},
      };
      return ctx;
    }
    focus() { activeElement = this; }
    blur() { if (activeElement === this) activeElement = null; }
  }

  const elements = {
    shade: new FakeElement('div'),
    ime: new FakeElement('input'),
    wall: new FakeElement('div'),
  };
  const documentElement = new FakeElement('html');
  // A CSS custom property is set through the style object, not assigned:
  // the page publishes terminal ground this way and the fake has to see it.
  documentElement.style.setProperty = (name, value) => { documentElement.style[name] = value; };
  const documentListeners = new Map();
  const document = {
    createElement: (tag) => new FakeElement(tag),
    getElementById: (id) => elements[id],
    documentElement,
    get activeElement() { return activeElement; },
    addEventListener(type, fn) { documentListeners.set(type, fn); },
    dispatchEvent(type, event) { return documentListeners.get(type)?.(event); },
  };
  const timers = [];
  const intervals = [];
  const navigator = { clipboard: undefined };
  const windowFake = { addEventListener() {}, devicePixelRatio: 1 };
  if (opts.storage !== 'none') {
    const store = opts.storage instanceof Map ? opts.storage : new Map();
    const localStorage = {
      getItem: (key) => (store.has(key) ? store.get(key) : null),
      setItem: (key, value) => store.set(key, String(value)),
      removeItem: (key) => store.delete(key),
    };
    if (opts.storage === 'throws') {
      Object.defineProperty(windowFake, 'localStorage', {
        get() { throw new Error('SecurityError: storage is denied'); },
      });
    } else {
      windowFake.localStorage = localStorage;
    }
  }
  const context = vm.createContext({
    ArrayBuffer, DataView, JSON, Math, Promise, Set, TextDecoder, TextEncoder,
    Uint8Array, WebAssembly, console: { warn() {}, error() {} }, document,
    location: { host: 'verify.invalid' }, navigator,
    setTimeout: (fn, ms) => { timers.push({ fn, ms, cleared: false }); return timers.length; },
    clearTimeout: (id) => { if (timers[id - 1]) timers[id - 1].cleared = true; },
    setInterval: (fn, ms) => { intervals.push({ fn, ms, cleared: false }); return intervals.length; },
    clearInterval: (id) => { if (intervals[id - 1]) intervals[id - 1].cleared = true; },
    window: windowFake,
    WebSocket: class { static OPEN = 1; },
    atob: globalThis.atob,
  });
  const beforeBoot = source.split('// --- boot ---')[0];
  new vm.Script(`${beforeBoot}\n;globalThis.__verify = {\n` +
    'Tile, clipboardText: typeof clipboardText === "function" ? clipboardText : undefined, ' +
    'unzoom, setZoomedTile(tile) { zoomedTile = tile; }, ' +
    'setSettingsTile(el) { settingsTileEl = el; }, ' +
    'spawnPollDelay: typeof spawnPollDelay === "function" ? spawnPollDelay : undefined, ' +
    'SPAWN_POLL_MS: typeof SPAWN_POLL_MS === "number" ? SPAWN_POLL_MS : undefined, ' +
    'parseGhosttyTheme: typeof parseGhosttyTheme === "function" ? parseGhosttyTheme : undefined, ' +
    'applySettings: typeof applySettings === "function" ? applySettings : undefined, ' +
    'loadSettings: typeof loadSettings === "function" ? loadSettings : undefined, ' +
    'saveSettings: typeof saveSettings === "function" ? saveSettings : undefined, ' +
    'THEME_BYTES_MAX: typeof THEME_BYTES_MAX === "number" ? THEME_BYTES_MAX : undefined, ' +
    // The render globals are re-bound by applySettings, so they are read
    // through a call: a captured copy would pin the value at boot and
    // pass however badly the settings failed to land.
    'settingsState: () => ({ FONT, METRICS, PALETTE, DEFAULT_FG, DEFAULT_BG, CURSOR_FILL, SELECTION_FILL })\n' +
    '};').runInContext(context);
  return {
    ...context.__verify, context, document, documentElement, elements, navigator, timers, intervals,
    // A tile the settings tests can paint through: not registered in the
    // page's own map, so applySettings' reflow loop never reaches it and
    // each assertion paints exactly when it says it does.
    makeSettingsTile() {
      const tile = new context.__verify.Tile(9, 'settings fixture', document.createElement('div'), '');
      tile.core = {
        mux_cols: () => 10,
        mux_rows: () => 5,
        mux_history_rows: () => 0,
        mux_cursor_x: () => 1,
        mux_cursor_y: () => 2,
        mux_mark_all_dirty() {},
      };
      tile.scrollPages = 0;
      return tile;
    },
  };
}

async function flushPromises() {
  // Some clipboard fakes deliberately use then/finally to model the
  // external side effect and active-call accounting. Drain enough turns
  // for the Tile's await continuation and a serialized follow-up to run.
  for (let i = 0; i < 8; i++) await Promise.resolve();
}

async function verifyClipboardShell(shell, html) {
  const h = browserShell(shell);
  const bytes = (text) => new Uint8Array(Buffer.from(text, 'ascii'));

  const hasClipboardText = typeof h.clipboardText === 'function';
  check('shell defines the strict clipboard decoder', hasClipboardText, true);
  if (!hasClipboardText) return;

  const unicode = 'snowman ☃ and 🌍';
  check(
    'clipboard helper decodes Unicode UTF-8',
    h.clipboardText(bytes(Buffer.from(unicode, 'utf8').toString('base64'))),
    unicode,
  );
  check('clipboard helper rejects invalid UTF-8', h.clipboardText(bytes('/w==')), null);
  check('clipboard helper rejects malformed base64', h.clipboardText(bytes('%%%=')), null);

  const tileFor = (text, zoomed = true) => {
    const wall = h.document.createElement('div');
    const tile = new h.Tile(7, '<unsafe-label>', wall, '<unsafe-session>');
    const encoded = Buffer.from(text, 'utf8').toString('base64');
    const memory = { buffer: new ArrayBuffer(128) };
    new Uint8Array(memory.buffer).set(Buffer.from(encoded, 'ascii'), 17);
    tile.core = {
      memory,
      mux_clipboard_target: () => 'c'.charCodeAt(0),
      mux_clipboard_ptr: () => 17,
      mux_clipboard_len: () => encoded.length,
    };
    tile.zoomed = zoomed;
    return { tile, memory, encoded };
  };

  const architecture = tileFor('architecture');
  const hasClipboardState = architecture.tile.pendingClipboard === null
    && architecture.tile.clipboardVersion === 0
    && architecture.tile.clipboardWriteActive === false;
  const hasCopyButton = architecture.tile.copyButton?.tagName === 'BUTTON';
  check('tile initializes clipboard request state', hasClipboardState, true);
  check('tile constructor stores a real copy button', hasCopyButton, true);
  if (!hasClipboardState || !hasCopyButton) return;
  const hasClipboardMethods = typeof architecture.tile.tryClipboardWrite === 'function'
    && typeof architecture.tile.copyPendingClipboard === 'function';
  check('tile exposes automatic and manual clipboard paths', hasClipboardMethods, true);
  if (!hasClipboardMethods) return;

  const ignored = tileFor('ignored', false);
  h.navigator.clipboard = { writeText: () => { throw new Error('must not write'); } };
  check('unzoomed clipboard effect returns nothing', ignored.tile.onClipboardEffect(), undefined);
  check('unzoomed clipboard effect keeps no pending text', ignored.tile.pendingClipboard, null);
  check('unzoomed clipboard effect does not advance version', ignored.tile.clipboardVersion, 0);

  const invalidEffect = tileFor('valid seed');
  new Uint8Array(invalidEffect.memory.buffer).set(Buffer.from('/w==', 'ascii'), 17);
  invalidEffect.tile.core.mux_clipboard_len = () => 4;
  h.navigator.clipboard = { writeText: () => { throw new Error('must not write'); } };
  await invalidEffect.tile.onClipboardEffect();
  check('invalid UTF-8 effect does not create pending UI', invalidEffect.tile.pendingClipboard, null);
  check('invalid UTF-8 effect does not advance version', invalidEffect.tile.clipboardVersion, 0);
  check('invalid UTF-8 effect leaves copy control reset', `${invalidEffect.tile.copyButton.className}|${invalidEffect.tile.copyButton.textContent}`, 'copy-request|Copy');

  // The protocol accepts every Pc the native client can route, but this
  // page has exactly one destination. `p` is what an ordinary X11 mouse
  // drag writes, and proxying it here would clobber the browser user's
  // clipboard from a program that only ever asked for PRIMARY.
  const primaryTarget = tileFor('PRIMARY, not the clipboard');
  primaryTarget.tile.core.mux_clipboard_target = () => 'p'.charCodeAt(0);
  h.navigator.clipboard = { writeText: () => { throw new Error('must not write'); } };
  await primaryTarget.tile.onClipboardEffect();
  check('non-c OSC 52 target creates no pending clipboard text', primaryTarget.tile.pendingClipboard, null);
  check('non-c OSC 52 target does not advance the clipboard version', primaryTarget.tile.clipboardVersion, 0);
  check('non-c OSC 52 target leaves the copy control reset', `${primaryTarget.tile.copyButton.className}|${primaryTarget.tile.copyButton.textContent}`, 'copy-request|Copy');

  const clipboardTarget = tileFor('the real clipboard');
  const clipboardTargetWrites = [];
  h.navigator.clipboard = { writeText: (text) => { clipboardTargetWrites.push(text); return Promise.resolve(); } };
  await clipboardTarget.tile.onClipboardEffect();
  check('target c OSC 52 still reaches the system clipboard', clipboardTargetWrites.join('|'), 'the real clipboard');

  ignored.tile.pendingClipboard = 'must stay local';
  h.navigator.clipboard = { writeText: () => { throw new Error('must not write'); } };
  check('manual clipboard retry is ignored while unzoomed', ignored.tile.copyPendingClipboard(), undefined);
  check('unzoomed manual retry leaves pending text untouched', ignored.tile.pendingClipboard, 'must stay local');

  const snap = tileFor('snapshot ☃');
  const snapshotWrite = deferred();
  const snapshotCalls = [];
  h.navigator.clipboard = { writeText: (text) => { snapshotCalls.push(text); return snapshotWrite.promise; } };
  const snapshotRun = snap.tile.onClipboardEffect();
  // The staging bytes are reused the instant that call returns, and the
  // pending text below is already the decoded value: the DECODE landed
  // before the first await. That is the whole of what this can pin. It is
  // deliberately NOT a claim about the `.slice` that takes the bytes —
  // nothing runs between the copy and `clipboardText`, so swapping in
  // `.subarray` is invisible to every check in this file (measured). The
  // copy is still correct, because a view is only safe while nothing can
  // grow linear memory; it is simply not a property a test can hold up.
  new Uint8Array(snap.memory.buffer).fill('A'.charCodeAt(0), 17, 17 + snap.encoded.length);
  check('zoomed effect decodes clipboard text before any await', snap.tile.pendingClipboard, 'snapshot ☃');
  check('zoomed effect starts automatic write', snapshotCalls.join('|'), 'snapshot ☃');
  check('zoomed effect advances version', snap.tile.clipboardVersion, 1);
  snapshotWrite.resolve();
  await snapshotRun;
  check('automatic success clears pending text', snap.tile.pendingClipboard, null);
  check('automatic success shows Copied', snap.tile.copyButton.textContent, 'Copied');
  check('automatic success makes feedback visible', snap.tile.copyButton.className, 'copy-request on');
  check('automatic success schedules 1200ms hide', h.timers.at(-1)?.ms, 1200);

  // The Tab affordance makes the copy control focusable, and the 1200ms
  // timer then hides it. Focus would land on <body>, the hidden IME would
  // stop seeing compositionend, and IME input would be dead until a click.
  const hiddenUnderFocus = tileFor('hidden while focused');
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await hiddenUnderFocus.tile.onClipboardEffect();
  const hiddenUnderFocusTimer = h.timers.at(-1);
  hiddenUnderFocus.tile.copyButton.focus();
  hiddenUnderFocusTimer.fn();
  check('hiding the focused copy control returns focus to the IME', h.document.activeElement, h.elements.ime);

  const fallback = tileFor('retry me');
  h.navigator.clipboard = undefined;
  await fallback.tile.onClipboardEffect();
  check('automatic unavailable retains latest pending text', fallback.tile.pendingClipboard, 'retry me');
  check('automatic unavailable shows Copy', fallback.tile.copyButton.textContent, 'Copy');
  check('automatic unavailable is not an error', fallback.tile.copyButton.className, 'copy-request on');
  h.navigator.clipboard = { writeText: () => Promise.reject(new Error('denied')) };
  await fallback.tile.copyPendingClipboard();
  check('manual rejection retains pending text', fallback.tile.pendingClipboard, 'retry me');
  check('manual rejection says Copy failed', fallback.tile.copyButton.textContent, 'Copy failed');
  check('manual rejection uses error styling', fallback.tile.copyButton.className, 'copy-request on error');
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await fallback.tile.copyPendingClipboard();
  check('manual success clears pending text', fallback.tile.pendingClipboard, null);
  check('manual success shows Copied', fallback.tile.copyButton.textContent, 'Copied');

  const rejected = tileFor('automatic rejection');
  h.navigator.clipboard = { writeText: () => Promise.reject(new Error('denied')) };
  await rejected.tile.onClipboardEffect();
  check('automatic rejection retains pending text', rejected.tile.pendingClipboard, 'automatic rejection');
  check('automatic rejection shows retry without error', rejected.tile.copyButton.className, 'copy-request on');

  const latest = tileFor('old');
  const serial = {
    active: 0, maxActive: 0, system: null, calls: [],
    writeText(text) {
      const done = deferred();
      this.calls.push({ text, done });
      this.active++;
      this.maxActive = Math.max(this.maxActive, this.active);
      return done.promise.then(() => { this.system = text; }).finally(() => { this.active--; });
    },
  };
  h.navigator.clipboard = { writeText: serial.writeText.bind(serial) };
  const latestRun = latest.tile.onClipboardEffect();
  for (const value of ['middle one', 'middle two', 'newest']) {
    const encoded = Buffer.from(value, 'utf8').toString('base64');
    new Uint8Array(latest.memory.buffer).fill(0);
    new Uint8Array(latest.memory.buffer).set(Buffer.from(encoded, 'ascii'), 17);
    latest.tile.core.mux_clipboard_len = () => encoded.length;
    latest.tile.onClipboardEffect();
  }
  check('unresolved clipboard writes have one external call', serial.calls.length, 1);
  check('unresolved clipboard writes retain only latest pending text', latest.tile.pendingClipboard, 'newest');
  check('clipboard writes never overlap before settlement', serial.maxActive, 1);
  serial.calls[0].done.resolve();
  await flushPromises();
  check('settled old write starts exactly one coalesced follow-up', serial.calls.length, 2);
  check('coalesced follow-up skips intermediate clipboard values', serial.calls[1]?.text, 'newest');
  check('old success cannot overwrite newer pending UI', `${latest.tile.copyButton.className}|${latest.tile.copyButton.textContent}`, 'copy-request|Copy');
  serial.calls[1].done.resolve();
  await latestRun;
  check('serialized clipboard writes have maximum concurrency one', serial.maxActive, 1);
  check('serialized external clipboard finishes with newest value', serial.system, 'newest');
  check('newest serialized success clears pending text', latest.tile.pendingClipboard, null);

  const afterFailure = tileFor('old failure');
  const failedSerial = { calls: [] };
  h.navigator.clipboard = { writeText: (text) => {
    const done = deferred();
    failedSerial.calls.push({ text, done });
    return done.promise;
  } };
  const afterFailureRun = afterFailure.tile.onClipboardEffect();
  const afterFailureText = Buffer.from('latest after failure', 'utf8').toString('base64');
  new Uint8Array(afterFailure.memory.buffer).fill(0);
  new Uint8Array(afterFailure.memory.buffer).set(Buffer.from(afterFailureText, 'ascii'), 17);
  afterFailure.tile.core.mux_clipboard_len = () => afterFailureText.length;
  afterFailure.tile.onClipboardEffect();
  check('new value queues behind unresolved old write', failedSerial.calls.length, 1);
  failedSerial.calls[0].done.reject(new Error('old denied'));
  await flushPromises();
  check('old failure starts only latest queued value', failedSerial.calls[1]?.text, 'latest after failure');
  check('stale old failure does not show fallback UI', `${afterFailure.tile.copyButton.className}|${afterFailure.tile.copyButton.textContent}`, 'copy-request|Copy');
  failedSerial.calls[1].done.reject(new Error('latest denied'));
  await afterFailureRun;
  check('latest failure retains latest pending request', afterFailure.tile.pendingClipboard, 'latest after failure');
  check('latest automatic failure shows fallback UI', `${afterFailure.tile.copyButton.className}|${afterFailure.tile.copyButton.textContent}`, 'copy-request on|Copy');

  const timerRace = tileFor('copied first');
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await timerRace.tile.onClipboardEffect();
  const hide = h.timers.at(-1);
  const timerNewest = Buffer.from('pending second', 'utf8').toString('base64');
  new Uint8Array(timerRace.memory.buffer).fill(0);
  new Uint8Array(timerRace.memory.buffer).set(Buffer.from(timerNewest, 'ascii'), 17);
  timerRace.tile.core.mux_clipboard_len = () => timerNewest.length;
  h.navigator.clipboard = undefined;
  await timerRace.tile.onClipboardEffect();
  hide.fn();
  check('old success timer cannot clear newer pending text', timerRace.tile.pendingClipboard, 'pending second');
  check('old success timer cannot hide newer retry', timerRace.tile.copyButton.className, 'copy-request on');
  check('old success timer cannot overwrite newer label', timerRace.tile.copyButton.textContent, 'Copy');

  const leaving = tileFor('leave');
  const inFlight = deferred();
  const leavingCalls = [];
  h.navigator.clipboard = { writeText: (text) => { leavingCalls.push(text); return inFlight.promise; } };
  const leavingRun = leaving.tile.onClipboardEffect();
  const queuedAfterLeave = Buffer.from('queued then left', 'utf8').toString('base64');
  new Uint8Array(leaving.memory.buffer).fill(0);
  new Uint8Array(leaving.memory.buffer).set(Buffer.from(queuedAfterLeave, 'ascii'), 17);
  leaving.tile.core.mux_clipboard_len = () => queuedAfterLeave.length;
  leaving.tile.onClipboardEffect();
  check('unresolved write keeps queued latest out of clipboard API', leavingCalls.length, 1);
  let reflows = 0;
  leaving.tile.exitScroll = () => {};
  leaving.tile.reflow = () => { reflows++; };
  h.setZoomedTile(leaving.tile);
  h.unzoom();
  check('unzoom invalidates clipboard work before reflow', leaving.tile.clipboardVersion, 3);
  check('unzoom clears pending clipboard text', leaving.tile.pendingClipboard, null);
  check('unzoom releases shared copy-control ownership', leaving.tile.copyUiOwner, null);
  check('unzoom hides and resets copy button', `${leaving.tile.copyButton.className}|${leaving.tile.copyButton.textContent}`, 'copy-request|Copy');
  check('unzoom still reflows once', reflows, 1);
  inFlight.resolve();
  await leavingRun;
  check('unzoom prevents queued clipboard follow-up', leavingCalls.length, 1);
  check('in-flight success after unzoom remains invisible', `${leaving.tile.copyButton.className}|${leaving.tile.copyButton.textContent}`, 'copy-request|Copy');

  const click = tileFor('click retry');
  h.navigator.clipboard = undefined;
  await click.tile.onClipboardEffect();
  h.setZoomedTile(click.tile);
  const keyCalls = [];
  click.tile.sendKey = (...args) => { keyCalls.push(args.join(',')); };
  let tabPrevented = false;
  h.elements.ime.focus();
  h.document.dispatchEvent('keydown', {
    key: 'Tab', target: h.elements.ime,
    preventDefault: () => { tabPrevented = true; },
  });
  check('visible retry Tab is captured for button focus', tabPrevented, true);
  check('visible retry Tab focuses copy button', h.document.activeElement, click.tile.copyButton);
  check('visible retry Tab emits no terminal key', keyCalls.length, 0);

  for (const key of ['Enter', ' ', 'Tab']) {
    let prevented = false;
    h.document.dispatchEvent('keydown', {
      key, target: click.tile.copyButton,
      preventDefault: () => { prevented = true; },
    });
    check(`copy button ${JSON.stringify(key)} keeps native behavior`, prevented, false);
  }
  check('copy button native keys emit no terminal bytes', keyCalls.length, 0);

  const clickWrites = [];
  h.navigator.clipboard = { writeText: (text) => { clickWrites.push(text); return Promise.resolve(); } };
  let stopped = false;
  click.tile.copyButton.focus();
  click.tile.copyButton.dispatchEvent('click', { stopPropagation: () => { stopped = true; } });
  await flushPromises();
  check('copy button click stops tile click propagation', stopped, true);
  check('copy button click takes manual clipboard path', clickWrites.join('|'), 'click retry');
  check('copy button manual path clears pending on success', click.tile.pendingClipboard, null);
  check('copy button interaction restores IME focus', h.document.activeElement, h.elements.ime);

  const terminalTab = tileFor('terminal tab');
  h.setZoomedTile(terminalTab.tile);
  const terminalKeys = [];
  terminalTab.tile.sendKey = (...args) => { terminalKeys.push(args.join(',')); };
  let terminalTabPrevented = false;
  h.elements.ime.focus();
  h.document.dispatchEvent('keydown', {
    key: 'Tab', target: h.elements.ime,
    preventDefault: () => { terminalTabPrevented = true; },
  });
  check('hidden fallback preserves terminal Tab prevention', terminalTabPrevented, true);
  check('hidden fallback preserves terminal Tab encoding', terminalKeys.join('|'), '2,0,0');

  const header = click.tile.el.querySelector('header');
  check('tile header places copy button between label and badge', header.children.map((el) => el.className).join('|'), 'label|copy-request on|spawn|badge connecting');
  check('tile copy control is a real button', click.tile.copyButton.tagName, 'BUTTON');
  check('tile copy control has button type', click.tile.copyButton.type, 'button');
  check('tile header does not interpolate label into innerHTML', click.tile.el.innerHTML.includes('<unsafe-label>'), false);
  check('tile header does not interpolate session into innerHTML', click.tile.el.innerHTML.includes('<unsafe-session>'), false);

  const hiddenRule = /\.copy-request\s*\{[^}]*display\s*:\s*none\s*;[^}]*\}/s;
  const visibleRule = /\.copy-request\.on\s*\{[^}]*display\s*:\s*(?!none)[^;}]+\s*;[^}]*\}/s;
  const errorRule = /\.copy-request\.error\s*\{[^}]+\}/s;
  const commentedRules = executableCss(`/*
    .copy-request { display: none; }
    .copy-request.on { display: inline-block; }
    .copy-request.error { color: red; }
  */`);
  check('CSS masker rejects commented hidden rule decoy', hiddenRule.test(commentedRules), false);
  check('CSS masker rejects commented visible rule decoy', visibleRule.test(commentedRules), false);
  check('CSS masker rejects commented error rule decoy', errorRule.test(commentedRules), false);
  const executableHtmlCss = executableCss(html);
  check('page styles copy control hidden by default', hiddenRule.test(executableHtmlCss), true);
  check('page styles copy control visible on request', visibleRule.test(executableHtmlCss), true);
  check('page gives failed copy a distinct style', errorRule.test(executableHtmlCss), true);
}

async function verifySelectionShell(shell, html) {
  const h = browserShell(shell);
  const wall = h.document.createElement('div');
  const tile = new h.Tile(3, 'selection', wall, '');
  const initialState = tile.viewStartRow === 0
    && tile.selection === null
    && tile.nextSelectionId === 0
    && tile.drag === null
    && tile.lastPointerX === null
    && tile.lastPointerY === null
    && tile.selectionScrollTimer === null
    && tile.scrollRequest === null
    && tile.scrollRequestTimer === null
    && tile.selectionRequestTimer === null
    && tile.selectionRequestVersion === 0
    && tile.activeSelectionRequest === null
    && tile.selectionFeedbackTimer === null
    && tile.selectionFeedbackVersion === 0
    && tile.selectionCopyVersion === 0;
  check('tile initializes retained selection state', initialState, true);
  check('tile initializes shared copy-control ownership', tile.copyUiOwner, null);
  check(
    'tile binds all pointer selection events',
    ['pointerdown', 'pointermove', 'pointerup', 'pointercancel', 'lostpointercapture']
      .every((name) => tile.canvas.listeners.has(name)),
    true,
  );

  const executableHtmlCss = executableCss(html);
  const selectionCanvasRule = /\.tile\.zoomed\s+canvas\s*\{(?=[^}]*\bcursor\s*:\s*text\s*;)(?=[^}]*\btouch-action\s*:\s*none\s*;)[^}]*\}/s;
  const commentedSelectionRule = executableCss(`/*
    .tile.zoomed canvas { cursor: text; touch-action: none; }
  */`);
  check('CSS masker rejects commented selection-canvas decoy', selectionCanvasRule.test(commentedSelectionRule), false);
  check('zoomed canvas uses a text cursor and owns touch dragging', selectionCanvasRule.test(executableHtmlCss), true);

  const selectionMethods = [
    'cellAtPointer', 'orderedSelection', 'clearSelection', 'paintSelection',
    'beginSelection', 'moveSelection', 'endSelection', 'onSelectionReply',
    'changeScrollPages', 'autoScrollSelection', 'writeClipboardText', 'copySelection',
  ];
  check(
    'tile exposes retained-selection behavior',
    selectionMethods.every((name) => typeof tile[name] === 'function'),
    true,
  );
  if (!initialState || !selectionMethods.every((name) => typeof tile[name] === 'function')) return;

  const makeTile = () => {
    const selected = new h.Tile(4, 'selection fixture', h.document.createElement('div'), '');
    const memory = { buffer: new ArrayBuffer(1024) };
    let requestResult = 37;
    let scrollFeedResult = 0;
    let result = { id: 0, status: 3, historyRows: 30, ptr: 96, len: 0 };
    const requestCalls = [];
    selected.core = {
      memory,
      mux_cols: () => 10,
      mux_rows: () => 5,
      mux_history_rows: () => 30,
      mux_mark_all_dirty() {},
      mux_read_viewport: () => 0,
      mux_read_scroll_viewport: () => 5,
      mux_dirty_row: (i) => i,
      mux_cursor_x: () => 0,
      mux_cursor_y: () => 0,
      mux_viewport_ptr: () => 512,
      mux_input_cap: () => 1024,
      mux_input_ptr: () => 0,
      mux_output_ptr: () => 256,
      mux_output_len: () => requestResult === 37 ? 37 : 0,
      mux_scroll_start: (pages, rows) => 30 - pages * rows,
      mux_scroll_feed: () => scrollFeedResult,
      mux_init: () => 0,
      mux_attach_payload: () => 20,
      mux_client_frame: (type) => type === 0x90 ? 4 : 0,
      mux_clipboard_target: () => 'c'.charCodeAt(0),
      mux_selection_id: () => result.id | 0,
      mux_selection_status: () => result.status,
      mux_selection_history_rows: () => result.historyRows | 0,
      mux_selection_ptr: () => result.ptr,
      mux_selection_len: () => result.len,
      mux_selection_request: (id, ar, ac, br, bc) => {
        requestCalls.push([id >>> 0, ar, ac, br, bc]);
        if (requestResult !== 37) return requestResult;
        const view = new DataView(memory.buffer, 256, 37);
        view.setUint32(0, id, true);
        view.setUint32(4, ar, true);
        view.setUint16(8, ac, true);
        view.setUint32(10, br, true);
        view.setUint16(14, bc, true);
        return 37;
      },
    };
    selected.zoomed = true;
    selected.drawScale = 1;
    selected.canvas.rect = { left: 10, top: 20, width: 80, height: 70 };
    const sent = [];
    selected.sendFrame = (type, payload) => {
      sent.push({ type, payload: Uint8Array.from(payload) });
      return true;
    };
    let reflows = 0;
    selected.reflow = () => { reflows++; };
    return {
      tile: selected, memory, requestCalls, sent,
      setRequestResult(value) { requestResult = value; },
      setScrollFeedResult(value) { scrollFeedResult = value; },
      // The default history reading matches mux_history_rows above, so a
      // test that does not care about the watermark never trips it.
      setResult(id, status, textBytes, historyRows = 30) {
        const bytes = Uint8Array.from(textBytes);
        new Uint8Array(memory.buffer).set(bytes, 96);
        result = { id, status, historyRows, ptr: 96, len: bytes.length };
      },
      reflows: () => reflows,
    };
  };
  const pointer = (pointerId, col, viewRow, button = 0) => {
    let prevented = false;
    return {
      pointerId, button,
      clientX: 10 + col * 8 + 1,
      clientY: 20 + viewRow * 14 + 1,
      preventDefault() { prevented = true; },
      wasPrevented: () => prevented,
    };
  };
  const scrollEnvelope = (start, count = 5, body = [1, 2, 3]) => {
    const chunk = new Uint8Array(6 + body.length);
    new DataView(chunk.buffer).setUint32(0, start, true);
    new DataView(chunk.buffer).setUint16(4, count, true);
    chunk.set(body, 6);
    const envelope = new Uint8Array(6 + chunk.length);
    envelope[0] = 0;
    envelope[1] = 0x97;
    new DataView(envelope.buffer).setUint32(2, chunk.length, true);
    envelope.set(chunk, 6);
    return envelope;
  };
  const frameEnvelope = (type, payload) => {
    const envelope = new Uint8Array(6 + payload.length);
    envelope[0] = 0;
    envelope[1] = type;
    new DataView(envelope.buffer).setUint32(2, payload.length, true);
    envelope.set(payload, 6);
    return envelope;
  };
  const authorizeSelectionReply = (fixture) =>
    fixture.tile.activateSelectionRequest(fixture.tile.selection);

  const transport = makeTile();
  transport.tile.sendFrame = h.Tile.prototype.sendFrame.bind(transport.tile);
  const transportWrites = [];
  transport.tile.ws = { readyState: 0, send: (bytes) => transportWrites.push(bytes) };
  check('sendFrame reports a closed-socket drop', transport.tile.sendFrame(0x05, new Uint8Array()), false);
  transport.tile.ws.readyState = 1;
  check('sendFrame reports an open-socket send', transport.tile.sendFrame(0x05, new Uint8Array()), true);
  check('open sendFrame writes exactly one envelope', transportWrites.length, 1);

  const droppedScroll = makeTile();
  droppedScroll.tile.sendFrame = h.Tile.prototype.sendFrame.bind(droppedScroll.tile);
  droppedScroll.tile.ws = { readyState: 0, send() { throw new Error('closed socket must not send'); } };
  droppedScroll.tile.viewStartRow = 30;
  check('closed-socket page request reports no movement', droppedScroll.tile.changeScrollPages(1), false);
  check('closed-socket page request immediately rolls page intent back', droppedScroll.tile.scrollPages, 0);
  check('closed-socket page request retains no pending request', droppedScroll.tile.scrollRequest, null);
  check('closed-socket page request starts no response timeout', droppedScroll.tile.scrollRequestTimer, null);

  const timeoutScroll = makeTile();
  timeoutScroll.tile.viewStartRow = 30;
  timeoutScroll.tile.canvas.dispatchEvent('pointerdown', pointer(25, 2, 2));
  timeoutScroll.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(25, 4, 0), clientY: 10,
  });
  const timeoutAutoTimer = h.intervals[timeoutScroll.tile.selectionScrollTimer - 1];
  timeoutAutoTimer.fn();
  const responseTimeout = h.timers[timeoutScroll.tile.scrollRequestTimer - 1];
  check('scroll request timeout is bounded to 2000ms', responseTimeout?.ms, 2000);
  timeoutAutoTimer.fn();
  check('120ms auto-scroll timer does not retry before response timeout', timeoutScroll.sent.length, 1);
  responseTimeout.fn();
  check('silent scroll server timeout rolls page intent back', timeoutScroll.tile.scrollPages, 0);
  check('silent scroll server timeout releases pending request', timeoutScroll.tile.scrollRequest, null);
  check('silent scroll server timeout clears timer identity', timeoutScroll.tile.scrollRequestTimer, null);
  timeoutAutoTimer.fn();
  check('auto-scroll retries once after bounded response timeout', timeoutScroll.sent.length, 2);
  timeoutScroll.tile.canvas.dispatchEvent('pointercancel', { pointerId: 25 });

  const staleTimeout = makeTile();
  staleTimeout.tile.viewStartRow = 30;
  staleTimeout.tile.paintScroll = () => {};
  staleTimeout.tile.changeScrollPages(1);
  const firstTimeout = h.timers[staleTimeout.tile.scrollRequestTimer - 1];
  staleTimeout.tile.onMessage(scrollEnvelope(25));
  check('successful scroll reply clears its response timeout', firstTimeout?.cleared, true);
  staleTimeout.tile.changeScrollPages(1);
  const secondRequest = staleTimeout.tile.scrollRequest;
  firstTimeout.fn();
  check('stale response timeout cannot roll back a later request', staleTimeout.tile.scrollRequest, secondRequest);
  check('stale response timeout cannot change later requested page', staleTimeout.tile.scrollPages, 2);

  const lifecycleScroll = makeTile();
  lifecycleScroll.tile.viewStartRow = 30;
  lifecycleScroll.tile.connect();
  lifecycleScroll.tile.changeScrollPages(1);
  const closeTimeout = h.timers[lifecycleScroll.tile.scrollRequestTimer - 1];
  lifecycleScroll.tile.ws.onclose();
  check('socket close rolls pending scroll request back', lifecycleScroll.tile.scrollRequest, null);
  check('socket close clears pending scroll timeout', closeTimeout?.cleared, true);
  lifecycleScroll.tile.changeScrollPages(1);
  const openTimeout = h.timers[lifecycleScroll.tile.scrollRequestTimer - 1];
  lifecycleScroll.tile.ws.onopen();
  check('socket open invalidates pre-open scroll request', lifecycleScroll.tile.scrollRequest, null);
  check('socket open clears pre-open scroll timeout', openTimeout?.cleared, true);

  const attachScroll = makeTile();
  attachScroll.tile.viewStartRow = 30;
  attachScroll.tile.changeScrollPages(1);
  const attachTimeout = h.timers[attachScroll.tile.scrollRequestTimer - 1];
  attachScroll.tile.sendAttach(false);
  check('reattach rolls pending scroll request back', attachScroll.tile.scrollRequest, null);
  check('reattach clears pending scroll timeout', attachTimeout?.cleared, true);

  const controlLifecycle = makeTile();
  controlLifecycle.tile.viewStartRow = 30;
  controlLifecycle.tile.changeScrollPages(1);
  const controlTimeout = h.timers[controlLifecycle.tile.scrollRequestTimer - 1];
  const reconnectingControl = Uint8Array.from([
    1, ...Buffer.from(JSON.stringify({ state: 'reconnecting' }), 'utf8'),
  ]);
  controlLifecycle.tile.onMessage(reconnectingControl);
  check('daemon reconnect lifecycle rolls pending scroll request back', controlLifecycle.tile.scrollRequest, null);
  check('daemon reconnect lifecycle clears pending scroll timeout', controlTimeout?.cleared, true);

  const exitLifecycle = makeTile();
  exitLifecycle.tile.viewStartRow = 30;
  exitLifecycle.tile.changeScrollPages(1);
  const exitTimeout = h.timers[exitLifecycle.tile.scrollRequestTimer - 1];
  exitLifecycle.tile.onMessage(Uint8Array.from([0, 0x82, 1, 0, 0, 0, 0]));
  check('session exit rolls pending scroll request back', exitLifecycle.tile.scrollRequest, null);
  check('session exit clears pending scroll timeout', exitTimeout?.cleared, true);

  const controlClearsSelection = makeTile();
  controlClearsSelection.tile.selection = {
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 61, text: 'daemon leg dropped',
  };
  controlClearsSelection.tile.onMessage(reconnectingControl);
  check('daemon reconnect lifecycle clears the retained selection', controlClearsSelection.tile.selection, null);

  const exitClearsSelection = makeTile();
  exitClearsSelection.tile.selection = {
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 62, text: 'outlived the shell',
  };
  exitClearsSelection.tile.onMessage(Uint8Array.from([0, 0x82, 1, 0, 0, 0, 0]));
  check('session exit clears the retained selection', exitClearsSelection.tile.selection, null);

  const countMismatch = makeTile();
  countMismatch.tile.viewStartRow = 30;
  let countPaints = 0;
  countMismatch.tile.paintScroll = () => { countPaints++; };
  countMismatch.tile.changeScrollPages(1);
  countMismatch.tile.onMessage(scrollEnvelope(25, 4));
  check('matching start with wrong echoed count is not painted', countPaints, 0);
  check('wrong echoed count leaves exact request pending', countMismatch.tile.scrollRequest !== null, true);
  countMismatch.tile.onMessage(scrollEnvelope(25, 5));
  check('matching start and count paints requested viewport', countPaints, 1);

  const resizeMismatch = makeTile();
  resizeMismatch.tile.viewStartRow = 30;
  resizeMismatch.tile.changeScrollPages(1);
  const resizeTimeout = h.timers[resizeMismatch.tile.scrollRequestTimer - 1];
  resizeMismatch.tile.zoomCols = () => 11;
  resizeMismatch.tile.zoomRows = () => 6;
  resizeMismatch.tile.sendResizeIfDiffers();
  check('grid resize invalidates old-count scroll request', resizeMismatch.tile.scrollRequest, null);
  check('grid resize clears old-count scroll timeout', resizeTimeout?.cleared, true);

  const snapshotHistory = makeTile();
  let snapshotHistoryRows = 30, snapshotRows = 5;
  snapshotHistory.tile.core.mux_history_rows = () => snapshotHistoryRows;
  snapshotHistory.tile.core.mux_rows = () => snapshotRows;
  snapshotHistory.tile.core.mux_apply_frame = () => {
    snapshotHistoryRows = 40;
    snapshotRows = 6;
    return 0;
  };
  snapshotHistory.tile.scrollPages = 2;
  snapshotHistory.tile.viewStartRow = 20;
  snapshotHistory.tile.selection = {
    anchor: { row: 21, col: 1 }, active: { row: 24, col: 3 }, requestId: 90, text: 'old grid',
  };
  snapshotHistory.tile.startSelectionRequestTimeout(
    snapshotHistory.tile.activateSelectionRequest(snapshotHistory.tile.selection),
  );
  const snapshotSelectionHandle = snapshotHistory.tile.selectionRequestTimer;
  const snapshotSelectionTimer = snapshotSelectionHandle === null
    ? undefined : h.timers[snapshotSelectionHandle - 1];
  snapshotHistory.tile.sizeCanvas = () => {};
  let snapshotLivePaints = 0, snapshotScrollPaints = 0;
  const realSnapshotLivePaint = h.Tile.prototype.paintLive.bind(snapshotHistory.tile);
  snapshotHistory.tile.paintLive = () => { snapshotLivePaints++; realSnapshotLivePaint(); };
  snapshotHistory.tile.paintScroll = () => { snapshotScrollPaints++; };
  snapshotHistory.tile.onMessage(frameEnvelope(0x95, Uint8Array.from([1, 2, 3])));
  check('authoritative snapshot exits settled history mode', snapshotHistory.tile.scrollPages, 0);
  check('authoritative snapshot clears old-grid selection', snapshotHistory.tile.selection, null);
  check('authoritative snapshot repaints new live geometry exactly once', snapshotLivePaints, 1);
  check('authoritative snapshot never repaints stale history scratch', snapshotScrollPaints, 0);
  check('snapshot live repaint adopts new history start', snapshotHistory.tile.viewStartRow, 40);
  check('snapshot leaves no scroll request timer', snapshotHistory.tile.scrollRequestTimer, null);
  check('snapshot clears old-grid selection response timeout', snapshotSelectionTimer?.cleared, true);
  check('snapshot leaves no selection response timer identity', snapshotHistory.tile.selectionRequestTimer, null);
  check('snapshot lifts history badge after successful apply', snapshotHistory.tile.el.querySelector('.badge').classList.contains('scroll'), false);
  check(
    'post-snapshot pointer coordinates use new live geometry',
    snapshotHistory.tile.cellAtPointer(pointer(30, 4, 5)).row,
    45,
  );

  const failedSnapshot = makeTile();
  failedSnapshot.tile.scrollPages = 2;
  failedSnapshot.tile.viewStartRow = 20;
  failedSnapshot.tile.selection = {
    anchor: { row: 21, col: 1 }, active: { row: 24, col: 3 }, requestId: 91, text: 'failed old grid',
  };
  failedSnapshot.tile.core.mux_apply_frame = () => -3;
  failedSnapshot.tile.onMessage(frameEnvelope(0x95, Uint8Array.from([9])));
  check('failed authoritative snapshot follows reset to live mode', failedSnapshot.tile.scrollPages, 0);
  check('failed authoritative snapshot clears stale selection', failedSnapshot.tile.selection, null);
  check('failed authoritative snapshot repaints through reset path once', failedSnapshot.reflows(), 1);

  const coordinate = makeTile();
  coordinate.tile.viewStartRow = 40;
  check(
    'pointer coordinates include the retained viewport start',
    JSON.stringify(coordinate.tile.cellAtPointer(pointer(1, 3, 2))),
    JSON.stringify({ col: 3, viewRow: 2, row: 42 }),
  );
  check(
    'pointer coordinates clamp outside the visible grid',
    JSON.stringify(coordinate.tile.cellAtPointer({ clientX: -50, clientY: 999 })),
    JSON.stringify({ col: 0, viewRow: 4, row: 44 }),
  );
  coordinate.tile.drawScale = 0.5;
  h.context.window.devicePixelRatio = 2;
  check(
    'fractional canvas scale maps CSS pixels without applying DPR',
    JSON.stringify(coordinate.tile.cellAtPointer({ clientX: 23, clientY: 36 })),
    JSON.stringify({ col: 3, viewRow: 2, row: 42 }),
  );
  h.context.window.devicePixelRatio = 1;
  const withoutScaleDivisor = shell
    .replace('/ this.drawScale / METRICS.w', '/ METRICS.w')
    .replace('/ this.drawScale / METRICS.h', '/ METRICS.h');
  check(
    'fractional-scale mutation removes both scale divisors',
    shell.includes('/ this.drawScale / METRICS.w')
      && shell.includes('/ this.drawScale / METRICS.h')
      && !withoutScaleDivisor.includes('/ this.drawScale / METRICS.w')
      && !withoutScaleDivisor.includes('/ this.drawScale / METRICS.h'),
    true,
  );
  const mutatedScale = browserShell(withoutScaleDivisor);
  const mutatedTile = new mutatedScale.Tile(
    5, 'scale mutation', mutatedScale.document.createElement('div'), '',
  );
  mutatedTile.core = { mux_cols: () => 10, mux_rows: () => 5 };
  mutatedTile.drawScale = 0.5;
  mutatedTile.viewStartRow = 40;
  mutatedTile.canvas.rect = { left: 10, top: 20, width: 80, height: 70 };
  mutatedScale.context.window.devicePixelRatio = 2;
  check(
    'removing the draw-scale divisor changes real pointer mapping',
    JSON.stringify(mutatedTile.cellAtPointer({ clientX: 23, clientY: 36 }))
      !== JSON.stringify({ col: 3, viewRow: 2, row: 42 }),
    true,
  );

  const ignored = makeTile();
  ignored.tile.zoomed = false;
  ignored.tile.canvas.dispatchEvent('pointerdown', pointer(1, 2, 2));
  ignored.tile.zoomed = true;
  ignored.tile.canvas.dispatchEvent('pointerdown', pointer(2, 2, 2, 1));
  check('unzoomed and non-left pointer downs are ignored', ignored.tile.selection, null);
  check('ignored pointer downs capture nothing', ignored.tile.canvas.capturedPointers.size, 0);

  const forward = makeTile();
  forward.tile.viewStartRow = 30;
  const down = pointer(7, 2, 1);
  const move = pointer(7, 5, 3);
  const up = pointer(7, 5, 3);
  forward.tile.canvas.dispatchEvent('pointerdown', down);
  check('left drag start prevents native canvas selection', down.wasPrevented(), true);
  check('left drag captures its pointer', forward.tile.canvas.hasPointerCapture(7), true);
  check('left drag starts the 120ms scroll timer', h.intervals.at(-1)?.ms, 120);
  forward.tile.canvas.dispatchEvent('pointermove', move);
  check('drag move prevents native pointer behavior', move.wasPrevented(), true);
  check(
    'forward drag retains absolute anchor and active geometry',
    JSON.stringify(forward.tile.orderedSelection()),
    JSON.stringify([{ col: 2, viewRow: 1, row: 31 }, { col: 5, viewRow: 3, row: 33 }]),
  );
  forward.tile.canvas.dispatchEvent('pointerup', up);
  check('drag release prevents native pointer behavior', up.wasPrevented(), true);
  check('drag release gives up pointer capture', forward.tile.canvas.hasPointerCapture(7), false);
  check('normal capture release preserves completed selection', forward.tile.selection !== null, true);
  check('drag release clears the scroll timer', forward.tile.selectionScrollTimer, null);
  check('drag release sends exactly one frame', forward.sent.length, 1);
  check('drag release uses selection-request wire type', forward.sent[0]?.type, 0x0b);
  check(
    'drag release passes forward daemon coordinates once',
    JSON.stringify(forward.requestCalls),
    JSON.stringify([[1, 31, 2, 33, 5]]),
  );
  check(
    'drag release sends the exact 37-byte request',
    Buffer.from(forward.sent[0]?.payload ?? []).toString('hex'),
    '010000001f0000000200210000000500000000000000000000000000000000000000000000',
  );

  const reverse = makeTile();
  reverse.tile.viewStartRow = 50;
  reverse.tile.canvas.dispatchEvent('pointerdown', pointer(8, 7, 4));
  reverse.tile.canvas.dispatchEvent('pointermove', pointer(8, 1, 0));
  check(
    'reverse drag normalizes retained geometry for painting',
    JSON.stringify(reverse.tile.orderedSelection()),
    JSON.stringify([{ col: 1, viewRow: 0, row: 50 }, { col: 7, viewRow: 4, row: 54 }]),
  );
  reverse.tile.canvas.dispatchEvent('pointerup', pointer(8, 1, 0));
  check(
    'reverse request preserves anchor and active direction for daemon extraction',
    JSON.stringify(reverse.requestCalls),
    JSON.stringify([[1, 54, 7, 50, 1]]),
  );

  const clickOnly = makeTile();
  clickOnly.tile.selection = { anchor: { row: 1, col: 1 }, active: { row: 2, col: 2 }, requestId: 9, text: 'old' };
  clickOnly.tile.canvas.dispatchEvent('pointerdown', pointer(9, 4, 2));
  clickOnly.tile.canvas.dispatchEvent('pointerup', pointer(9, 4, 2));
  check('click without movement clears retained selection', clickOnly.tile.selection, null);
  check('click without movement sends no request', clickOnly.sent.length, 0);

  const cancelled = makeTile();
  cancelled.tile.canvas.dispatchEvent('pointerdown', pointer(10, 2, 1));
  const cancelTimer = cancelled.tile.selectionScrollTimer;
  cancelled.tile.canvas.dispatchEvent('pointercancel', { pointerId: 10 });
  check('pointer cancel clears retained selection', cancelled.tile.selection, null);
  check('pointer cancel clears drag state', cancelled.tile.drag, null);
  check('pointer cancel stops the scroll timer', h.intervals[cancelTimer - 1]?.cleared, true);
  check('pointer cancel sends no request', cancelled.sent.length, 0);

  const lost = makeTile();
  lost.tile.canvas.dispatchEvent('pointerdown', pointer(12, 2, 1));
  const lostTimer = lost.tile.selectionScrollTimer;
  lost.tile.canvas.capturedPointers.delete(12);
  lost.tile.canvas.dispatchEvent('lostpointercapture', { pointerId: 12 });
  check('unexpected lost pointer capture clears selection', lost.tile.selection, null);
  check('unexpected lost pointer capture clears drag state', lost.tile.drag, null);
  check('unexpected lost pointer capture clears pointer position', lost.tile.lastPointerY, null);
  check('unexpected lost pointer capture stops the timer', h.intervals[lostTimer - 1]?.cleared, true);
  check('unexpected lost pointer capture sends no request', lost.sent.length, 0);

  const refused = makeTile();
  refused.setRequestResult(-3);
  refused.tile.canvas.dispatchEvent('pointerdown', pointer(11, 1, 1));
  refused.tile.canvas.dispatchEvent('pointermove', pointer(11, 2, 2));
  refused.tile.canvas.dispatchEvent('pointerup', pointer(11, 2, 2));
  check('failed selection request clears retained selection', refused.tile.selection, null);
  check('failed selection request sends no frame', refused.sent.length, 0);

  const scrolling = makeTile();
  scrolling.tile.scrollPages = 0;
  scrolling.tile.viewStartRow = 30;
  scrolling.tile.onWheel({ deltaY: -1 });
  check('wheel request preserves the currently painted start row', scrolling.tile.viewStartRow, 30);
  check('wheel sends scrollback fetch', scrolling.sent[0]?.type, 0x05);
  scrolling.tile.canvas.dispatchEvent('pointerdown', pointer(13, 2, 2));
  check('quick drag before history reply maps against painted rows', scrolling.tile.selection.anchor.row, 32);
  scrolling.tile.canvas.dispatchEvent('pointercancel', { pointerId: 13 });
  let paintedScroll = 0;
  scrolling.tile.paintScroll = () => { paintedScroll++; };
  const chunk = new Uint8Array(6 + 3);
  new DataView(chunk.buffer).setUint32(0, 25, true);
  new DataView(chunk.buffer).setUint16(4, 5, true);
  chunk.set([1, 2, 3], 6);
  const envelope = new Uint8Array(6 + chunk.length);
  envelope[0] = 0;
  envelope[1] = 0x97;
  new DataView(envelope.buffer).setUint32(2, chunk.length, true);
  envelope.set(chunk, 6);
  scrolling.tile.onMessage(envelope);
  check('matching scrollback echo becomes the displayed start row', scrolling.tile.viewStartRow, 25);
  check('scrollback echo paints the history viewport', paintedScroll, 1);

  // A wheel tick arriving while a fetch is outstanding must not replace
  // scrollRequest and scrollRequestTimer without clearing the old timer:
  // that leaks one setTimeout per tick and leaves a rollback armed for a
  // request nobody is waiting on any more.
  const wheelDuringFetch = makeTile();
  wheelDuringFetch.tile.viewStartRow = 30;
  wheelDuringFetch.tile.changeScrollPages(1);
  const inFlightScroll = wheelDuringFetch.tile.scrollRequest;
  const inFlightScrollTimer = wheelDuringFetch.tile.scrollRequestTimer;
  wheelDuringFetch.tile.onWheel({ deltaY: -1 });
  check('a wheel tick during a pending fetch sends nothing', wheelDuringFetch.sent.length, 1);
  check('a wheel tick during a pending fetch keeps the one request', wheelDuringFetch.tile.scrollRequest, inFlightScroll);
  check('a wheel tick during a pending fetch keeps its one timer', wheelDuringFetch.tile.scrollRequestTimer, inFlightScrollTimer);
  check('a wheel tick during a pending fetch does not move page intent', wheelDuringFetch.tile.scrollPages, 1);

  const failedStage = makeTile();
  failedStage.tile.scrollPages = 1;
  failedStage.tile.viewStartRow = 30;
  failedStage.tile.stage = () => false;
  failedStage.tile.onMessage(envelope);
  check('failed scrollback staging preserves painted start row', failedStage.tile.viewStartRow, 30);

  const failedFeed = makeTile();
  failedFeed.tile.scrollPages = 1;
  failedFeed.tile.viewStartRow = 30;
  failedFeed.setScrollFeedResult(-3);
  failedFeed.tile.onMessage(envelope);
  check('failed scrollback decode preserves painted start row', failedFeed.tile.viewStartRow, 30);

  const auto = makeTile();
  auto.tile.viewStartRow = 30;
  let autoPaints = 0;
  auto.tile.paintScroll = () => { autoPaints++; };
  auto.tile.canvas.dispatchEvent('pointerdown', pointer(18, 2, 2));
  auto.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(18, 4, 0), clientY: 10,
  });
  const autoTimer = h.intervals[auto.tile.selectionScrollTimer - 1];
  autoTimer.fn();
  check('outside drag timer requests one older page without pointermove', auto.sent.length, 1);
  check('auto-scroll uses fetch-scrollback wire type', auto.sent[0]?.type, 0x05);
  check('auto-scroll keeps coordinates tied to painted page while pending', auto.tile.selection.active.row, 30);
  check('auto-scroll keeps painted start unchanged while pending', auto.tile.viewStartRow, 30);
  autoTimer.fn();
  check('auto-scroll does not stack requests while a paint is pending', auto.sent.length, 1);
  auto.tile.onMessage(scrollEnvelope(20));
  check('out-of-order history reply cannot move the painted viewport', auto.tile.viewStartRow, 30);
  check('out-of-order history reply cannot move the drag endpoint', auto.tile.selection.active.row, 30);
  check('out-of-order history reply does not paint', autoPaints, 0);
  auto.tile.onMessage(scrollEnvelope(25));
  check('matching successful history reply advances painted viewport', auto.tile.viewStartRow, 25);
  check('matching successful history reply moves drag to painted boundary', auto.tile.selection.active.row, 25);
  check('matching successful history reply remaps latest pointer column', auto.tile.selection.active.col, 4);
  check('matching successful history reply retains boundary view row', auto.tile.selection.active.viewRow, 0);
  check('matching successful history reply paints once', autoPaints, 1);
  autoTimer.fn();
  check('timer continues to the next page while pointer remains outside', auto.sent.length, 2);
  check(
    'continued auto-scroll requests exact next viewport',
    Buffer.from(auto.sent[1]?.payload ?? []).toString('hex'),
    '140000000500',
  );
  auto.tile.canvas.dispatchEvent('pointercancel', { pointerId: 18 });

  const insideDrift = makeTile();
  insideDrift.tile.viewStartRow = 30;
  insideDrift.tile.paintScroll = () => {};
  insideDrift.tile.canvas.dispatchEvent('pointerdown', pointer(26, 2, 2));
  insideDrift.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(26, 4, 0), clientY: 10,
  });
  h.intervals[insideDrift.tile.selectionScrollTimer - 1].fn();
  insideDrift.tile.canvas.dispatchEvent('pointermove', pointer(26, 7, 3));
  check('pointer returning inside pending page still names old painted row', insideDrift.tile.selection.active.row, 33);
  insideDrift.tile.onMessage(scrollEnvelope(25));
  check('matching paint remaps inside pointer to new painted row', insideDrift.tile.selection.active.row, 28);
  check('matching paint remaps latest inside pointer X', insideDrift.tile.selection.active.col, 7);
  insideDrift.tile.canvas.dispatchEvent('pointercancel', { pointerId: 26 });

  const outsideXDrift = makeTile();
  outsideXDrift.tile.viewStartRow = 30;
  outsideXDrift.tile.paintScroll = () => {};
  outsideXDrift.tile.canvas.dispatchEvent('pointerdown', pointer(27, 2, 2));
  outsideXDrift.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(27, 4, 0), clientY: 10,
  });
  h.intervals[outsideXDrift.tile.selectionScrollTimer - 1].fn();
  outsideXDrift.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(27, 8, 0), clientY: 10,
  });
  outsideXDrift.tile.onMessage(scrollEnvelope(25));
  check('matching paint remaps changed outside pointer X', outsideXDrift.tile.selection.active.col, 8);
  check('matching paint clamps changed outside pointer to painted boundary', outsideXDrift.tile.selection.active.row, 25);
  outsideXDrift.tile.canvas.dispatchEvent('pointercancel', { pointerId: 27 });

  const failedAuto = makeTile();
  failedAuto.tile.viewStartRow = 30;
  failedAuto.tile.canvas.dispatchEvent('pointerdown', pointer(19, 2, 2));
  failedAuto.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(19, 4, 0), clientY: 10,
  });
  const failedAutoTimer = h.intervals[failedAuto.tile.selectionScrollTimer - 1];
  failedAutoTimer.fn();
  failedAuto.setScrollFeedResult(-3);
  const failedDecodeTimeout = h.timers[failedAuto.tile.scrollRequestTimer - 1];
  failedAuto.tile.onMessage(scrollEnvelope(25));
  check('failed auto-scroll reply preserves painted viewport', failedAuto.tile.viewStartRow, 30);
  check('failed auto-scroll reply preserves displayed endpoint', failedAuto.tile.selection.active.row, 30);
  check('failed auto-scroll reply rolls requested page back', failedAuto.tile.scrollPages, 0);
  check('failed auto-scroll reply clears response timeout', failedDecodeTimeout?.cleared, true);
  failedAuto.setScrollFeedResult(0);
  failedAutoTimer.fn();
  check('auto-scroll retries the same page after a failed decode', failedAuto.sent.length, 2);
  check(
    'auto-scroll retry keeps exact failed viewport coordinates',
    Buffer.from(failedAuto.sent[1]?.payload ?? []).toString('hex'),
    '190000000500',
  );
  failedAuto.tile.canvas.dispatchEvent('pointercancel', { pointerId: 19 });

  const failedAutoStage = makeTile();
  failedAutoStage.tile.viewStartRow = 30;
  failedAutoStage.tile.canvas.dispatchEvent('pointerdown', pointer(22, 2, 2));
  failedAutoStage.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(22, 4, 0), clientY: 10,
  });
  const failedStageTimer = h.intervals[failedAutoStage.tile.selectionScrollTimer - 1];
  failedStageTimer.fn();
  failedAutoStage.tile.stage = () => false;
  const failedStageTimeout = h.timers[failedAutoStage.tile.scrollRequestTimer - 1];
  failedAutoStage.tile.onMessage(scrollEnvelope(25));
  check('failed auto-scroll staging rolls requested page back', failedAutoStage.tile.scrollPages, 0);
  check('failed auto-scroll staging preserves painted endpoint', failedAutoStage.tile.selection.active.row, 30);
  check('failed auto-scroll staging clears response timeout', failedStageTimeout?.cleared, true);
  failedAutoStage.tile.stage = h.Tile.prototype.stage.bind(failedAutoStage.tile);
  failedStageTimer.fn();
  check('auto-scroll retries after failed staging', failedAutoStage.sent.length, 2);
  failedAutoStage.tile.canvas.dispatchEvent('pointercancel', { pointerId: 22 });

  const malformedAuto = makeTile();
  malformedAuto.tile.viewStartRow = 30;
  malformedAuto.tile.canvas.dispatchEvent('pointerdown', pointer(24, 2, 2));
  malformedAuto.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(24, 4, 0), clientY: 10,
  });
  h.intervals[malformedAuto.tile.selectionScrollTimer - 1].fn();
  const malformedScroll = new Uint8Array([0, 0x97, 1, 0, 0, 0, 0]);
  malformedAuto.tile.onMessage(malformedScroll);
  check('malformed matching scroll lane releases pending page intent', malformedAuto.tile.scrollPages, 0);
  check('malformed matching scroll lane preserves painted start', malformedAuto.tile.viewStartRow, 30);
  malformedAuto.tile.canvas.dispatchEvent('pointercancel', { pointerId: 24 });

  const edgeAuto = makeTile();
  edgeAuto.tile.scrollPages = 6;
  edgeAuto.tile.viewStartRow = 0;
  edgeAuto.tile.canvas.dispatchEvent('pointerdown', pointer(20, 3, 0));
  edgeAuto.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(20, 3, 0), clientY: 10,
  });
  h.intervals[edgeAuto.tile.selectionScrollTimer - 1].fn();
  check('auto-scroll is bounded at oldest history page', edgeAuto.sent.length, 0);
  check('bounded auto-scroll retains painted endpoint', edgeAuto.tile.selection.active.row, 0);
  edgeAuto.tile.canvas.dispatchEvent('pointercancel', { pointerId: 20 });

  // A drag that leaves the canvas at the very top never changes cell —
  // cellAtPointer clamps to view row 0 — so `moved` can only become true
  // when auto-scroll remaps the endpoint onto a genuinely different
  // absolute row. Without that remap setting the flag, pointerup takes the
  // plain-click branch and throws a multi-page selection away.
  const clampedDrag = makeTile();
  clampedDrag.tile.viewStartRow = 30;
  clampedDrag.tile.paintScroll = () => {};
  clampedDrag.tile.canvas.dispatchEvent('pointerdown', pointer(44, 2, 0));
  clampedDrag.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(44, 2, 0), clientY: 10,
  });
  check('a drag clamped at the top edge has not moved a cell yet', clampedDrag.tile.selection.active.row, 30);
  h.intervals[clampedDrag.tile.selectionScrollTimer - 1].fn();
  clampedDrag.tile.onMessage(scrollEnvelope(25));
  check('the painted older page remaps the clamped endpoint', clampedDrag.tile.selection.active.row, 25);
  clampedDrag.tile.canvas.dispatchEvent('pointerup', {
    ...pointer(44, 2, 0), clientY: 10,
  });
  check('a page-crossing drag is not discarded as a plain click', clampedDrag.tile.selection !== null, true);
  check(
    'a page-crossing drag requests the rows it actually spanned',
    JSON.stringify(clampedDrag.requestCalls),
    JSON.stringify([[1, 30, 2, 25, 2]]),
  );

  const releaseRace = makeTile();
  releaseRace.tile.viewStartRow = 30;
  let releasePaints = 0;
  releaseRace.tile.paintScroll = () => { releasePaints++; };
  releaseRace.tile.canvas.dispatchEvent('pointerdown', pointer(21, 2, 2));
  const outside = { ...pointer(21, 4, 0), clientY: 10 };
  releaseRace.tile.canvas.dispatchEvent('pointermove', outside);
  h.intervals[releaseRace.tile.selectionScrollTimer - 1].fn();
  releaseRace.tile.canvas.dispatchEvent('pointerup', outside);
  check(
    'release before scroll reply requests only displayed coordinates',
    JSON.stringify(releaseRace.requestCalls),
    JSON.stringify([[1, 32, 2, 30, 4]]),
  );
  const finalizedActive = JSON.stringify(releaseRace.tile.selection.active);
  releaseRace.tile.onMessage(scrollEnvelope(25));
  check('late scroll reply after release may paint requested viewport', releasePaints, 1);
  check('late scroll reply after release cannot change finalized selection', JSON.stringify(releaseRace.tile.selection.active), finalizedActive);
  check('late scroll reply after release sends no second selection request', releaseRace.requestCalls.length, 1);

  const finalUp = makeTile();
  finalUp.tile.viewStartRow = 30;
  finalUp.tile.canvas.dispatchEvent('pointerdown', pointer(28, 2, 1));
  finalUp.tile.canvas.dispatchEvent('pointerup', pointer(28, 6, 4));
  check(
    'pointerup samples final coordinates even without a last pointermove',
    JSON.stringify(finalUp.requestCalls),
    JSON.stringify([[1, 31, 2, 34, 6]]),
  );
  check('pointerup-only endpoint movement repaints retained overlay once', finalUp.reflows(), 2);

  const finalPendingUp = makeTile();
  finalPendingUp.tile.viewStartRow = 30;
  finalPendingUp.tile.paintScroll = () => {};
  finalPendingUp.tile.canvas.dispatchEvent('pointerdown', pointer(29, 2, 2));
  finalPendingUp.tile.canvas.dispatchEvent('pointermove', {
    ...pointer(29, 4, 0), clientY: 10,
  });
  h.intervals[finalPendingUp.tile.selectionScrollTimer - 1].fn();
  finalPendingUp.tile.canvas.dispatchEvent('pointerup', pointer(29, 7, 3));
  check(
    'pointerup before page reply samples final point on displayed viewport',
    JSON.stringify(finalPendingUp.requestCalls),
    JSON.stringify([[1, 32, 2, 33, 7]]),
  );
  check('changed pointerup after move adds one final overlay repaint', finalPendingUp.reflows(), 3);
  const finalPendingPoint = JSON.stringify(finalPendingUp.tile.selection.active);
  finalPendingUp.tile.onMessage(scrollEnvelope(25));
  check('late painted page cannot remap final pointerup point', JSON.stringify(finalPendingUp.tile.selection.active), finalPendingPoint);

  const droppedSelection = makeTile();
  let droppedSelectionSends = 0;
  droppedSelection.tile.sendFrame = () => { droppedSelectionSends++; return false; };
  droppedSelection.tile.canvas.dispatchEvent('pointerdown', pointer(31, 2, 1));
  droppedSelection.tile.canvas.dispatchEvent('pointerup', pointer(31, 5, 3));
  check('dropped selection request attempts one transport send', droppedSelectionSends, 1);
  check('dropped selection request clears retained pending geometry', droppedSelection.tile.selection, null);
  check('dropped selection request starts no response timeout', droppedSelection.tile.selectionRequestTimer, null);

  const selectionTimeout = makeTile();
  selectionTimeout.tile.canvas.dispatchEvent('pointerdown', pointer(32, 1, 1));
  selectionTimeout.tile.canvas.dispatchEvent('pointerup', pointer(32, 4, 2));
  const selectionTimeoutHandle = selectionTimeout.tile.selectionRequestTimer;
  const selectionTimeoutTask = selectionTimeoutHandle === null
    ? undefined : h.timers[selectionTimeoutHandle - 1];
  check('selection response timeout is bounded to 5000ms', selectionTimeoutTask?.ms, 5000);
  selectionTimeoutTask?.fn();
  check('selection response timeout clears timer identity', selectionTimeout.tile.selectionRequestTimer, null);
  check('selection response timeout retains no authoritative text', selectionTimeout.tile.selection?.text, null);
  check('selection response timeout reports transient unavailability', `${selectionTimeout.tile.copyButton.className}|${selectionTimeout.tile.copyButton.textContent}`, 'copy-request on error|Selection unavailable');
  check('selection response timeout revokes active request capability', selectionTimeout.tile.activeSelectionRequest, null);

  const lateAfterTimeout = makeTile();
  lateAfterTimeout.tile.pendingClipboard = 'OSC fallback after timeout';
  lateAfterTimeout.tile.clipboardVersion = 1;
  lateAfterTimeout.tile.copyUiOwner = 'clipboard';
  lateAfterTimeout.tile.canvas.dispatchEvent('pointerdown', pointer(41, 1, 1));
  lateAfterTimeout.tile.canvas.dispatchEvent('pointerup', pointer(41, 4, 2));
  const lateLeaseHandle = lateAfterTimeout.tile.selectionRequestTimer;
  const lateLeaseTimer = lateLeaseHandle === null
    ? undefined : h.timers[lateLeaseHandle - 1];
  lateLeaseTimer?.fn();
  const lateFeedbackHandle = lateAfterTimeout.tile.selectionFeedbackTimer;
  const lateFeedbackTimer = lateFeedbackHandle === null
    ? undefined : h.timers[lateFeedbackHandle - 1];
  lateFeedbackTimer?.fn();
  check('expired selection feedback restores OSC fallback before late reply', lateAfterTimeout.tile.copyUiOwner, 'clipboard');
  lateAfterTimeout.setResult(1, 0, Buffer.from('must stay expired'));
  lateAfterTimeout.tile.onSelectionReply();
  check('late same-id success cannot restore expired authoritative text', lateAfterTimeout.tile.selection?.text, null);
  check('late same-id success cannot reclaim shared copy UI', lateAfterTimeout.tile.copyUiOwner, 'clipboard');
  lateAfterTimeout.setResult(1, 2, []);
  lateAfterTimeout.tile.onSelectionReply();
  check('late same-id failure cannot reclaim shared copy UI', lateAfterTimeout.tile.copyUiOwner, 'clipboard');
  check('late same-id failure cannot start new unavailable feedback', lateAfterTimeout.tile.selectionFeedbackTimer, null);

  const replyBeforeTimeout = makeTile();
  replyBeforeTimeout.tile.canvas.dispatchEvent('pointerdown', pointer(33, 1, 1));
  replyBeforeTimeout.tile.canvas.dispatchEvent('pointerup', pointer(33, 4, 2));
  const replyTimeoutHandle = replyBeforeTimeout.tile.selectionRequestTimer;
  const replyTimeoutTask = replyTimeoutHandle === null
    ? undefined : h.timers[replyTimeoutHandle - 1];
  replyBeforeTimeout.setResult(1, 0, Buffer.from('reply before timeout'));
  replyBeforeTimeout.tile.onSelectionReply();
  check('matching selection reply clears response timeout', replyTimeoutTask?.cleared, true);
  check('matching selection reply clears timeout identity', replyBeforeTimeout.tile.selectionRequestTimer, null);
  check('matching selection reply retains authoritative text', replyBeforeTimeout.tile.selection?.text, 'reply before timeout');
  check('matching selection reply consumes active request capability', replyBeforeTimeout.tile.activeSelectionRequest, null);

  const synchronousReply = makeTile();
  synchronousReply.tile.sendFrame = () => {
    synchronousReply.setResult(1, 0, Buffer.from('synchronous host reply'));
    synchronousReply.tile.onSelectionReply();
    return true;
  };
  synchronousReply.tile.canvas.dispatchEvent('pointerdown', pointer(40, 1, 1));
  synchronousReply.tile.canvas.dispatchEvent('pointerup', pointer(40, 4, 2));
  check('synchronous host reply retains authoritative selection', synchronousReply.tile.selection?.text, 'synchronous host reply');
  check('synchronous host reply cannot acquire a stale response timeout', synchronousReply.tile.selectionRequestTimer, null);
  check('synchronous host reply consumes pre-send active capability', synchronousReply.tile.activeSelectionRequest, null);

  const failureBeforeTimeout = makeTile();
  failureBeforeTimeout.tile.canvas.dispatchEvent('pointerdown', pointer(34, 1, 1));
  failureBeforeTimeout.tile.canvas.dispatchEvent('pointerup', pointer(34, 4, 2));
  const failureTimeoutHandle = failureBeforeTimeout.tile.selectionRequestTimer;
  const failureTimeoutTask = failureTimeoutHandle === null
    ? undefined : h.timers[failureTimeoutHandle - 1];
  failureBeforeTimeout.setResult(1, 2, []);
  failureBeforeTimeout.tile.onSelectionReply();
  check('matching failed selection reply clears response timeout', failureTimeoutTask?.cleared, true);
  check('matching failed selection reply clears timeout identity', failureBeforeTimeout.tile.selectionRequestTimer, null);

  const staleSelectionTimeout = makeTile();
  staleSelectionTimeout.tile.canvas.dispatchEvent('pointerdown', pointer(35, 1, 1));
  staleSelectionTimeout.tile.canvas.dispatchEvent('pointerup', pointer(35, 4, 2));
  const staleSelectionHandle = staleSelectionTimeout.tile.selectionRequestTimer;
  const staleSelectionTask = staleSelectionHandle === null
    ? undefined : h.timers[staleSelectionHandle - 1];
  staleSelectionTimeout.tile.canvas.dispatchEvent('pointerdown', pointer(36, 2, 2));
  staleSelectionTimeout.tile.canvas.dispatchEvent('pointerup', pointer(36, 5, 3));
  const newerSelection = staleSelectionTimeout.tile.selection;
  const newerSelectionTimer = staleSelectionTimeout.tile.selectionRequestTimer;
  staleSelectionTask?.fn();
  check('stale selection timeout cannot clear newer retained geometry', staleSelectionTimeout.tile.selection, newerSelection);
  check('stale selection timeout cannot clear newer response timer', staleSelectionTimeout.tile.selectionRequestTimer, newerSelectionTimer);
  check('stale selection timeout cannot show unavailable for newer request', staleSelectionTimeout.tile.copyUiOwner, null);

  const staleSelectionReply = makeTile();
  staleSelectionReply.tile.canvas.dispatchEvent('pointerdown', pointer(42, 1, 1));
  staleSelectionReply.tile.canvas.dispatchEvent('pointerup', pointer(42, 4, 2));
  staleSelectionReply.tile.canvas.dispatchEvent('pointerdown', pointer(43, 2, 2));
  staleSelectionReply.tile.canvas.dispatchEvent('pointerup', pointer(43, 5, 3));
  const activeNewerRequest = staleSelectionReply.tile.activeSelectionRequest;
  check('newer selection establishes active pre-send capability', activeNewerRequest !== null, true);
  check('newer selection active capability carries request id', activeNewerRequest?.id, 2);
  staleSelectionReply.setResult(1, 0, Buffer.from('stale older reply'));
  staleSelectionReply.tile.onSelectionReply();
  check('older reply cannot consume newer active request capability', staleSelectionReply.tile.activeSelectionRequest, activeNewerRequest);
  check('older reply cannot bind text to newer selection', staleSelectionReply.tile.selection?.text, null);
  staleSelectionReply.setResult(2, 0, Buffer.from('matching newer reply'));
  staleSelectionReply.tile.onSelectionReply();
  check('matching newer reply binds after stale older reply', staleSelectionReply.tile.selection?.text, 'matching newer reply');
  check('matching newer reply consumes active capability', staleSelectionReply.tile.activeSelectionRequest, null);

  const selectionLifecycle = makeTile();
  selectionLifecycle.tile.canvas.dispatchEvent('pointerdown', pointer(37, 1, 1));
  selectionLifecycle.tile.canvas.dispatchEvent('pointerup', pointer(37, 4, 2));
  const lifecycleSelectionHandle = selectionLifecycle.tile.selectionRequestTimer;
  const lifecycleSelectionTask = lifecycleSelectionHandle === null
    ? undefined : h.timers[lifecycleSelectionHandle - 1];
  selectionLifecycle.tile.sendAttach(false);
  check('selection lifecycle reset clears pending response timer', lifecycleSelectionTask?.cleared, true);
  check('selection lifecycle reset clears response timer identity', selectionLifecycle.tile.selectionRequestTimer, null);
  check('selection lifecycle reset clears pending geometry', selectionLifecycle.tile.selection, null);

  const selectionUnzoom = makeTile();
  selectionUnzoom.tile.canvas.dispatchEvent('pointerdown', pointer(38, 1, 1));
  selectionUnzoom.tile.canvas.dispatchEvent('pointerup', pointer(38, 4, 2));
  const unzoomSelectionHandle = selectionUnzoom.tile.selectionRequestTimer;
  const unzoomSelectionTask = unzoomSelectionHandle === null
    ? undefined : h.timers[unzoomSelectionHandle - 1];
  h.setZoomedTile(selectionUnzoom.tile);
  h.unzoom();
  check('unzoom clears pending selection response timeout', unzoomSelectionTask?.cleared, true);
  check('unzoom clears pending selection response identity', selectionUnzoom.tile.selectionRequestTimer, null);
  check('unzoom clears pending selection geometry', selectionUnzoom.tile.selection, null);

  const selectionReset = makeTile();
  selectionReset.tile.canvas.dispatchEvent('pointerdown', pointer(39, 1, 1));
  selectionReset.tile.canvas.dispatchEvent('pointerup', pointer(39, 4, 2));
  const resetSelectionHandle = selectionReset.tile.selectionRequestTimer;
  const resetSelectionTask = resetSelectionHandle === null
    ? undefined : h.timers[resetSelectionHandle - 1];
  selectionReset.tile.resetCore('pending selection timeout');
  check('core reset clears pending selection response timeout', resetSelectionTask?.cleared, true);
  check('core reset clears pending selection response identity', selectionReset.tile.selectionRequestTimer, null);
  check('core reset clears pending selection geometry', selectionReset.tile.selection, null);

  const painted = makeTile();
  const order = [];
  painted.tile.sizeCanvas = () => {};
  painted.tile.paintRow = (row) => { order.push(`row${row}`); };
  painted.tile.paintCursor = () => { order.push('cursor'); };
  painted.tile.paintSelection = () => { order.push('selection'); };
  painted.tile.core.mux_read_viewport = () => 2;
  painted.tile.paintLive();
  check('live paint follows current history start', painted.tile.viewStartRow, 30);
  check('live selection overlay paints after cells and cursor', order.join('|'), 'row0|row1|cursor|selection');
  order.length = 0;
  painted.tile.viewStartRow = 12;
  painted.tile.paintScroll();
  check('history paint retains echoed start row', painted.tile.viewStartRow, 12);
  check('history selection overlay paints after cells', order.join('|'), 'row0|row1|row2|row3|row4|selection');

  const partial = makeTile();
  partial.tile.sizeCanvas = () => {};
  partial.tile.selection = {
    anchor: { row: 31, col: 2 }, active: { row: 33, col: 4 }, requestId: 1, text: null,
  };
  let dirtyRows = [0];
  partial.tile.core.mux_read_viewport = () => dirtyRows.length;
  partial.tile.core.mux_dirty_row = (i) => dirtyRows[i];
  const partialOps = [];
  partial.tile.paintRow = (row) => { partialOps.push(`row${row}`); };
  partial.tile.paintCursor = () => { partialOps.push('cursor'); };
  partial.tile.paintSelection = () => { partialOps.push('overlay'); };
  partial.tile.paintLive();
  check(
    'partial live paint restores every selected row before translucent overlay',
    partialOps.join('|'),
    'row0|row1|row2|row3|cursor|overlay',
  );
  dirtyRows = [2, 4];
  partialOps.length = 0;
  partial.tile.paintLive();
  check(
    'successive partial live paint restores selected rows without duplicates',
    partialOps.join('|'),
    'row2|row4|row1|row3|cursor|overlay',
  );

  const overlay = makeTile();
  const fills = [];
  overlay.tile.ctx = {
    fillStyle: '',
    fillRect(x, y, width, height) { fills.push([x, y, width, height, this.fillStyle]); },
  };
  overlay.tile.viewStartRow = 100;
  overlay.tile.selection = {
    anchor: { row: 103, col: 4 }, active: { row: 101, col: 2 }, requestId: 1, text: null,
  };
  overlay.tile.paintSelection();
  check(
    'retained overlay paints reverse multi-row geometry in history',
    JSON.stringify(fills),
    JSON.stringify([
      [16, 14, 64, 14, '#6ab0e055'],
      [0, 28, 80, 14, '#6ab0e055'],
      [0, 42, 40, 14, '#6ab0e055'],
    ]),
  );

  const replies = makeTile();
  const highId = 0xfedcba98;
  replies.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: highId, text: null,
  };
  const authoritative = Buffer.from('daemon ☃', 'utf8');
  authorizeSelectionReply(replies);
  replies.setResult(highId, 0, authoritative);
  replies.tile.onSelectionReply();
  new Uint8Array(replies.memory.buffer).fill(0, 96, 96 + authoritative.length);
  check('matching high-bit reply binds with unsigned ID comparison', replies.tile.selection.text, 'daemon ☃');

  replies.tile.selection.text = 'keep';
  replies.setResult(123, 0, Buffer.from('stale'));
  replies.tile.onSelectionReply();
  check('stale semantic reply cannot replace retained text', replies.tile.selection.text, 'keep');

  authorizeSelectionReply(replies);
  replies.setResult(highId, 2, []);
  replies.tile.onSelectionReply();
  check('failed matching reply clears authoritative text', replies.tile.selection.text, null);
  check('failed matching reply reports selection unavailable', `${replies.tile.copyButton.className}|${replies.tile.copyButton.textContent}`, 'copy-request on error|Selection unavailable');

  replies.tile.selection.text = 'previous';
  authorizeSelectionReply(replies);
  replies.setResult(highId, 0, [0xff]);
  replies.tile.onSelectionReply();
  check('browser rejects invalid UTF-8 selection text defensively', replies.tile.selection.text, null);
  const invalidUtf8FeedbackHandle = replies.tile.selectionFeedbackTimer;
  const invalidUtf8Feedback = invalidUtf8FeedbackHandle === null
    ? undefined : h.timers[invalidUtf8FeedbackHandle - 1];
  check('invalid UTF-8 selection status is bounded to 1200ms', invalidUtf8Feedback?.ms, 1200);
  invalidUtf8Feedback?.fn();
  check('invalid UTF-8 selection status releases shared copy UI', `${replies.tile.copyUiOwner}|${replies.tile.copyButton.className}|${replies.tile.copyButton.textContent}`, 'null|copy-request|Copy');

  const routed = makeTile();
  routed.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 77, text: null,
  };
  authorizeSelectionReply(routed);
  routed.setResult(77, 0, Buffer.from('routed'));
  const replyPayload = Uint8Array.from([77, 0, 0, 0, 0, 30, 0, 0, 0, ...Buffer.from('routed')]);
  const replyEnvelope = new Uint8Array(6 + replyPayload.length);
  replyEnvelope[0] = 0;
  replyEnvelope[1] = 0x90;
  new DataView(replyEnvelope.buffer).setUint32(2, replyPayload.length, true);
  replyEnvelope.set(replyPayload, 6);
  routed.tile.onMessage(replyEnvelope);
  check('selection-reply frame routes through semantic client core', routed.tile.selection.text, 'routed');

  const evicted = makeTile();
  evicted.tile.selection = {
    anchor: { row: 30, col: 0 }, active: { row: 31, col: 2 }, requestId: 82, text: null,
  };
  const evictedRequest = authorizeSelectionReply(evicted);
  check('selection request samples the history base its rows are counted from', evictedRequest.historyBase, 30);
  evicted.setResult(82, 0, Buffer.from('rows that moved under the request'), 25);
  evicted.tile.onSelectionReply();
  check('reply below the sampled history base retains no text', evicted.tile.selection.text, null);
  check('reply below the sampled history base reports unavailable', `${evicted.tile.copyButton.className}|${evicted.tile.copyButton.textContent}`, 'copy-request on error|Selection unavailable');
  check('reply below the sampled history base consumes the request', evicted.tile.activeSelectionRequest, null);

  // The other half of the same rule, and the one a stricter comparison
  // would break: output APPENDS rows below the retained ones, so a higher
  // reading names the same lines the drag did.
  const grown = makeTile();
  grown.tile.selection = {
    anchor: { row: 30, col: 0 }, active: { row: 31, col: 2 }, requestId: 83, text: null,
  };
  authorizeSelectionReply(grown);
  grown.setResult(83, 0, Buffer.from('same rows, more below them'), 900);
  grown.tile.onSelectionReply();
  check('a risen history base leaves the reply usable', grown.tile.selection.text, 'same rows, more below them');

  const blockedCopy = makeTile();
  blockedCopy.tile.pendingClipboard = 'unrelated OSC 52 text';
  blockedCopy.tile.clipboardVersion = 1;
  blockedCopy.tile.copyUiOwner = 'clipboard';
  blockedCopy.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 71, text: null,
  };
  authorizeSelectionReply(blockedCopy);
  blockedCopy.setResult(71, 3, []);
  blockedCopy.tile.onSelectionReply();
  const unavailableFeedbackHandle = blockedCopy.tile.selectionFeedbackTimer;
  const unavailableFeedback = unavailableFeedbackHandle === null
    ? undefined : h.timers[unavailableFeedbackHandle - 1];
  const blockedWrites = [];
  h.navigator.clipboard = { writeText: (text) => { blockedWrites.push(text); return Promise.resolve(); } };
  blockedCopy.tile.copyButton.dispatchEvent('click', { stopPropagation() {} });
  await flushPromises();
  check('selection failure owns the shared copy control', blockedCopy.tile.copyUiOwner, 'selection');
  check('selection failure button cannot copy unrelated OSC 52 text', blockedWrites.length, 0);
  check('selection failure leaves unrelated OSC 52 text pending', blockedCopy.tile.pendingClipboard, 'unrelated OSC 52 text');
  check('selection unavailable feedback is bounded to 1200ms', unavailableFeedback?.ms, 1200);
  unavailableFeedback?.fn();
  check('expired selection unavailable restores pending OSC52 owner', blockedCopy.tile.copyUiOwner, 'clipboard');
  check('expired selection unavailable restores pending OSC52 action', `${blockedCopy.tile.copyButton.className}|${blockedCopy.tile.copyButton.textContent}`, 'copy-request on|Copy');

  const ownerChangedFeedback = makeTile();
  ownerChangedFeedback.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 78, text: null,
  };
  authorizeSelectionReply(ownerChangedFeedback);
  ownerChangedFeedback.setResult(78, 3, []);
  ownerChangedFeedback.tile.onSelectionReply();
  const supersededFeedbackHandle = ownerChangedFeedback.tile.selectionFeedbackTimer;
  const supersededFeedback = supersededFeedbackHandle === null
    ? undefined : h.timers[supersededFeedbackHandle - 1];
  const ownerOsc = Buffer.from('new OSC owner', 'utf8').toString('base64');
  new Uint8Array(ownerChangedFeedback.memory.buffer).set(Buffer.from(ownerOsc, 'ascii'), 96);
  ownerChangedFeedback.tile.core.mux_clipboard_ptr = () => 96;
  ownerChangedFeedback.tile.core.mux_clipboard_len = () => ownerOsc.length;
  h.navigator.clipboard = undefined;
  await ownerChangedFeedback.tile.onClipboardEffect();
  check('new OSC52 owner clears transient selection feedback timer', supersededFeedback?.cleared, true);
  supersededFeedback?.fn();
  check('stale unavailable feedback cannot overwrite new OSC52 owner', ownerChangedFeedback.tile.copyUiOwner, 'clipboard');
  check('stale unavailable feedback cannot hide new OSC52 retry', `${ownerChangedFeedback.tile.copyButton.className}|${ownerChangedFeedback.tile.copyButton.textContent}`, 'copy-request on|Copy');

  const copySupersedesFeedback = makeTile();
  copySupersedesFeedback.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 79, text: null,
  };
  authorizeSelectionReply(copySupersedesFeedback);
  copySupersedesFeedback.setResult(79, 3, []);
  copySupersedesFeedback.tile.onSelectionReply();
  const preCopyFeedbackHandle = copySupersedesFeedback.tile.selectionFeedbackTimer;
  const preCopyFeedback = preCopyFeedbackHandle === null
    ? undefined : h.timers[preCopyFeedbackHandle - 1];
  copySupersedesFeedback.tile.selection.text = 'late authoritative text';
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await copySupersedesFeedback.tile.copySelection();
  check('authoritative copy clears prior unavailable feedback timer', preCopyFeedback?.cleared, true);
  preCopyFeedback?.fn();
  check('stale unavailable timer cannot overwrite copy success', `${copySupersedesFeedback.tile.copyButton.className}|${copySupersedesFeedback.tile.copyButton.textContent}`, 'copy-request on|Copied');

  const unzoomFeedback = makeTile();
  unzoomFeedback.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 81, text: null,
  };
  authorizeSelectionReply(unzoomFeedback);
  unzoomFeedback.setResult(81, 3, []);
  unzoomFeedback.tile.onSelectionReply();
  const unzoomFeedbackHandle = unzoomFeedback.tile.selectionFeedbackTimer;
  const unzoomFeedbackTask = unzoomFeedbackHandle === null
    ? undefined : h.timers[unzoomFeedbackHandle - 1];
  h.setZoomedTile(unzoomFeedback.tile);
  h.unzoom();
  check('unzoom clears transient unavailable feedback timer', unzoomFeedbackTask?.cleared, true);
  unzoomFeedbackTask?.fn();
  check('stale unavailable timer remains invisible after unzoom', `${unzoomFeedback.tile.copyUiOwner}|${unzoomFeedback.tile.copyButton.className}|${unzoomFeedback.tile.copyButton.textContent}`, 'null|copy-request|Copy');

  // The selection lane's two timer-driven hides, same hazard.
  const unavailableFocus = makeTile();
  unavailableFocus.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 84, text: null,
  };
  authorizeSelectionReply(unavailableFocus);
  unavailableFocus.setResult(84, 3, []);
  unavailableFocus.tile.onSelectionReply();
  const unavailableFocusTimer = h.timers[unavailableFocus.tile.selectionFeedbackTimer - 1];
  unavailableFocus.tile.copyButton.focus();
  unavailableFocusTimer?.fn();
  check('expiring selection feedback under focus returns focus to the IME', h.document.activeElement, h.elements.ime);

  // The other side of the guard: retiring selection feedback onto a pending
  // OSC 52 leaves the control VISIBLE and actionable, so focus must stay
  // where the user put it.
  const visibleAfterFeedback = makeTile();
  visibleAfterFeedback.tile.pendingClipboard = 'OSC52 still waiting';
  visibleAfterFeedback.tile.clipboardVersion = 1;
  visibleAfterFeedback.tile.copyUiOwner = 'clipboard';
  visibleAfterFeedback.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 86, text: null,
  };
  authorizeSelectionReply(visibleAfterFeedback);
  visibleAfterFeedback.setResult(86, 3, []);
  visibleAfterFeedback.tile.onSelectionReply();
  const visibleFeedbackTimer = h.timers[visibleAfterFeedback.tile.selectionFeedbackTimer - 1];
  visibleAfterFeedback.tile.copyButton.focus();
  visibleFeedbackTimer?.fn();
  check('feedback expiring onto a still-visible retry keeps its focus', h.document.activeElement, visibleAfterFeedback.tile.copyButton);
  check('feedback expiring onto a still-visible retry stays actionable', `${visibleAfterFeedback.tile.copyButton.className}|${visibleAfterFeedback.tile.copyButton.textContent}`, 'copy-request on|Copy');

  const copiedFocus = makeTile();
  copiedFocus.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 85, text: 'copied then hidden',
  };
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await copiedFocus.tile.copySelection();
  const copiedFocusTimer = h.timers.at(-1);
  copiedFocus.tile.copyButton.focus();
  copiedFocusTimer.fn();
  check('hiding the selection copy control under focus returns focus to the IME', h.document.activeElement, h.elements.ime);

  const inFlightUi = makeTile();
  inFlightUi.tile.pendingClipboard = 'in flight';
  inFlightUi.tile.clipboardVersion = 1;
  inFlightUi.tile.copyUiOwner = 'clipboard';
  const inFlightWrite = deferred();
  h.navigator.clipboard = { writeText: () => inFlightWrite.promise };
  const inFlightRun = inFlightUi.tile.tryClipboardWrite(1, true);
  inFlightUi.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 72, text: null,
  };
  authorizeSelectionReply(inFlightUi);
  inFlightUi.setResult(72, 2, []);
  inFlightUi.tile.onSelectionReply();
  inFlightWrite.resolve();
  await inFlightRun;
  check(
    'settled in-flight OSC 52 write cannot overwrite selection failure status',
    `${inFlightUi.tile.copyButton.className}|${inFlightUi.tile.copyButton.textContent}`,
    'copy-request on error|Selection unavailable',
  );

  const timerUi = makeTile();
  timerUi.tile.pendingClipboard = 'copied before selection failure';
  timerUi.tile.clipboardVersion = 1;
  timerUi.tile.copyUiOwner = 'clipboard';
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await timerUi.tile.tryClipboardWrite(1, true);
  const oldClipboardTimer = h.timers.at(-1);
  timerUi.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 73, text: null,
  };
  authorizeSelectionReply(timerUi);
  timerUi.setResult(73, 1, []);
  timerUi.tile.onSelectionReply();
  oldClipboardTimer.fn();
  check(
    'old OSC 52 feedback timer cannot overwrite selection failure status',
    `${timerUi.tile.copyButton.className}|${timerUi.tile.copyButton.textContent}`,
    'copy-request on error|Selection unavailable',
  );

  const clearedUi = makeTile();
  clearedUi.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 74, text: null,
  };
  authorizeSelectionReply(clearedUi);
  clearedUi.setResult(74, 3, []);
  clearedUi.tile.onSelectionReply();
  clearedUi.tile.clearSelection(false);
  check('clearing failed selection removes stale error UI', `${clearedUi.tile.copyButton.className}|${clearedUi.tile.copyButton.textContent}`, 'copy-request|Copy');

  const successfulUi = makeTile();
  successfulUi.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 75, text: null,
  };
  authorizeSelectionReply(successfulUi);
  successfulUi.setResult(75, 3, []);
  successfulUi.tile.onSelectionReply();
  authorizeSelectionReply(successfulUi);
  successfulUi.setResult(75, 0, Buffer.from('available'));
  successfulUi.tile.onSelectionReply();
  check('successful selection removes stale unavailable UI', `${successfulUi.tile.copyButton.className}|${successfulUi.tile.copyButton.textContent}`, 'copy-request|Copy');

  const keyEvent = (overrides = {}) => {
    let prevented = false;
    return {
      key: '', target: h.elements.ime, isComposing: false,
      shiftKey: false, altKey: false, ctrlKey: false, metaKey: false,
      preventDefault() { prevented = true; },
      wasPrevented: () => prevented,
      ...overrides,
    };
  };
  const authoritativeSelection = (text) => ({
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 80, text,
  });

  for (const [name, modifiers] of [
    ['Ctrl+Shift+C', { ctrlKey: true, shiftKey: true }],
    ['Ctrl+C', { ctrlKey: true }],
    ['Meta+C', { metaKey: true }],
  ]) {
    const chord = makeTile();
    chord.tile.selection = authoritativeSelection(`exact ${name}`);
    h.setZoomedTile(chord.tile);
    const writes = [];
    h.navigator.clipboard = { writeText(text) { writes.push(text); return Promise.resolve(); } };
    const ev = keyEvent({ key: 'c', ...modifiers });
    h.document.dispatchEvent('keydown', ev);
    check(`${name} with authoritative selection prevents browser default`, ev.wasPrevented(), true);
    check(`${name} invokes clipboard in the keydown user gesture`, writes.join('|'), `exact ${name}`);
    check(`${name} with authoritative selection sends no PTY frame`, chord.sent.length, 0);
    await flushPromises();
    check(`${name} retains the selection after copy`, chord.tile.selection?.text, `exact ${name}`);
  }

  const emptyCopy = makeTile();
  emptyCopy.tile.selection = authoritativeSelection('');
  h.setZoomedTile(emptyCopy.tile);
  const emptyWrites = [];
  h.navigator.clipboard = { writeText(text) { emptyWrites.push(text); return Promise.resolve(); } };
  const emptyEvent = keyEvent({ key: 'c', ctrlKey: true });
  h.document.dispatchEvent('keydown', emptyEvent);
  check('empty authoritative selection is still copyable', emptyEvent.wasPrevented(), true);
  check('empty authoritative selection writes exact empty text', JSON.stringify(emptyWrites), '[""]');
  await flushPromises();
  check('empty selection copy retains authoritative empty text', emptyCopy.tile.selection?.text, '');

  const nativeChords = makeTile();
  h.setZoomedTile(nativeChords.tile);
  for (const [name, modifiers] of [
    ['Ctrl+Shift+C', { ctrlKey: true, shiftKey: true }],
    ['Meta+C', { metaKey: true }],
  ]) {
    const ev = keyEvent({ key: 'c', ...modifiers });
    h.document.dispatchEvent('keydown', ev);
    check(`${name} without selection remains browser-native`, ev.wasPrevented(), false);
  }
  check('browser-native copy chords without selection send no PTY frame', nativeChords.sent.length, 0);

  const terminalCopy = makeTile();
  const encodedKeys = [];
  terminalCopy.tile.core.mux_key_encode = (...args) => {
    encodedKeys.push(args);
    new Uint8Array(terminalCopy.memory.buffer)[256] = 3;
    return 1;
  };
  terminalCopy.tile.core.mux_output_len = () => 1;
  h.setZoomedTile(terminalCopy.tile);
  const ctrlC = keyEvent({ key: 'c', ctrlKey: true });
  h.document.dispatchEvent('keydown', ctrlC);
  check('Ctrl+C without selection prevents browser default for terminal input', ctrlC.wasPrevented(), true);
  check('Ctrl+C without selection reaches terminal key encoding', JSON.stringify(encodedKeys), '[[0,99,4]]');
  check('Ctrl+C without selection sends one PTY frame', terminalCopy.sent[0]?.type, 0x02);
  check('Ctrl+C fake encoder emits ETX byte', terminalCopy.sent[0]?.payload[0], 3);

  // The retained selection exists but its reply has not decoded (or came
  // back non-ok), so copySelection would return at its own text === null
  // guard. Treating "a selection object exists" as "there is something to
  // copy" swallows the interrupt for up to the whole response timeout.
  const undecodedCopy = makeTile();
  undecodedCopy.tile.selection = authoritativeSelection(null);
  undecodedCopy.tile.core.mux_key_encode = () => 1;
  undecodedCopy.tile.core.mux_output_len = () => 1;
  h.setZoomedTile(undecodedCopy.tile);
  const undecodedWrites = [];
  h.navigator.clipboard = { writeText: (text) => { undecodedWrites.push(text); return Promise.resolve(); } };
  const undecodedCtrlC = keyEvent({ key: 'c', ctrlKey: true });
  h.document.dispatchEvent('keydown', undecodedCtrlC);
  await flushPromises();
  check('Ctrl+C with an undecoded selection still reaches the shell', undecodedCopy.sent[0]?.type, 0x02);
  check('Ctrl+C with an undecoded selection sends exactly one frame', undecodedCopy.sent.length, 1);
  check('Ctrl+C with an undecoded selection writes no clipboard', undecodedWrites.length, 0);

  const altCopy = makeTile();
  const altKeys = [];
  altCopy.tile.selection = authoritativeSelection('must not copy');
  altCopy.tile.core.mux_key_encode = (...args) => { altKeys.push(args); return 1; };
  altCopy.tile.core.mux_output_len = () => 1;
  h.setZoomedTile(altCopy.tile);
  let altWrites = 0;
  h.navigator.clipboard = { writeText() { altWrites++; return Promise.resolve(); } };
  const ctrlAltC = keyEvent({ key: 'c', ctrlKey: true, altKey: true });
  h.document.dispatchEvent('keydown', ctrlAltC);
  check('Ctrl+Alt+C is terminal input, not selection copy', altWrites, 0);
  check('Ctrl+Alt+C includes Alt and Ctrl in terminal modifiers', JSON.stringify(altKeys), '[[0,99,6]]');
  check('Ctrl+Alt+C clears retained selection through sendKey', altCopy.tile.selection, null);

  const nativePaste = makeTile();
  nativePaste.tile.selection = authoritativeSelection('keep during browser paste chord');
  h.setZoomedTile(nativePaste.tile);
  const ctrlShiftV = keyEvent({ key: 'v', ctrlKey: true, shiftKey: true });
  h.document.dispatchEvent('keydown', ctrlShiftV);
  check('Ctrl+Shift+V remains browser-native', ctrlShiftV.wasPrevented(), false);
  check('Ctrl+Shift+V sends no PTY frame', nativePaste.sent.length, 0);
  check('Ctrl+Shift+V keydown alone does not clear selection', nativePaste.tile.selection?.text, 'keep during browser paste chord');

  const composingCopy = makeTile();
  composingCopy.tile.selection = authoritativeSelection('IME owns this key');
  h.setZoomedTile(composingCopy.tile);
  let composingWrites = 0;
  h.navigator.clipboard = { writeText() { composingWrites++; return Promise.resolve(); } };
  const composingEvent = keyEvent({ key: 'c', ctrlKey: true, isComposing: true });
  h.document.dispatchEvent('keydown', composingEvent);
  check('composition handling precedes selection copy chords', composingWrites, 0);
  check('composing copy chord remains unprevented', composingEvent.wasPrevented(), false);

  const selectionRetryTab = makeTile();
  selectionRetryTab.tile.selection = authoritativeSelection('retry by keyboard');
  selectionRetryTab.tile.renderCopyUi('selection', 'copy-request on error', 'Copy failed');
  h.setZoomedTile(selectionRetryTab.tile);
  h.elements.ime.focus();
  const selectionTab = keyEvent({ key: 'Tab' });
  h.document.dispatchEvent('keydown', selectionTab);
  check('plain Tab reaches actionable selection-copy failure', selectionTab.wasPrevented(), true);
  check('selection-copy retry receives keyboard focus', h.document.activeElement, selectionRetryTab.tile.copyButton);
  check('selection-copy retry Tab sends no PTY bytes', selectionRetryTab.sent.length, 0);

  const unavailableTab = makeTile();
  unavailableTab.tile.selection = authoritativeSelection(null);
  unavailableTab.tile.pendingClipboard = 'unrelated hidden OSC52';
  unavailableTab.tile.renderCopyUi('selection', 'copy-request on error', 'Selection unavailable');
  unavailableTab.tile.core.mux_key_encode = () => 1;
  unavailableTab.tile.core.mux_output_len = () => 1;
  h.setZoomedTile(unavailableTab.tile);
  h.elements.ime.focus();
  const unavailableSelectionTab = keyEvent({ key: 'Tab' });
  h.document.dispatchEvent('keydown', unavailableSelectionTab);
  check('unavailable selection does not advertise an actionable retry', h.document.activeElement, h.elements.ime);
  check('unavailable selection Tab keeps terminal key handling', unavailableSelectionTab.wasPrevented(), true);
  check('unavailable selection Tab reaches PTY once', unavailableTab.sent.length, 1);

  const scrollExitKey = makeTile();
  scrollExitKey.tile.scrollPages = 1;
  scrollExitKey.tile.viewStartRow = 25;
  scrollExitKey.tile.selection = authoritativeSelection('history selection');
  h.setZoomedTile(scrollExitKey.tile);
  const swallowedHistoryKey = keyEvent({ key: 'x' });
  h.document.dispatchEvent('keydown', swallowedHistoryKey);
  check('key swallowed to leave history still clears retained selection', scrollExitKey.tile.selection, null);
  check('history-exit key remains swallowed without PTY bytes', scrollExitKey.sent.length, 0);

  const inputClears = makeTile();
  inputClears.tile.core.mux_key_encode = () => 1;
  inputClears.tile.core.mux_text_encode = (len) => len;
  inputClears.tile.core.mux_output_len = () => 1;
  inputClears.tile.selection = authoritativeSelection('typed over');
  inputClears.tile.sendKey(0, 120, 0);
  check('sendKey clears retained selection before terminal output', inputClears.tile.selection, null);
  check('sendKey clear does not duplicate terminal output', inputClears.sent.length, 1);
  inputClears.tile.selection = authoritativeSelection('IME over');
  inputClears.tile.sendText('x');
  check('sendText clears retained selection before terminal output', inputClears.tile.selection, null);
  check('sendText clear does not duplicate terminal output', inputClears.sent.length, 2);

  const pasteClears = makeTile();
  pasteClears.tile.core.mux_paste_begin = () => 0;
  pasteClears.tile.core.mux_paste_end = () => 0;
  pasteClears.tile.core.mux_text_encode = (len) => len;
  pasteClears.tile.core.mux_output_len = () => 1;
  pasteClears.tile.selection = authoritativeSelection('paste over');
  h.setZoomedTile(pasteClears.tile);
  let pastePrevented = false;
  h.document.dispatchEvent('paste', {
    preventDefault() { pastePrevented = true; },
    clipboardData: { getData: () => 'p' },
  });
  check('real paste handler prevents browser insertion', pastePrevented, true);
  check('real paste handler clears retained selection through sendText', pasteClears.tile.selection, null);
  check('real paste handler emits one unwrapped text frame', pasteClears.sent.length, 1);

  const pasteScroll = makeTile();
  pasteScroll.tile.scrollPages = 1;
  pasteScroll.tile.viewStartRow = 25;
  pasteScroll.tile.core.mux_paste_begin = () => 0;
  pasteScroll.tile.core.mux_paste_end = () => 0;
  pasteScroll.tile.core.mux_text_encode = (len) => len;
  pasteScroll.tile.core.mux_output_len = () => 1;
  h.setZoomedTile(pasteScroll.tile);
  h.document.dispatchEvent('paste', {
    preventDefault() {},
    clipboardData: { getData: () => 'pasted while paged back' },
  });
  check('paste returns the tile to live like every other input path', pasteScroll.tile.scrollPages, 0);
  check('paste out of history mode still delivers its content', pasteScroll.sent.length, 1);

  const compositionClears = makeTile();
  compositionClears.tile.core.mux_text_encode = (len) => len;
  compositionClears.tile.core.mux_output_len = () => 1;
  compositionClears.tile.selection = authoritativeSelection('composition over');
  h.setZoomedTile(compositionClears.tile);
  h.elements.ime.value = 'stale';
  h.elements.ime.dispatchEvent('compositionend', { data: '漢' });
  check('real composition handler clears retained selection through sendText', compositionClears.tile.selection, null);
  check('real composition handler emits one terminal text frame', compositionClears.sent.length, 1);
  check('composition handler still clears hidden IME value', h.elements.ime.value, '');

  const sharedWriter = makeTile();
  const sharedCalls = [];
  sharedWriter.tile.writeClipboardText = (text) => { sharedCalls.push(text); return Promise.resolve(); };
  const encodedOsc = Buffer.from('shared OSC writer', 'utf8').toString('base64');
  new Uint8Array(sharedWriter.memory.buffer).set(Buffer.from(encodedOsc, 'ascii'), 96);
  sharedWriter.tile.core.mux_clipboard_ptr = () => 96;
  sharedWriter.tile.core.mux_clipboard_len = () => encodedOsc.length;
  await sharedWriter.tile.onClipboardEffect();
  sharedWriter.tile.selection = authoritativeSelection('shared selection writer');
  await sharedWriter.tile.copySelection();
  check('OSC52 and explicit selection share the low-level writer', sharedCalls.join('|'), 'shared OSC writer|shared selection writer');

  const clipboardRace = makeTile();
  clipboardRace.tile.pendingClipboard = 'older OSC52';
  clipboardRace.tile.clipboardVersion = 1;
  clipboardRace.tile.copyUiOwner = 'clipboard';
  const oldOsc = deferred(), explicitCopy = deferred();
  const raceCalls = [];
  h.navigator.clipboard = { writeText(text) {
    raceCalls.push(text);
    return text === 'older OSC52' ? oldOsc.promise : explicitCopy.promise;
  } };
  const oldOscRun = clipboardRace.tile.tryClipboardWrite(1, true);
  clipboardRace.tile.selection = authoritativeSelection('new explicit selection');
  h.setZoomedTile(clipboardRace.tile);
  const raceEvent = keyEvent({ key: 'c', ctrlKey: true });
  h.document.dispatchEvent('keydown', raceEvent);
  check('explicit selection copy starts immediately while OSC52 is active', raceCalls.join('|'), 'older OSC52|new explicit selection');
  check('selection copy owns shared UI while writes overlap', clipboardRace.tile.copyUiOwner, 'selection');
  explicitCopy.resolve();
  await flushPromises();
  check('explicit selection success reports Copied', `${clipboardRace.tile.copyButton.className}|${clipboardRace.tile.copyButton.textContent}`, 'copy-request on|Copied');
  oldOsc.resolve();
  await oldOscRun;
  check('older OSC52 settlement cannot overwrite selection-copy success UI', `${clipboardRace.tile.copyButton.className}|${clipboardRace.tile.copyButton.textContent}`, 'copy-request on|Copied');
  check('older OSC52 still completes its serialized pending state', clipboardRace.tile.pendingClipboard, null);

  // The re-dispatch at the end of tryClipboardWrite is ours to start, and
  // an explicit user copy is the newest statement of intent: a queued OSC 52
  // must not reach the system clipboard after the gesture that asked for
  // something else.
  const revoked = makeTile();
  revoked.tile.pendingClipboard = 'first OSC52';
  revoked.tile.clipboardVersion = 1;
  revoked.tile.copyUiOwner = 'clipboard';
  const firstOsc = deferred();
  const revokedCalls = [];
  h.navigator.clipboard = { writeText(text) {
    revokedCalls.push(text);
    return text === 'first OSC52' ? firstOsc.promise : Promise.resolve();
  } };
  const revokedRun = revoked.tile.tryClipboardWrite(1, true);
  // A second OSC 52 lands while the first write is still with the browser,
  // so it is queued in the one pending slot rather than started.
  revoked.tile.pendingClipboard = 'second OSC52';
  revoked.tile.clipboardVersion++;
  check('queued OSC52 is not yet handed to the browser', revokedCalls.join('|'), 'first OSC52');
  revoked.tile.selection = authoritativeSelection('what the user asked for');
  h.setZoomedTile(revoked.tile);
  h.document.dispatchEvent('keydown', keyEvent({ key: 'c', ctrlKey: true }));
  await flushPromises();
  firstOsc.resolve();
  await revokedRun;
  await flushPromises();
  check('explicit copy revokes the queued OSC52 write', revokedCalls.join('|'), 'first OSC52|what the user asked for');
  check('explicit copy drops the queued OSC52 text', revoked.tile.pendingClipboard, null);
  check('revoked OSC52 leaves the copy control to the selection', revoked.tile.copyUiOwner, 'selection');

  const oldTimerRace = makeTile();
  oldTimerRace.tile.pendingClipboard = 'prior copied OSC52';
  oldTimerRace.tile.clipboardVersion = 1;
  oldTimerRace.tile.copyUiOwner = 'clipboard';
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await oldTimerRace.tile.tryClipboardWrite(1, true);
  const priorOscTimer = h.timers.at(-1);
  oldTimerRace.tile.selection = authoritativeSelection('selection after OSC timer');
  const selectionTimerWrite = deferred();
  h.navigator.clipboard = { writeText: () => selectionTimerWrite.promise };
  const selectionTimerRun = oldTimerRace.tile.copySelection();
  selectionTimerWrite.reject(new Error('selection denied'));
  await selectionTimerRun;
  priorOscTimer.fn();
  check('old OSC52 timer cannot overwrite selection-copy failure', `${oldTimerRace.tile.copyButton.className}|${oldTimerRace.tile.copyButton.textContent}`, 'copy-request on error|Copy failed');

  const copyFailure = makeTile();
  copyFailure.tile.selection = authoritativeSelection('retry exact selection');
  h.setZoomedTile(copyFailure.tile);
  h.navigator.clipboard = { writeText: () => Promise.reject(new Error('denied')) };
  const failedCopyEvent = keyEvent({ key: 'c', ctrlKey: true });
  h.document.dispatchEvent('keydown', failedCopyEvent);
  await flushPromises();
  check('selection clipboard rejection is handled as visible failure', `${copyFailure.tile.copyButton.className}|${copyFailure.tile.copyButton.textContent}`, 'copy-request on error|Copy failed');
  check('selection clipboard rejection retains exact text for retry', copyFailure.tile.selection?.text, 'retry exact selection');
  check('copy failure with valid text is durable, not transient unavailable feedback', copyFailure.tile.selectionFeedbackTimer, null);
  const retryWrites = [];
  h.navigator.clipboard = { writeText: (text) => { retryWrites.push(text); return Promise.resolve(); } };
  copyFailure.tile.copyButton.dispatchEvent('click', { stopPropagation() {} });
  await flushPromises();
  check('selection failure button retries the retained selection only', retryWrites.join('|'), 'retry exact selection');
  check('selection retry click restores hidden IME focus', h.document.activeElement, h.elements.ime);

  // The same zoom gate onClipboardEffect and copyPendingClipboard carry:
  // only the tile the human is looking at may reach the system clipboard.
  const unzoomedSelectionCopy = makeTile();
  unzoomedSelectionCopy.tile.zoomed = false;
  unzoomedSelectionCopy.tile.selection = authoritativeSelection('wall tiles do not copy');
  const unzoomedSelectionWrites = [];
  h.navigator.clipboard = { writeText: (text) => { unzoomedSelectionWrites.push(text); return Promise.resolve(); } };
  await unzoomedSelectionCopy.tile.copySelection();
  check('an unzoomed tile writes no clipboard for its selection', unzoomedSelectionWrites.length, 0);
  check('an unzoomed selection copy claims no copy control', unzoomedSelectionCopy.tile.copyUiOwner, null);
  check('an unzoomed selection copy retains its text', unzoomedSelectionCopy.tile.selection?.text, 'wall tiles do not copy');

  const invalidatedCopy = makeTile();
  invalidatedCopy.tile.selection = authoritativeSelection('old selection');
  h.setZoomedTile(invalidatedCopy.tile);
  const pendingSelectionCopy = deferred();
  h.navigator.clipboard = { writeText: () => pendingSelectionCopy.promise };
  const invalidatedRun = invalidatedCopy.tile.copySelection();
  invalidatedCopy.tile.clearSelection(false);
  invalidatedCopy.tile.selection = authoritativeSelection('new selection');
  pendingSelectionCopy.resolve();
  await invalidatedRun;
  check('old copy promise cannot overwrite a new selection UI', `${invalidatedCopy.tile.copyButton.className}|${invalidatedCopy.tile.copyButton.textContent}`, 'copy-request|Copy');

  const oldSelectionTimer = makeTile();
  oldSelectionTimer.tile.selection = authoritativeSelection('copied old selection');
  h.navigator.clipboard = { writeText: () => Promise.resolve() };
  await oldSelectionTimer.tile.copySelection();
  const oldSelectionHide = h.timers.at(-1);
  oldSelectionTimer.tile.canvas.dispatchEvent('pointerdown', pointer(23, 3, 2));
  oldSelectionTimer.tile.renderCopyUi('selection', 'copy-request on error', 'New selection pending');
  oldSelectionHide.fn();
  check('old selection success timer cannot overwrite new selection UI', `${oldSelectionTimer.tile.copyButton.className}|${oldSelectionTimer.tile.copyButton.textContent}`, 'copy-request on error|New selection pending');
  oldSelectionTimer.tile.canvas.dispatchEvent('pointercancel', { pointerId: 23 });

  const unzoomCopy = makeTile();
  unzoomCopy.tile.selection = authoritativeSelection('leave during copy');
  unzoomCopy.tile.exitScroll = () => {};
  unzoomCopy.tile.reflow = () => {};
  h.setZoomedTile(unzoomCopy.tile);
  const afterUnzoom = deferred();
  h.navigator.clipboard = { writeText: () => afterUnzoom.promise };
  const unzoomCopyRun = unzoomCopy.tile.copySelection();
  h.unzoom();
  check('unzoom clears retained selection during copy', unzoomCopy.tile.selection, null);
  afterUnzoom.reject(new Error('late denial'));
  await unzoomCopyRun;
  check('late selection-copy failure after unzoom remains invisible', `${unzoomCopy.tile.copyButton.className}|${unzoomCopy.tile.copyButton.textContent}`, 'copy-request|Copy');

  const unzoomScroll = makeTile();
  unzoomScroll.tile.viewStartRow = 30;
  unzoomScroll.tile.changeScrollPages(1);
  const unzoomScrollTimeout = h.timers[unzoomScroll.tile.scrollRequestTimer - 1];
  h.setZoomedTile(unzoomScroll.tile);
  h.unzoom();
  check('unzoom invalidates pending scroll request', unzoomScroll.tile.scrollRequest, null);
  check('unzoom clears pending scroll timeout', unzoomScrollTimeout?.cleared, true);

  const newSelectionUi = makeTile();
  newSelectionUi.tile.selection = {
    anchor: { row: 0, col: 0 }, active: { row: 0, col: 1 }, requestId: 76, text: null,
  };
  authorizeSelectionReply(newSelectionUi);
  newSelectionUi.setResult(76, 3, []);
  newSelectionUi.tile.onSelectionReply();
  const oldUnavailableHandle = newSelectionUi.tile.selectionFeedbackTimer;
  const oldUnavailableTimer = oldUnavailableHandle === null
    ? undefined : h.timers[oldUnavailableHandle - 1];
  newSelectionUi.tile.canvas.dispatchEvent('pointerdown', pointer(17, 1, 1));
  check('starting a new selection removes stale unavailable UI', `${newSelectionUi.tile.copyButton.className}|${newSelectionUi.tile.copyButton.textContent}`, 'copy-request|Copy');
  check('starting a new selection clears unavailable feedback timer', oldUnavailableTimer?.cleared, true);
  oldUnavailableTimer?.fn();
  check('stale unavailable timer cannot alter new selection UI', `${newSelectionUi.tile.copyButton.className}|${newSelectionUi.tile.copyButton.textContent}`, 'copy-request|Copy');
  newSelectionUi.tile.canvas.dispatchEvent('pointercancel', { pointerId: 17 });

  const wrap = makeTile();
  wrap.tile.nextSelectionId = 0xffffffff;
  wrap.tile.canvas.dispatchEvent('pointerdown', pointer(14, 1, 1));
  wrap.tile.canvas.dispatchEvent('pointermove', pointer(14, 2, 1));
  wrap.tile.canvas.dispatchEvent('pointerup', pointer(14, 2, 1));
  check('selection request ID naturally wraps from max u32 to zero', wrap.requestCalls[0]?.[0], 0);
  check('wrapped zero remains the retained correlation ID', wrap.tile.selection.requestId, 0);
  wrap.setResult(0, 0, Buffer.from('wrapped'));
  wrap.tile.onSelectionReply();
  check('wrapped zero reply correlates normally', wrap.tile.selection.text, 'wrapped');

  const reset = makeTile();
  reset.tile.selection = {
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 7, text: 'resolved old session',
  };
  reset.tile.drag = { pointerId: 15, moved: true };
  reset.tile.lastPointerX = 20;
  reset.tile.lastPointerY = 30;
  reset.tile.viewStartRow = 30;
  reset.tile.changeScrollPages(1);
  const resetScrollTimeout = h.timers[reset.tile.scrollRequestTimer - 1];
  reset.tile.canvas.setPointerCapture(15);
  reset.tile.selectionScrollTimer = h.context.setInterval(() => {}, 120);
  const resetTimer = reset.tile.selectionScrollTimer;
  reset.tile.resetCore('selection test');
  check('destructive core reset clears resolved selection', reset.tile.selection, null);
  check('destructive core reset clears drag state', reset.tile.drag, null);
  check('destructive core reset clears pointer X', reset.tile.lastPointerX, null);
  check('destructive core reset clears pointer Y', reset.tile.lastPointerY, null);
  check('destructive core reset stops selection timer', h.intervals[resetTimer - 1]?.cleared, true);
  check('destructive core reset clears orphaned scroll request', reset.tile.scrollRequest, null);
  check('destructive core reset clears scroll timeout', resetScrollTimeout?.cleared, true);

  const reconnect = makeTile();
  reconnect.tile.canvas.dispatchEvent('pointerdown', pointer(16, 1, 1));
  const reconnectTimer = reconnect.tile.selectionScrollTimer;
  reconnect.tile.selection.requestId = 44;
  reconnect.tile.connect();
  reconnect.tile.ws.onopen();
  check('new socket clears pending selection from prior connection', reconnect.tile.selection, null);
  check('new socket clears prior drag state', reconnect.tile.drag, null);
  check('new socket clears prior pointer X', reconnect.tile.lastPointerX, null);
  check('new socket clears prior pointer Y', reconnect.tile.lastPointerY, null);
  check('new socket stops prior selection timer', h.intervals[reconnectTimer - 1]?.cleared, true);

  const reattach = makeTile();
  reattach.tile.selection = {
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 45, text: 'old attach',
  };
  reattach.tile.sendAttach(false);
  check('reattach clears selection text from prior session', reattach.tile.selection, null);

  const openPaint = makeTile();
  delete openPaint.tile.reflow;
  const openOps = [];
  openPaint.tile.core.mux_mark_all_dirty = () => { openOps.push('mark'); };
  openPaint.tile.paintLive = () => { openOps.push('live'); };
  openPaint.tile.paintScroll = () => { openOps.push('scroll'); };
  openPaint.tile.selection = {
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 50, text: 'painted',
  };
  openPaint.tile.connect();
  openPaint.tile.ws.onopen();
  check('socket open erases retained live selection pixels once', openOps.join('|'), 'mark|live');

  const attachPaint = makeTile();
  delete attachPaint.tile.reflow;
  const attachOps = [];
  attachPaint.tile.scrollPages = 2;
  attachPaint.tile.core.mux_mark_all_dirty = () => { attachOps.push('mark'); };
  attachPaint.tile.paintLive = () => { attachOps.push('live'); };
  attachPaint.tile.paintScroll = () => { attachOps.push('scroll'); };
  attachPaint.tile.selection = {
    anchor: { row: 20, col: 1 }, active: { row: 21, col: 2 }, requestId: 51, text: 'history pixels',
  };
  attachPaint.tile.sendAttach(false);
  check('reattach erases retained history selection pixels once', attachOps.join('|'), 'mark|scroll');

  const resetPaint = makeTile();
  delete resetPaint.tile.reflow;
  const resetOps = [];
  resetPaint.tile.scrollPages = 2;
  resetPaint.tile.core.mux_mark_all_dirty = () => { resetOps.push('mark'); };
  resetPaint.tile.paintLive = () => { resetOps.push('live'); };
  resetPaint.tile.paintScroll = () => { resetOps.push('scroll'); };
  resetPaint.tile.selection = {
    anchor: { row: 20, col: 1 }, active: { row: 21, col: 2 }, requestId: 52, text: 'old core',
  };
  resetPaint.tile.resetCore('paint safety test');
  check('successful destructive reset repaints only the new live core', resetOps.join('|'), 'mark|live');

  const failedResetPaint = makeTile();
  delete failedResetPaint.tile.reflow;
  const failedResetOps = [];
  failedResetPaint.tile.canvas.width = 640;
  failedResetPaint.tile.canvas.height = 480;
  failedResetPaint.tile.ctx.setTransform = (...args) => { failedResetOps.push(`transform:${args.join(',')}`); };
  failedResetPaint.tile.ctx.clearRect = (...args) => { failedResetOps.push(`clear:${args.join(',')}`); };
  failedResetPaint.tile.core.mux_init = () => -1;
  failedResetPaint.tile.core.mux_mark_all_dirty = () => { throw new Error('invalid old core read'); };
  failedResetPaint.tile.paintLive = () => { throw new Error('invalid old live paint'); };
  failedResetPaint.tile.paintScroll = () => { throw new Error('invalid old scroll paint'); };
  failedResetPaint.tile.selection = {
    anchor: { row: 1, col: 1 }, active: { row: 1, col: 2 }, requestId: 53, text: 'must disappear',
  };
  failedResetPaint.tile.scrollRequest = { fromPages: 1, start: 20 };
  failedResetPaint.tile.resetCore('failed paint safety test');
  check(
    'failed destructive reset clears backing pixels without reading invalid core',
    failedResetOps.join('|'),
    'transform:1,0,0,1,0,0|clear:0,0,640,480',
  );
  check('failed destructive reset also clears orphaned scroll request', failedResetPaint.tile.scrollRequest, null);
}

// The terminal-status region: which badge stops a tile re-attaching, and
// the two words the hub and the page agree on. Nothing on the wire can
// see any of it — wsclient attaches 0x0 and renders no badge — so this
// shell is the gate, and a real browser is the gate for pixels and keys.
async function verifyStatusShell(shell) {
  const h = browserShell(shell);
  const wall = h.document.createElement('div');

  // ENV_FRAME/ENV_CONTROL and the exit_status opcode, spelled as the
  // browser receives them: mux.js reads these bytes off a WebSocket, so a
  // fixture that called its constants instead would pin nothing.
  const exitStatus = (code) => Uint8Array.from([0x00, 0x82, 1, 0, 0, 0, code]);
  const control = (state) =>
    Uint8Array.from([0x01, ...new TextEncoder().encode(JSON.stringify({ state }))]);

  const makeTile = () => {
    const tile = new h.Tile(9, 'status fixture', wall, '');
    tile.core = {
      memory: { buffer: new ArrayBuffer(64) },
      mux_attach_payload: () => 20,
      mux_output_ptr: () => 0,
      mux_output_len: () => 20,
    };
    const sent = [];
    tile.sendFrame = (type) => { sent.push(type); return true; };
    tile.reflow = () => {};
    return { tile, sent };
  };

  // --- the badge vocabulary, which webhub.zig's restore rule reads back ---

  const refusal = makeTile();
  refusal.tile.onMessage(exitStatus(0));
  check('a refusal before any grid badges refused', refusal.tile.status, 'refused');
  check('the refusal badge names no reason the daemon never gave', refusal.tile.statusText, 'refused');

  const ending = makeTile();
  ending.tile.gotState = true;
  ending.tile.onMessage(exitStatus(3));
  check('an exit after a grid badges exited', ending.tile.status, 'exited');
  check('the exit badge carries the code', ending.tile.statusText, 'exited 3');

  // --- what each badge does to the next attach ---

  const exited = makeTile();
  exited.tile.gotState = true;
  exited.tile.onMessage(exitStatus(0));
  exited.tile.zoomed = true; // a sized attach CREATES: the dangerous one
  exited.tile.sendAttach(false);
  check('a session the user ended is not re-attached', exited.sent.length, 0);

  // The restore heal IS a refusal followed by a re-attach: the hub births
  // the session and re-dials, and this attach is what lands on it. Gate
  // the whole TERMINAL set and the browser wall stays dead after a daemon
  // restart.
  const refused = makeTile();
  refused.tile.onMessage(exitStatus(0));
  refused.tile.sendAttach(false);
  check('a refused tile keeps re-attaching', refused.sent.length, 1);

  // --- the precedence the gate rests on ---

  const narrated = makeTile();
  narrated.tile.gotState = true;
  narrated.tile.onMessage(exitStatus(0));
  narrated.tile.onMessage(control('up'));
  check('link news cannot clear exited', narrated.tile.status, 'exited');
  check('and the up it rode in on sends no attach', narrated.sent.length, 0);
}

// --- wire builders (layouts golden-pinned in protocol.zig) ---
//
// A row on the wire is a CellRow: a u16 cell count, then runs of cells. A run
// is a u16 count, a u8 mask of the style fields that CHANGED since the run
// before it in the same row, those fields, and then the cells. Each cell is a
// head byte of `wide << 6 | text_len` followed by its UTF-8, except in an
// all-ASCII run (mask bit 7), where every cell is one bare byte.
//
// Colours pack as 0 none, (1 << 24) | index palette, (2 << 24) | rgb; on the
// wire a present colour is a tag byte, 0 none, 1 palette + one byte, 2 rgb +
// three. Style flags are ghostty's bit order: bold 0, italic 1, faint 2,
// blink 3, inverse 4, invisible 5, strikethrough 6, overline 7.
const MASK_FLAGS = 1 << 0, MASK_FG = 1 << 1, MASK_BG = 1 << 2, MASK_UL = 1 << 3;
const MASK_ASCII = 1 << 7;

function colorBytes(packed) {
  const tag = packed >>> 24;
  if (tag === 0) return [0];
  if (tag === 1) return [1, packed & 0xff];
  return [2, (packed >>> 16) & 0xff, (packed >>> 8) & 0xff, packed & 0xff];
}

/// One run of cells sharing a style. `cells` are {text, wide} — wide 0 narrow,
/// 1 wide, 2 spacer_tail, 3 spacer_head.
function runBytes(style, cells, prev) {
  const out = [];
  out.push(cells.length & 0xff, (cells.length >> 8) & 0xff);
  const ascii = cells.every((c) => c.wide === 0 && c.text.length === 1 &&
    c.text.charCodeAt(0) >= 0x20 && c.text.charCodeAt(0) <= 0x7e);
  let mask = ascii ? MASK_ASCII : 0;
  const fields = [];
  if (style.flags !== prev.flags) {
    mask |= MASK_FLAGS;
    fields.push(style.flags & 0xff, (style.flags >> 8) & 0xff);
  }
  if (style.fg !== prev.fg) { mask |= MASK_FG; fields.push(...colorBytes(style.fg)); }
  if (style.bg !== prev.bg) { mask |= MASK_BG; fields.push(...colorBytes(style.bg)); }
  if (style.ul !== prev.ul) { mask |= MASK_UL; fields.push(...colorBytes(style.ul)); }
  out.push(mask, ...fields);
  for (const c of cells) {
    const bytes = [...Buffer.from(c.text, 'utf8')];
    if (ascii) out.push(bytes[0]);
    else out.push((c.wide << 6) | bytes.length, ...bytes);
  }
  return out;
}

/// A CellRow from a list of runs, each {style, cells}. Styles default to
/// plain, so a caller states only what it means to exercise.
function cellRow(runs) {
  const plain = { fg: 0, bg: 0, ul: 0, flags: 0 };
  let ncells = 0;
  for (const r of runs) ncells += r.cells.length;
  const out = [ncells & 0xff, (ncells >> 8) & 0xff];
  let prev = plain;
  for (const r of runs) {
    const style = { ...plain, ...(r.style || {}) };
    out.push(...runBytes(style, r.cells, prev));
    prev = style;
  }
  return Buffer.from(out);
}

/// A CellRow of plain narrow ASCII: the common case, one run.
function textRow(text) {
  if (text.length === 0) return Buffer.from([0, 0]);
  return cellRow([{ cells: [...text].map((ch) => ({ text: ch, wide: 0 })) }]);
}

function snapshotPayload({ seq, history, cols, rows, epoch, cx = 0, cy = 0 }, rowList) {
  const head = Buffer.alloc(28);
  head.writeBigUInt64LE(BigInt(seq), 0);
  head.writeUInt32LE(history, 8);
  head.writeUInt16LE(cols, 12);
  head.writeUInt16LE(rows, 14);
  head.writeBigUInt64LE(BigInt(epoch), 16);
  head.writeUInt16LE(cx, 24);
  head.writeUInt16LE(cy, 26);
  const body = [];
  for (let y = 0; y < rows; y++) {
    const r = rowList[y];
    body.push(r === undefined ? textRow('') : (Buffer.isBuffer(r) ? r : textRow(r)));
  }
  return Buffer.concat([head, ...body]);
}

function deltaPayload({ seq, history, cx, cy }, rowEntries) {
  const parts = [];
  const hdr = Buffer.alloc(18);
  hdr.writeBigUInt64LE(BigInt(seq), 0);
  hdr.writeUInt32LE(history, 8);
  hdr.writeUInt16LE(cx, 12);
  hdr.writeUInt16LE(cy, 14);
  hdr.writeUInt16LE(rowEntries.length, 16);
  parts.push(hdr);
  for (const [row, content] of rowEntries) {
    const bytes = Buffer.isBuffer(content) ? content : textRow(content);
    const rh = Buffer.alloc(6);
    rh.writeUInt16LE(row, 0);
    rh.writeUInt32LE(bytes.length, 2);
    parts.push(rh, bytes);
  }
  return Buffer.concat(parts);
}

/// A scrollback_chunk payload: the echoed start and count, then the rows.
function scrollChunk(start, rowList) {
  const head = Buffer.alloc(6);
  head.writeUInt32LE(start, 0);
  head.writeUInt16LE(rowList.length, 4);
  return Buffer.concat([head, ...rowList.map((r) => (Buffer.isBuffer(r) ? r : textRow(r)))]);
}

// Settings: the browser's own theme and font. Neither is a protocol
// concern — colour is invented at paint time from a palette INDEX, and
// the daemon has no notion of a font — with one exception this file
// pins: font size moves the cell, so a ZOOMED tile's grid moves with it.
async function verifySettingsShell(shell, html) {
  const h = browserShell(shell);
  const parse = h.parseGhosttyTheme;
  check('shell exposes a ghostty theme parser', typeof parse, 'function');
  check('shell exposes settings application', typeof h.applySettings, 'function');
  check('shell exposes persisted settings loading', typeof h.loadSettings, 'function');
  if (typeof parse !== 'function' || typeof h.applySettings !== 'function') return;

  // --- the parser, against what a real theme file contains ---
  const mocha = [
    'palette = 0=#45475a',
    'palette = 15=#bac2de',
    'background = #1e1e2e',
    'foreground = #cdd6f4',
    'cursor-color = #f5e0dc',
    'cursor-text = #1e1e2e',
    'selection-background = #585b70',
    'selection-foreground = #cdd6f4',
  ].join('\n');
  const m = parse(mocha);
  // The value of a palette line CONTAINS an '=' — splitting on all of
  // them is the bug this asserts against.
  check('palette line splits on the first = only', m?.theme.palette[0], '#45475a');
  check('palette line reads the last ANSI slot', m?.theme.palette[15], '#bac2de');
  check('theme reads background', m?.theme.bg, '#1e1e2e');
  check('theme reads foreground', m?.theme.fg, '#cdd6f4');
  check('theme reads cursor colour', m?.theme.cursor, '#f5e0dc');
  check('theme reads selection background', m?.theme.selection, '#585b70');
  // A translucent overlay cannot honour a text colour: naming them is not
  // the same as applying them, so they count as ignored, not applied.
  check('theme counts keys it cannot honour as ignored', m?.ignored, 2);

  check('parser tolerates missing spaces', parse('palette=8=#585b70')?.theme.palette[8], '#585b70');
  check('parser tolerates CRLF', parse('background = #112233\r\nforeground = #445566\r\n')?.theme.bg, '#112233');
  check('parser strips a BOM', parse('background = #112233')?.theme.bg, '#112233');
  check('parser ignores comments', parse('# background = #ffffff\nbackground = #112233')?.theme.bg, '#112233');
  check('parser accepts bare hex', parse('background = 1e1e2e')?.theme.bg, '#1e1e2e');
  check('parser expands short hex', parse('background = #abc')?.theme.bg, '#aabbcc');
  check('parser takes the last of duplicate keys', parse('background = #111111\nbackground = #222222')?.theme.bg, '#222222');
  check('parser is case-insensitive about hex', parse('background = #AABBCC')?.theme.bg, '#aabbcc');

  // 16-255 are an xterm formula, not a theme's to set: taking one would
  // leave the cube incoherent with the sixteen below it.
  const beyond = parse('palette = 16=#ffffff\npalette = 255=#ffffff\nbackground = #111111');
  check('parser refuses palette indices above 15', beyond?.theme.palette.length, 16);
  check('parser counts an out-of-range palette line as ignored', beyond?.ignored, 2);
  check('parser refuses a negative palette index', parse('palette = -1=#ffffff'), null);
  check('parser refuses a non-numeric palette index', parse('palette = x=#ffffff'), null);
  check('parser refuses a malformed colour', parse('background = #12345'), null);
  check('parser refuses a colour name', parse('background = rebeccapurple'), null);

  // A file that is not a theme must be REFUSED, not silently applied as
  // an empty one: a PNG and a whole ghostty config both land here.
  check('parser refuses a file with no colours', parse('PNG\r\n\n  binary'), null);
  check('parser refuses an empty file', parse(''), null);
  const config = 'font-family = JetBrains Mono\nkeybind = ctrl+a=new_tab\nbackground = #101010\n';
  const fromConfig = parse(config);
  check('a whole ghostty config still yields its colours', fromConfig?.theme.bg, '#101010');
  check('a whole ghostty config reports what it skipped', fromConfig?.ignored, 2);
  // keybind's value contains '=' too, so a first-= split must not let it
  // through as a colour.
  check('a keybind line is never mistaken for a colour', fromConfig?.applied, 1);

  // --- applying a theme ---
  const before = h.settingsState();
  check('the untouched palette is the full xterm 256', before.PALETTE.length, 256);
  const cube = before.PALETTE.slice(16).join('|');
  h.applySettings({ theme: parse(mocha).theme, fontFamily: null, fontSize: null });
  const themed = h.settingsState();
  check('applying a theme replaces the ANSI slots', themed.PALETTE[0], '#45475a');
  check('applying a theme leaves the 240-colour cube computed', themed.PALETTE.slice(16).join('|'), cube);
  check('applying a theme keeps the palette 256 long', themed.PALETTE.length, 256);
  // A theme names 0 and 15 here and nothing between: the unnamed slots
  // keep the built-in ANSI colour rather than becoming undefined.
  check('a partial theme keeps built-in colours for unnamed slots', themed.PALETTE[1], before.PALETTE[1]);
  check('applying a theme sets the default foreground', themed.DEFAULT_FG, '#cdd6f4');
  check('applying a theme sets the default background', themed.DEFAULT_BG, '#1e1e2e');
  // The overlays are translucent so the glyph under them survives: theme
  // colour plus an alpha byte, never a second hardcoded constant.
  check('the cursor overlay derives from the theme', themed.CURSOR_FILL, '#f5e0dc88');
  check('the selection overlay derives from the theme', themed.SELECTION_FILL, '#585b7055');
  // The canvas element's own background is terminal ground: it shows
  // before the first frame and through sizeCanvas's rounding edge, so a
  // light theme with a black canvas flashes black.
  check('applying a theme republishes terminal ground', h.documentElement.style['--term-bg'], '#1e1e2e');

  const noCursor = parse('background = #101010\nforeground = #e0e0e0');
  h.applySettings({ theme: noCursor.theme, fontFamily: null, fontSize: null });
  const fallback = h.settingsState();
  check('a theme without a cursor colour falls back to its foreground', fallback.CURSOR_FILL, '#e0e0e088');
  check('a theme without a selection colour keeps the built-in blend', fallback.SELECTION_FILL, '#6ab0e055');

  h.applySettings({ theme: null, fontFamily: null, fontSize: null });
  const reset = h.settingsState();
  check('clearing the theme restores the built-in foreground', reset.DEFAULT_FG, before.DEFAULT_FG);
  check('clearing the theme restores the built-in palette', reset.PALETTE.join('|'), before.PALETTE.join('|'));

  // --- the overlays actually paint what the theme said ---
  const painted = h.makeSettingsTile();
  h.applySettings({ theme: parse(mocha).theme, fontFamily: null, fontSize: null });
  const fills = [];
  painted.ctx = {
    fillStyle: '',
    fillRect(x, y, width, height) { fills.push([x, y, width, height, this.fillStyle]); },
  };
  painted.paintCursor();
  check('the cursor paints the themed colour', fills.at(-1)?.[4], '#f5e0dc88');
  painted.viewStartRow = 0;
  painted.selection = { anchor: { row: 0, col: 0 }, active: { row: 0, col: 2 }, requestId: 1, text: null };
  painted.paintSelection();
  check('the selection paints the themed colour', fills.at(-1)?.[4], '#585b7055');

  // A repaint after a settings change must CLEAR first. A wall tile paints
  // through a scaled transform, so each cell fill lands on fractional
  // device pixels and its antialiased seam keeps a blend of whatever was
  // underneath — a light theme painted straight over a dark one keeps the
  // old colour in a lattice along every cell boundary.
  const stale = h.makeSettingsTile();
  const order = [];
  stale.clearBackingCanvas = () => order.push('clear');
  stale.reflow = () => order.push('reflow');
  stale.repaintForSettings();
  check('a settings repaint clears the stale palette before painting', order.join('|'), 'clear|reflow');
  const coreless = h.makeSettingsTile();
  coreless.core = null;
  coreless.clearBackingCanvas = () => order.push('clear-coreless');
  coreless.repaintForSettings();
  check('a tile with no core is left alone by a settings repaint', order.join('|'), 'clear|reflow');

  // --- font: the one setting that reaches the daemon ---
  h.applySettings({ theme: null, fontFamily: null, fontSize: null });
  const baseMetrics = h.settingsState().METRICS;
  check('the default cell is measured from the default font', `${baseMetrics.w}x${baseMetrics.h}`, '8x14');
  const zoomed = h.makeSettingsTile();
  zoomed.zoomed = true;
  const baseCols = zoomed.zoomCols();
  h.applySettings({ theme: null, fontFamily: null, fontSize: 28 });
  const bigger = h.settingsState().METRICS;
  check('a larger font measures a larger cell', `${bigger.w}x${bigger.h}`, '16x28');
  check('a larger font fits fewer columns in the same box', zoomed.zoomCols() < baseCols, true);
  check('the font string keeps the size-then-family shape', h.settingsState().FONT, '28px ui-monospace, monospace');
  check('a font family is applied verbatim', (h.applySettings({ theme: null, fontFamily: 'JetBrains Mono', fontSize: 14 }), h.settingsState().FONT), '14px JetBrains Mono');

  // A zero size would divide the zoom box by a zero-width cell: zoomCols
  // becomes Infinity, setUint16 writes 0, and the daemon is told to
  // resize the session to no columns at all.
  h.applySettings({ theme: null, fontFamily: null, fontSize: 0 });
  const clamped = h.settingsState();
  check('a zero font size is clamped to something measurable', clamped.METRICS.w >= 1, true);
  check('a zero font size never yields an infinite column count', Number.isFinite(zoomed.zoomCols()), true);
  h.applySettings({ theme: null, fontFamily: null, fontSize: 4000 });
  check('an absurd font size is clamped too', h.settingsState().METRICS.w < 200, true);
  check('a non-numeric font size falls back to the default', (h.applySettings({ theme: null, fontFamily: null, fontSize: 'huge' }), h.settingsState().FONT), '14px ui-monospace, monospace');

  // The resize is the SAME claim the window-resize handler makes: only
  // the zoomed tile sends it, and only when the grid really moved.
  const sent = [];
  const claiming = h.makeSettingsTile();
  claiming.sendFrame = (type, payload) => sent.push([type, payload]);
  claiming.zoomed = true;
  h.setZoomedTile(claiming);
  h.applySettings({ theme: null, fontFamily: null, fontSize: 28 });
  check('a font change claims the grid for the zoomed tile', sent.length, 1);
  const claimedCols = sent.length ? new DataView(sent[0][1].buffer, sent[0][1].byteOffset).getUint16(0, true) : 0;
  check('the claimed grid is the one the new cell fits', claimedCols, claiming.zoomCols());
  check('the claimed grid is never zero columns', claimedCols > 0, true);
  // Theme is paint-local: with the grid already agreeing, applying one
  // must put nothing on the wire at all.
  claiming.core.mux_cols = () => claiming.zoomCols();
  claiming.core.mux_rows = () => claiming.zoomRows();
  sent.length = 0;
  h.applySettings({ theme: parse(mocha).theme, fontFamily: null, fontSize: 28 });
  check('a theme change sends nothing to the daemon', sent.length, 0);
  h.setZoomedTile(null);

  // An unzoomed tile claims nothing, so its font is paint-only — the
  // wall stays passive however the settings move.
  const passive = h.makeSettingsTile();
  const passiveSent = [];
  passive.sendFrame = (type, payload) => passiveSent.push([type, payload]);
  h.applySettings({ theme: null, fontFamily: null, fontSize: 20 });
  check('an unzoomed tile sends nothing when the font changes', passiveSent.length, 0);

  // --- the panel must not hold the keyboard the terminal owns ---
  // Zoom hides the settings tile behind the shade, so the panel is
  // unreachable by pointer — but Tab still reaches it, and a key answered
  // by both the focused field and the session types the user's font size
  // into their shell. Caught in a real browser, not here, so it is pinned.
  const keyed = h.makeSettingsTile();
  const typed = [];
  keyed.sendKey = () => typed.push('key');
  keyed.zoomed = true;
  h.setZoomedTile(keyed);
  const fakeSettingsTile = h.document.createElement('div');
  const fakePanelField = fakeSettingsTile.appendChild(h.document.createElement('input'));
  const fakeSizeField = fakeSettingsTile.appendChild(h.document.createElement('input'));
  h.setSettingsTile(fakeSettingsTile);
  const key = (target) => {
    target.focus();
    h.document.dispatchEvent('keydown', {
      key: '2', target, shiftKey: false, altKey: false, ctrlKey: false, metaKey: false,
      preventDefault() {}, isComposing: false,
    });
  };
  key(fakePanelField);
  check('a focused settings field keeps its keys out of the session', typed.length, 0);
  key(fakeSizeField);
  check('a second settings field keeps its keys out too', typed.length, 0);
  // The guard is the settings tile, not "any input": the terminal still
  // has to receive keys when focus is anywhere else.
  const elsewhere = h.document.createElement('input');
  key(elsewhere);
  check('a key with focus outside the settings tile still reaches the session', typed.length > 0, true);
  h.setZoomedTile(null);
  h.setSettingsTile(null);

  // --- persistence, where the browser is allowed to say no ---
  check('settings load to defaults with no stored value', h.loadSettings().fontSize, 14);
  const stored = browserShell(shell, { storage: new Map([['mux.settings.v1', JSON.stringify({ v: 1, fontSize: 20 })]]) });
  check('stored settings are read back', stored.loadSettings().fontSize, 20);
  const corrupt = browserShell(shell, { storage: new Map([['mux.settings.v1', '{not json']]) });
  check('corrupt stored settings fall back to defaults', corrupt.loadSettings().fontSize, 14);
  const older = browserShell(shell, { storage: new Map([['mux.settings.v1', JSON.stringify({ fontSize: 20 })]]) });
  check('settings without the current version are discarded', older.loadSettings().fontSize, 14);
  // Chrome with cookies blocked throws on ACCESSING localStorage, not
  // just on write, and the page must still boot.
  const denied = browserShell(shell, { storage: 'throws' });
  // Caught rather than allowed to propagate: an unguarded storage read
  // throws out of here, and a stack trace kills the whole run instead of
  // naming the one assertion that broke.
  let deniedSize = null;
  try { deniedSize = denied.loadSettings().fontSize; } catch (_) { deniedSize = 'threw'; }
  check('a browser that refuses storage still loads settings', deniedSize, 14);
  let saveThrew = false;
  try { denied.saveSettings({ theme: null, fontFamily: null, fontSize: 20 }); } catch (_) { saveThrew = true; }
  check('a browser that refuses storage still saves without throwing', saveThrew, false);
  const absent = browserShell(shell, { storage: 'none' });
  check('a browser with no storage at all still loads settings', absent.loadSettings().fontSize, 14);

  // --- the file the user hands us ---
  check('the theme upload is capped before it is read', typeof h.THEME_BYTES_MAX, 'number');
  check('the cap is smaller than any plausible paste of a log', h.THEME_BYTES_MAX <= 1 << 20, true);

  // --- the wall keeps its one standing control ---
  const executableHtmlCss = executableCss(html);
  check(
    'terminal ground is a themeable variable, not a hardcoded black',
    /\.tile\s+canvas\s*\{[^}]*background\s*:\s*var\(--term-bg/.test(executableHtmlCss),
    true,
  );
  check(
    'the settings panel lives inside the settings tile',
    /\.tile\.settings-tile\s+\.settings/.test(executableHtmlCss),
    true,
  );
  // The browser authors nothing: the wall is the hosts file, so a page
  // that grew an add box or an `x` back would be showing a control whose
  // route the hub answers 405 to.
  check(
    'the page has no add box and no per-tile close',
    /addTileEl|removeTile|putOrder|draggable/.test(shell),
    false,
  );

  // --- the `+` must not out-race the hub it just asked ---
  // `Hub.spawn` stores a poke and answers; the poller feels it at its next
  // 50 ms slice and only then dials. Six unspaced fetches finish in ~10 ms,
  // so an unspaced loop finds nothing, falls through, and the session the
  // user asked for arrives unzoomed a second later. The delay is a named
  // function precisely so this check can reach it — the network path around
  // it is not executable here.
  check('the spawn poll delay is a function', typeof h.spawnPollDelay, 'function');
  check('the spawn poll delay clears the hub 50ms poke slice', h.SPAWN_POLL_MS >= 100, true);
  const timersBefore = h.timers.length;
  h.spawnPollDelay();
  const spawnTimer = h.timers.at(-1);
  check('the spawn poll delay schedules exactly one timer', h.timers.length - timersBefore, 1);
  check('the spawn poll delay sleeps SPAWN_POLL_MS', spawnTimer?.ms, h.SPAWN_POLL_MS);
  // ...and `spawnHere` actually awaits it. Read off the EXECUTABLE tokens,
  // so a delay that survives only in a comment does not pass.
  const spawnBodies = balancedBodiesAfter(
    executableJsTokens(shell),
    /\basync function spawnHere\s*\([^)]*\)\s*\{/g,
  );
  check('shell has exactly one spawnHere', spawnBodies.length, 1);
  const spawnBody = spawnBodies.length === 1 ? (spawnBodies[0].body ?? '') : '';
  check(
    'spawnHere waits between its polls',
    /await\s+spawnPollDelay\s*\(\s*\)/.test(spawnBody),
    true,
  );
  // The wait is INSIDE the retry loop, not once before it: a single sleep
  // would cover the poke slice and then burn the remaining five polls in
  // 10 ms against a daemon that answered slowly.
  const spawnLoops = balancedBodiesAfter(spawnBody, /\bfor\s*\([^)]*SPAWN_POLLS[^)]*\)\s*\{/g);
  check('spawnHere has one bounded retry loop', spawnLoops.length, 1);
  check(
    'the wait is inside the retry loop',
    /await\s+spawnPollDelay\s*\(\s*\)/.test(spawnLoops.length === 1 ? (spawnLoops[0].body ?? '') : ''),
    true,
  );
}

async function main() {
  const bin = fs.readFileSync(wasmPath);
  const { instance } = await WebAssembly.instantiate(bin, {});
  const e = instance.exports;
  const mem = () => Buffer.from(e.memory.buffer); // ALWAYS fresh
  const inputPtr = e.mux_input_ptr();

  const stage = (buf) => {
    if (buf.length > e.mux_input_cap()) throw new Error('over cap');
    buf.copy(mem(), e.mux_input_ptr());
    return buf.length;
  };
  // Exercise consumers without asking for the staging pointer again. This
  // catches invalidation at the consumer boundary as well as mux_input_ptr.
  const stageThroughKnownPtr = (buf) => {
    if (buf.length > e.mux_input_cap()) throw new Error('over cap');
    buf.copy(mem(), inputPtr);
    return buf.length;
  };
  const outBytes = () =>
    Buffer.from(mem().subarray(e.mux_output_ptr(), e.mux_output_ptr() + e.mux_output_len()));
  const cell = (x, y) => {
    const cols = e.mux_cols();
    const base = e.mux_viewport_ptr() + (y * cols + x) * 16;
    const m = mem();
    return {
      cp: m.readUInt32LE(base),
      fg: m.readUInt32LE(base + 4),
      bg: m.readUInt32LE(base + 8),
      flags: m.readUInt32LE(base + 12),
    };
  };

  // The resume coordinates as the only reader of them has them: the
  // attach payload. There are no scalar accessors for seq/epoch — the
  // shell never wanted them (it cannot hand-assemble a u64 anyway), so
  // the payload IS the interface, and what asserts on it is this.
  const resumeArgs = () => {
    e.mux_attach_payload(0, 0, 0);
    const b = outBytes();
    return { seq: b.readBigUInt64LE(4), epoch: b.readBigUInt64LE(12) };
  };

  // --- init ---
  check('init', e.mux_init(80, 24), 0);
  check('cols', e.mux_cols(), 80);
  check('rows', e.mux_rows(), 24);

  // --- shared client semantics ABI ---
  // Pasting is raw until the daemon explicitly samples mode 2004 as on.
  stage(Buffer.from('seed'));
  check('seed output before disabled begin', e.mux_text_encode(4), 4);
  check('paste begin disabled', e.mux_paste_begin(), 0);
  check('paste begin disabled clears output', e.mux_output_len(), 0);
  stage(Buffer.from('again'));
  check('seed output before disabled end', e.mux_text_encode(5), 5);
  check('paste end disabled', e.mux_paste_end(), 0);
  check('paste end disabled clears output', e.mux_output_len(), 0);

  const modesOn = Buffer.from([1, 0, 0, 0]);
  const modesOff = Buffer.from([0, 0, 0, 0]);
  check('client modes on action', e.mux_client_frame(0x8d, stage(modesOn)), clientAction.terminalModes);
  check('bracketed paste on', e.mux_bracketed_paste(), 1);
  check('client repeated modes action', e.mux_client_frame(0x8d, stage(modesOn)), clientAction.terminalModes);
  check('client modes off action', e.mux_client_frame(0x8d, stage(modesOff)), clientAction.terminalModes);
  check('bracketed paste off', e.mux_bracketed_paste(), 0);
  check('client malformed modes ignored', e.mux_client_frame(0x8d, stage(Buffer.from([1, 0]))), clientAction.ignored);
  check('malformed modes preserve state', e.mux_bracketed_paste(), 0);

  const clipboard = Buffer.from([0, 'c'.charCodeAt(0), ...Buffer.from('aGk=', 'ascii')]);
  check('client clipboard action', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
  check('clipboard target', e.mux_clipboard_target(), 'c'.charCodeAt(0));
  check('clipboard len', e.mux_clipboard_len(), 4);
  check(
    'clipboard borrowed bytes',
    Buffer.from(mem().subarray(e.mux_clipboard_ptr(), e.mux_clipboard_ptr() + e.mux_clipboard_len())).toString('ascii'),
    'aGk=',
  );
  check('client bell action', e.mux_client_frame(0x8f, stage(Buffer.from([1]))), clientAction.bell);
  check('bell clears clipboard getter', e.mux_clipboard_len(), 0);
  check('client unsafe clipboard ignored', e.mux_client_frame(0x8f, stage(Buffer.from([0, 'X'.charCodeAt(0), 'A'.charCodeAt(0)]))), clientAction.ignored);
  check('ignored event clears clipboard getter', e.mux_clipboard_len(), 0);
  check('client unknown u8 type ignored', e.mux_client_frame(0x40, 0), clientAction.ignored);

  check('clipboard before oversize guard', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
  check('client oversize length', e.mux_client_frame(0x8f, e.mux_input_cap() + 1), -2);
  check('oversize clears clipboard len', e.mux_clipboard_len(), 0);
  check('oversize clears clipboard target', e.mux_clipboard_target(), 0);
  check('clipboard before wide-type guard', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
  check('client wide type ignored', e.mux_client_frame(0x100, 0), clientAction.ignored);
  check('wide type clears clipboard len', e.mux_clipboard_len(), 0);
  check('wide type clears clipboard target', e.mux_clipboard_target(), 0);

  const selectionReply = (id, status, text = '', historyRows = 0) => {
    const body = Buffer.from(text, 'utf8');
    const reply = Buffer.alloc(41 + body.length);
    reply.writeUInt32LE(id, 0);
    reply[4] = status;
    reply.writeUInt32LE(historyRows, 5);
    body.copy(reply, 41);
    return reply;
  };
  const selectionText = () => Buffer.from(mem().subarray(
    e.mux_selection_ptr(),
    e.mux_selection_ptr() + e.mux_selection_len(),
  ));
  const firstSelectionId = 0x78563412;
  check('selection request len', e.mux_selection_request(firstSelectionId, 0x44332211, 0x6655, 0xaa998877, 0xccbb), 37);
  check('selection request output len', e.mux_output_len(), 37);
  check(
    'selection request golden bytes',
    outBytes().toString('hex'),
    '12345678112233445566778899aabbcc000000000000000000000000000000000000000000',
  );
  check('selection invalid anchor col', e.mux_selection_request(30, 1, 65536, 2, 3), -3);
  check('selection invalid anchor col clears output', e.mux_output_len(), 0);
  check('selection invalid active col', e.mux_selection_request(30, 1, 2, 3, 65536), -3);
  check('selection invalid active col clears output', e.mux_output_len(), 0);
  check(
    'selection invalid columns preserve pending id',
    e.mux_client_frame(0x90, stage(selectionReply(firstSelectionId, 0, 'preserved'))),
    clientAction.selection,
  );

  const supersededSelectionId = 0x01020304;
  check('selection superseded request', e.mux_selection_request(supersededSelectionId, 5, 6, 7, 8), 37);
  const latestSelectionId = 0x10203040;
  check('selection latest request replaces pending', e.mux_selection_request(latestSelectionId, 9, 10, 11, 12), 37);
  check(
    'selection request survives enlarged input staging capacity',
    outBytes().toString('hex'),
    '40302010090000000a000b0000000c00000000000000000000000000000000000000000000',
  );
  check(
    'selection stale reply ignored',
    e.mux_client_frame(0x90, stage(selectionReply(supersededSelectionId, 0, 'stale'))),
    clientAction.ignored,
  );
  check('selection stale reply leaves getters empty', e.mux_selection_len(), 0);
  check(
    'selection malformed matching reply ignored',
    e.mux_client_frame(0x90, stage(selectionReply(latestSelectionId, 1, 'bad'))),
    clientAction.ignored,
  );
  check(
    'selection valid after malformed matching reply',
    e.mux_client_frame(0x90, stage(selectionReply(latestSelectionId, 0, 'selected 漢', 0xdeadbeef))),
    clientAction.selection,
  );
  check('selection getter id', e.mux_selection_id(), latestSelectionId);
  check('selection getter status', e.mux_selection_status(), 0);
  check('selection getter history rows', e.mux_selection_history_rows() >>> 0, 0xdeadbeef);
  check('selection getter len', e.mux_selection_len(), Buffer.byteLength('selected 漢'));
  check('selection getter borrowed text', selectionText().toString('utf8'), 'selected 漢');
  check(
    'selection repeated reply after consume ignored',
    e.mux_client_frame(0x90, stage(selectionReply(latestSelectionId, 0, 'again'))),
    clientAction.ignored,
  );
  check('selection repeated reply clears getter', e.mux_selection_len(), 0);

  const highSelectionId = 0xfedcba98;
  check('high-bit selection request', e.mux_selection_request(highSelectionId, 0, 0, 0, 0), 37);
  check(
    'high-bit selection reply action',
    e.mux_client_frame(0x90, stage(selectionReply(highSelectionId, 0, 'high'))),
    clientAction.selection,
  );
  // WebAssembly exposes u32 results to JS as signed i32. Every browser
  // consumer must normalize this getter with >>> 0 before comparing IDs.
  check('high-bit selection id uses unsigned JS consumer contract', e.mux_selection_id() >>> 0, highSelectionId);

  for (const [name, status] of [['invalid', 1], ['too large', 2], ['unavailable', 3]]) {
    const id = 100 + status;
    check(`selection ${name} request`, e.mux_selection_request(id, 0, 0, 0, 0), 37);
    check(
      `selection ${name} action`,
      e.mux_client_frame(0x90, stage(selectionReply(id, status))),
      clientAction.selection,
    );
    check(`selection ${name} id`, e.mux_selection_id(), id);
    check(`selection ${name} status`, e.mux_selection_status(), status);
    check(`selection ${name} empty text`, e.mux_selection_len(), 0);
  }

  const populateSelection = (id) => {
    check(`selection ${id} request`, e.mux_selection_request(id, 0, 0, 0, 0), 37);
    check(
      `selection ${id} reply`,
      e.mux_client_frame(0x90, stage(selectionReply(id, 0, 'x'))),
      clientAction.selection,
    );
  };
  populateSelection(201);
  check('bell after selection', e.mux_client_frame(0x8f, stage(Buffer.from([1]))), clientAction.bell);
  check('bell clears selection getter', e.mux_selection_len(), 0);
  check('empty selection pointer is input buffer', e.mux_selection_ptr(), e.mux_input_ptr());
  populateSelection(202);
  check('ignored after selection', e.mux_client_frame(0x40, 0), clientAction.ignored);
  check('ignored clears selection getter', e.mux_selection_len(), 0);
  populateSelection(203);
  check('oversize after selection', e.mux_client_frame(0x90, e.mux_input_cap() + 1), -2);
  check('oversize clears selection getter', e.mux_selection_len(), 0);
  populateSelection(204);
  check('wide type after selection', e.mux_client_frame(0x100, 0), clientAction.ignored);
  check('wide type clears selection getter', e.mux_selection_len(), 0);
  populateSelection(205);
  check('valid new request after selection', e.mux_selection_request(206, 1, 2, 3, 4), 37);
  check('valid new request clears selection getter', e.mux_selection_len(), 0);

  populateSelection(207);
  e.mux_input_ptr();
  check('asking for staging pointer clears selection id', e.mux_selection_id(), 0);
  check('asking for staging pointer clears selection status', e.mux_selection_status(), 3);
  check('asking for staging pointer clears selection len', e.mux_selection_len(), 0);
  check('asking for staging pointer resets selection ptr', e.mux_selection_ptr(), inputPtr);

  check('clipboard before staging pointer invalidation', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
  e.mux_input_ptr();
  check('staging pointer clears borrowed clipboard len', e.mux_clipboard_len(), 0);
  check('staging pointer clears borrowed clipboard target', e.mux_clipboard_target(), 0);

  populateSelection(208);
  check('text encode guard after selection', e.mux_text_encode(e.mux_input_cap() + 1), -2);
  check('text encode guard clears selection', e.mux_selection_len(), 0);
  populateSelection(209);
  check('apply frame guard after selection', e.mux_apply_frame(0x95, e.mux_input_cap() + 1), -2);
  check('apply frame guard clears selection', e.mux_selection_len(), 0);
  populateSelection(210);
  check('scroll feed guard after selection', e.mux_scroll_feed(e.mux_input_cap() + 1), -2);
  check('scroll feed guard clears selection', e.mux_selection_len(), 0);

  populateSelection(211);
  check('invalid selection request after result', e.mux_selection_request(212, 0, 65536, 0, 0), -3);
  check('invalid selection request clears exposed id', e.mux_selection_id(), 0);
  check('invalid selection request clears exposed status', e.mux_selection_status(), 3);
  check('invalid selection request clears exposed len', e.mux_selection_len(), 0);
  check('pending selection request before invalid request', e.mux_selection_request(213, 1, 2, 3, 4), 37);
  check('invalid selection request preserves pending correlation', e.mux_selection_request(214, 1, 2, 3, 65536), -3);
  check(
    'matching reply after invalid request is accepted',
    e.mux_client_frame(0x90, stage(selectionReply(213, 0, 'still pending'))),
    clientAction.selection,
  );

  // --- attach payload before any state: quotes (0,0); wall spelling 0x0 ---
  check('attach len', e.mux_attach_payload(0, 0, 0), 20);
  let att = outBytes();
  check('attach cols', att.readUInt16LE(0), 0);
  check('attach rows', att.readUInt16LE(2), 0);
  check('attach seq0', att.readBigUInt64LE(4), 0n);
  check('attach epoch0', att.readBigUInt64LE(12), 0n);

  // --- snapshot: styled text, adoption, full damage ---
  // `hello` bold on palette red, then a plain ` world`: two runs in one row,
  // so the second run's mask says what it CHANGED and nothing else.
  const snap = snapshotPayload(
    { seq: 7, history: 3, cols: 80, rows: 24, epoch: 0xabcdn, cx: 11, cy: 0 },
    [cellRow([
      {
        style: { flags: 1 << 0, fg: (1 << 24) | 1 },
        cells: [...'hello'].map((ch) => ({ text: ch, wide: 0 })),
      },
      { cells: [...' world'].map((ch) => ({ text: ch, wide: 0 })) },
    ])],
  );
  populateSelection(215);
  check('apply snapshot', e.mux_apply_frame(0x95, stageThroughKnownPtr(snap)), 0);
  check('apply snapshot clears borrowed selection', e.mux_selection_len(), 0);
  check('seq adopted', resumeArgs().seq, 7n);
  check('epoch adopted', resumeArgs().epoch, 0xabcdn);
  check('history', e.mux_history_rows(), 3);
  check('snapshot dirties all', e.mux_read_viewport(), 24);
  check('cell h', cell(0, 0).cp, 'h'.codePointAt(0));
  check('cell h bold', cell(0, 0).flags & 1, 1);
  check('cell h fg palette red', cell(0, 0).fg, (1 << 24) | 1);
  check('cell w plain', cell(6, 0).flags & 0xffff, 0);
  check('cell w fg none', cell(6, 0).fg, 0);
  check('cursor x', e.mux_cursor_x(), 11); // where the snapshot's own cursor says

  // --- delta: rows 0 and 2 repainted, damage = their rows + cursor rows ---
  const delta = deltaPayload(
    { seq: 8, history: 4, cx: 2, cy: 2 },
    [
      [0, cellRow([{ style: { flags: 1 << 4 }, cells: [...'yo'].map((ch) => ({ text: ch, wide: 0 })) }])],
      [2, 'row two'],
    ],
  );
  check('apply delta', e.mux_apply_frame(0x96, stage(delta)), 0);
  check('seq advanced', resumeArgs().seq, 8n);
  check('history follows', e.mux_history_rows(), 4);
  const ndirty = e.mux_read_viewport();
  const dirtyRows = [];
  for (let i = 0; i < ndirty; i++) dirtyRows.push(e.mux_dirty_row(i));
  // rows 0 (content+old cursor row 0) and 2 (content+new cursor) — sorted.
  check('delta dirty rows', dirtyRows.join(','), '0,2');
  check('cell yo inverse', cell(0, 0).flags & (1 << 4), 1 << 4);
  check('cell row2', cell(0, 2).cp, 'r'.codePointAt(0));
  check('cursor y', e.mux_cursor_y(), 2);

  // --- attach after state: quotes what we hold; fresh=1 re-quotes zero ---
  e.mux_attach_payload(80, 24, 0);
  att = outBytes();
  // The size really is encoded and not merely left zero — which the wall
  // spelling above, being 0x0, can no longer tell you on its own.
  check('attach cols held', att.readUInt16LE(0), 80);
  check('attach rows held', att.readUInt16LE(2), 24);
  check('attach seq held', att.readBigUInt64LE(4), 8n);
  check('attach epoch held', att.readBigUInt64LE(12), 0xabcdn);
  e.mux_attach_payload(80, 24, 1);
  att = outBytes();
  check('attach fresh seq', att.readBigUInt64LE(4), 0n);

  // --- resync path: header lies about row count ---
  const bad = deltaPayload({ seq: 9, history: 4, cx: 0, cy: 0 }, [[0, 'x']]);
  bad.writeUInt16LE(2, 16); // row_count claims 2
  check('resync', e.mux_apply_frame(0x96, stage(bad)), 1);
  check('resync holds seq', resumeArgs().seq, 8n);

  // --- bad frames ---
  check('not replay frame', e.mux_apply_frame(0x88, 1), -3);
  check('unknown type', e.mux_apply_frame(0x40, 0), -3);
  check('short snapshot', e.mux_apply_frame(0x95, 10), -3);

  // --- grid move via snapshot: readout follows, all dirty ---
  const wide = snapshotPayload(
    { seq: 10, history: 0, cols: 100, rows: 30, epoch: 0xabcdn },
    ['wide'],
  );
  check('apply wide', e.mux_apply_frame(0x95, stage(wide)), 0);
  check('cols follow', e.mux_cols(), 100);
  check('rows follow', e.mux_rows(), 30);
  check('grid move dirties all', e.mux_read_viewport(), 30);
  check('cell after move', cell(0, 0).cp, 'w'.codePointAt(0));

  // --- wide CJK: wide flag + spacer ---
  // A wide glyph, its spacer, then a second wide glyph: the head byte's
  // `wide` field is the only thing that says which is which.
  const cjk = snapshotPayload(
    { seq: 11, history: 0, cols: 100, rows: 30, epoch: 0xabcdn },
    [cellRow([{ cells: [
      { text: '漢', wide: 1 },
      { text: '', wide: 2 },
      { text: '字', wide: 1 },
      { text: '', wide: 2 },
    ] }])],
  );
  e.mux_apply_frame(0x95, stage(cjk));
  e.mux_read_viewport();
  check('cjk cp', cell(0, 0).cp, 0x6f22);
  check('cjk wide', cell(0, 0).flags & (1 << 16), 1 << 16);
  check('cjk spacer', cell(1, 0).flags & (1 << 17), 1 << 17);
  check('cjk second', cell(2, 0).cp, 0x5b57);

  // --- key encoding round-trips two table rows ---
  check('key a', e.mux_key_encode(0, 'a'.codePointAt(0), 0), 1);
  check('key a byte', outBytes().toString('latin1'), 'a');
  check('key ctrl-up', e.mux_key_encode(5, 0, 4), 6);
  check('key ctrl-up seq', outBytes().toString('latin1'), '\x1b[1;5A');
  check('key unknown', e.mux_key_encode(99, 0, 0), -3);

  // --- text vs paste: the markers are separate exports on purpose ---
  // Composed IME text is TYPING and goes out raw; a paste is the same
  // bytes with ONE wrap around the whole of it, however many chunks it
  // takes. Reassembling here is what proves a chunked paste carries
  // exactly one begin and one end.
  populateSelection(216);
  stageThroughKnownPtr(Buffer.from('two\nlines'));
  check('text len', e.mux_text_encode(9), 9);
  check('text encode clears borrowed selection', e.mux_selection_len(), 0);
  check('text bytes: no wrap', outBytes().toString('latin1'), 'two\nlines');

  // Fresh sample: this assertion must not inherit mode state from the
  // client-ABI checks above.
  check('paste section modes on', e.mux_client_frame(0x8d, stage(modesOn)), clientAction.terminalModes);
  check('paste begin len', e.mux_paste_begin(), 6);
  const pasteWire = [outBytes().toString('latin1')];
  for (const chunk of ['two\n', 'lines']) {
    stage(Buffer.from(chunk));
    check(`paste chunk ${chunk.length}`, e.mux_text_encode(chunk.length), chunk.length);
    pasteWire.push(outBytes().toString('latin1'));
  }
  check('paste end len', e.mux_paste_end(), 6);
  pasteWire.push(outBytes().toString('latin1'));
  check('paste wire', pasteWire.join(''), '\x1b[200~two\nlines\x1b[201~');
  // The markers themselves, so a drift in input.zig's spelling is caught
  // here and not by an application quietly re-indenting.
  check('paste begin bytes', pasteWire[0], '\x1b[200~');
  check('paste end bytes', pasteWire[pasteWire.length - 1], '\x1b[201~');
  // The staging cap is the refusal, not a truncation.
  check('text over cap', e.mux_text_encode(e.mux_input_cap() + 1), -2);

  // --- scroll scratch: never touches the live replica ---
  check('scroll start', e.mux_scroll_start(1, 30), 0); // history 0: saturates
  populateSelection(217);
  const historyChunk = scrollChunk(0, ['old history line']);
  stageThroughKnownPtr(historyChunk);
  check('scroll feed', e.mux_scroll_feed(historyChunk.length), 0);
  check('scroll feed clears borrowed selection', e.mux_selection_len(), 0);
  check('scroll read', e.mux_read_scroll_viewport(), 30);
  check('scroll cell', cell(0, 0).cp, 'o'.codePointAt(0));
  check('live untouched: full repaint queued', e.mux_read_viewport(), 30);
  check('live cell back', cell(0, 0).cp, 0x6f22);

  // --- diagnostics: the dump is the referee ---
  e.mux_dump_plain();
  check('dump starts', outBytes().toString('utf8').startsWith('漢字'), true);

  // --- a real-sized snapshot fits the staging buffer ---
  // The staging cap is pinned to the largest selection reply: its nine-byte
  // correlation/status/history prefix plus the protocol's one-MiB text
  // maximum.
  check('input cap', e.mux_input_cap(), 1024 * 1024 + 41);

  // --- the shell's ACTUAL call list, read out of mux.js ---
  // Everything above pins exports this file happens to name. This pins
  // the ones the page names, which is the pair that has to agree: an
  // export renamed on the Zig side is a TypeError in the browser and
  // nowhere else, and the wasm builds fine without it.
  const shell = fs.readFileSync(path.join(__dirname, 'mux.js'), 'utf8');
  const html = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
  await verifyClipboardShell(shell, html);
  await verifySelectionShell(shell, html);
  await verifySettingsShell(shell, html);
  await verifyStatusShell(shell);

  // Mutation fixture for the source checks below. A correct-looking route
  // in either kind of comment must be invisible, while live literal
  // boundaries remain valid after their non-executable contents are masked.
  const lexicalFixture = [
    '// const MSG = { term_modes: 0x8d, term_event: 0x8f };',
    '/* const CLIENT_ACTION = { ignored: 0, terminalModes: 1, clipboard: 2, bell: 3 }; */',
    '/* case MSG.term_modes:',
    'case MSG.term_event: { const action = this.core.mux_client_frame(type, payload.length); }',
    'case MSG.pty_mode: */',
    '// sendPaste(text) { try { this.sendText(text); } finally {} }',
    '/* onClipboardEffect() {} */',
    '// this.core.mux_comment_decoy(1);',
    'const stringLiteral = "https://host/*literal*/";',
    'const templateLiteral = `// literal /* template */`;',
    "const regexLiteral = /[\"']https?:\\/\\/host/; // remove this tail",
  ].join('\n');
  const executableFixture = executableJsTokens(lexicalFixture);
  check('source masker rejects line-comment declaration decoy', /\bconst MSG\b/.test(executableFixture), false);
  check('source masker rejects block-comment declaration decoy', /\bconst CLIENT_ACTION\b/.test(executableFixture), false);
  check('source masker rejects commented semantic-route decoy', /\bcase MSG\.term_modes\b/.test(executableFixture), false);
  check('source masker rejects commented sendPaste decoy', /\bsendPaste\s*\(/.test(executableFixture), false);
  check('source masker rejects commented placeholder decoy', /\bonClipboardEffect\s*\(/.test(executableFixture), false);
  check('WASM-call scan rejects commented decoy', wasmCalls(lexicalFixture).includes('mux_comment_decoy'), false);
  check('source masker removes a regex trailing comment', /remove this tail/.test(executableFixture), false);
  check('source masker excludes string literal contents', executableFixture.includes('https://host'), false);
  check('source masker excludes template raw contents', executableFixture.includes('literal /* template */'), false);
  check('source masker excludes regex body contents', executableFixture.includes('https?:\\/\\/host'), false);
  let lexicalFixtureCompiles = true;
  try { new vm.Script(executableFixture); } catch (_) { lexicalFixtureCompiles = false; }
  check('source masker preserves literal syntactic boundaries', lexicalFixtureCompiles, true);

  const completeDecoy = [
    'const MSG = { term_modes: 0x8d, term_event: 0x8f, selection_reply: 0x90 };',
    'const CLIENT_ACTION = { ignored: 0, terminalModes: 1, clipboard: 2, bell: 3, selection: 4 };',
    'case MSG.term_modes: case MSG.term_event: case MSG.selection_reply: { if (!this.stage(payload)) return;',
    'const action = this.core.mux_client_frame(type, payload.length);',
    'if (action === CLIENT_ACTION.clipboard) this.onClipboardEffect();',
    'else if (action === CLIENT_ACTION.selection) this.onSelectionReply(); return; }',
    'sendPaste(text) { if (this.core.mux_paste_begin() > 0) this.sendText(text);',
    'try { this.sendText(text); } finally { this.core.mux_paste_end(); } }',
    'onClipboardEffect() {} this.core.mux_client_frame(type, payload.length);',
  ].join(' ');
  const literalDecoys = [
    `const stringDecoy = '${completeDecoy}';`,
    `const templateDecoy = \`${completeDecoy}\`;`,
    `const regexDecoy = /${completeDecoy}/;`,
  ].join('\n');
  let literalDecoysCompile = true;
  try { new vm.Script(literalDecoys); } catch (_) { literalDecoysCompile = false; }
  check('complete literal decoy attack is valid JavaScript', literalDecoysCompile, true);
  const executableLiteralDecoys = executableJsTokens(literalDecoys);
  check('literal decoys add no MSG declaration', (executableLiteralDecoys.match(/\b(?:const|let|var)\s+MSG\b/g) ?? []).length, 0);
  check('literal decoys add no CLIENT_ACTION declaration', (executableLiteralDecoys.match(/\b(?:const|let|var)\s+CLIENT_ACTION\b/g) ?? []).length, 0);
  check(
    'literal decoys add no semantic route body',
    balancedBodiesAfter(executableLiteralDecoys, /case\s+MSG\.term_modes\s*:\s*case\s+MSG\.term_event\s*:\s*case\s+MSG\.selection_reply\s*:\s*\{/g).length,
    0,
  );
  check(
    'literal decoys add no paste method body',
    balancedBodiesAfter(executableLiteralDecoys, /\bsendPaste\s*\([^)]*\)\s*\{/g).length,
    0,
  );
  check(
    'literal decoys add no placeholder body',
    balancedBodiesAfter(executableLiteralDecoys, /\bonClipboardEffect\s*\([^)]*\)\s*\{/g).length,
    0,
  );
  check('WASM-call scan rejects all literal decoys', wasmCalls(literalDecoys).includes('mux_client_frame'), false);

  const templateExpressionFixture = [
    'const nested = `raw mux_raw_decoy ${',
    'this.core.mux_live_call("mux_string_decoy" /* mux_comment_decoy */)',
    '}`;',
    'const flagged = /mux_regex_decoy/gim;',
  ].join(' ');
  const executableTemplateExpression = executableJsTokens(templateExpressionFixture);
  check('template raw text is not searchable', /mux_raw_decoy/.test(executableTemplateExpression), false);
  check('template expression code remains live', /\.mux_live_call\s*\(/.test(executableTemplateExpression), true);
  check('nested string content is not searchable', /mux_string_decoy/.test(executableTemplateExpression), false);
  check('nested comment content is not searchable', /mux_comment_decoy/.test(executableTemplateExpression), false);
  check('regex body is not searchable', /mux_regex_decoy/.test(executableTemplateExpression), false);
  check('regex flags are not searchable', /\bgim\b/.test(executableTemplateExpression), false);

  const slashFixture = [
    'let ok = true, x = "/", left = 6, right = 3;',
    'if (ok) /[//]/.test(x);',
    'if (ok) {} /[/*]/.test(x);',
    'const quotient = left / right;',
    'const objectQuotient = {} / 2;',
  ].join('\n');
  let rawSlashFixtureCompiles = true;
  try { new vm.Script(slashFixture); } catch (_) { rawSlashFixtureCompiles = false; }
  check('regex/control and division attack is valid JavaScript', rawSlashFixtureCompiles, true);
  const executableSlashes = executableJsTokens(slashFixture);
  let slashFixtureCompiles = true;
  try { new vm.Script(executableSlashes); } catch (_) { slashFixtureCompiles = false; }
  check('masked regex/control and division fixture remains valid JS', slashFixtureCompiles, true);
  check('regex after control and block remains live', (executableSlashes.match(/\.test\s*\(/g) ?? []).length, 2);
  check('division after a value remains live', /left\s*\/\s*right/.test(executableSlashes), true);
  check('division after an object expression remains live', /\{\}\s*\/\s*2/.test(executableSlashes), true);

  const neighborFixture = executableJsTokens([
    'case MSG.term_modes:',
    'case MSG.term_event:',
    'case MSG.selection_reply: { if (ready) { route(); } }',
    'case MSG.unrelated: { return; }',
    'case MSG.pty_mode: return;',
    'sendPaste(text) { try { if (text) { this.sendText(text); } } finally { finish(); } }',
    'unrelatedMethod() { return 1; }',
    'sendResizeIfDiffers() {}',
  ].join('\n'));
  check(
    'semantic extraction ignores a later unrelated case',
    balancedBodiesAfter(neighborFixture, /case\s+MSG\.term_modes\s*:\s*case\s+MSG\.term_event\s*:\s*case\s+MSG\.selection_reply\s*:\s*\{/g).length,
    1,
  );
  check(
    'paste extraction ignores a later unrelated method',
    balancedBodiesAfter(neighborFixture, /\bsendPaste\s*\(\s*text\s*\)\s*\{/g).length,
    1,
  );

  const executableShell = executableJsTokens(shell);
  let executableShellCompiles = true;
  try { new vm.Script(executableShell); } catch (_) { executableShellCompiles = false; }
  check('real shell executable-token representation remains valid JS', executableShellCompiles, true);

  // The browser must route sampled terminal state and host effects through
  // the same semantic core as the CLI. Pin the real page source here: this
  // verifier otherwise exercises only the WASM half of that handshake.
  const msgBindings = executableShell.match(/\b(?:const|let|var)\s+MSG\b/g) ?? [];
  const msgObjects = balancedBodiesAfter(executableShell, /\bconst MSG\s*=\s*\{/g);
  check('shell has exactly one live MSG declaration', msgBindings.length, 1);
  check('shell MSG declaration has the expected object shape', msgObjects.length, 1);
  const msgDecl = msgObjects.length === 1 ? (msgObjects[0].body ?? '') : '';
  check('shell has one term_modes property', (msgDecl.match(/\bterm_modes\s*:/g) ?? []).length, 1);
  check('shell has one term_event property', (msgDecl.match(/\bterm_event\s*:/g) ?? []).length, 1);
  check('shell has one selection_req property', (msgDecl.match(/\bselection_req\s*:/g) ?? []).length, 1);
  check('shell has one selection_reply property', (msgDecl.match(/\bselection_reply\s*:/g) ?? []).length, 1);
  check('shell declares term_modes wire code', /\bterm_modes\s*:\s*0x8d\b/.test(msgDecl), true);
  check('shell declares term_event wire code', /\bterm_event\s*:\s*0x8f\b/.test(msgDecl), true);
  check('shell declares selection wire codes', /\bselection_req\s*:\s*0x0b\b/.test(msgDecl)
    && /\bselection_reply\s*:\s*0x90\b/.test(msgDecl), true);

  const actionBindings = executableShell.match(/\b(?:const|let|var)\s+CLIENT_ACTION\b/g) ?? [];
  const actionObjects = balancedBodiesAfter(executableShell, /\bconst CLIENT_ACTION\s*=\s*\{/g);
  check('shell has exactly one live CLIENT_ACTION declaration', actionBindings.length, 1);
  check('shell CLIENT_ACTION declaration has the expected object shape', actionObjects.length, 1);
  const actionDecl = actionObjects.length === 1 ? (actionObjects[0].body ?? '') : '';
  for (const name of ['ignored', 'terminalModes', 'clipboard', 'bell', 'selection']) {
    check(`shell has one ${name} client action`, (actionDecl.match(new RegExp(`\\b${name}\\s*:`, 'g')) ?? []).length, 1);
  }
  check(
    'shell pins shared client action ABI',
    /\bignored\s*:\s*0\b/.test(actionDecl)
      && /\bterminalModes\s*:\s*1\b/.test(actionDecl)
      && /\bclipboard\s*:\s*2\b/.test(actionDecl)
      && /\bbell\s*:\s*3\b/.test(actionDecl)
      && /\bselection\s*:\s*4\b/.test(actionDecl),
    true,
  );

  const semanticRoutes = balancedBodiesAfter(
    executableShell,
    /case\s+MSG\.term_modes\s*:\s*case\s+MSG\.term_event\s*:\s*case\s+MSG\.selection_reply\s*:\s*\{/g,
  );
  check('shell has one live term_modes case', (executableShell.match(/case\s+MSG\.term_modes\s*:/g) ?? []).length, 1);
  check('shell has one live term_event case', (executableShell.match(/case\s+MSG\.term_event\s*:/g) ?? []).length, 1);
  check('shell has one live selection_reply case', (executableShell.match(/case\s+MSG\.selection_reply\s*:/g) ?? []).length, 1);
  check('shell shares exactly one semantic frame route', semanticRoutes.length, 1);
  const semanticCases = semanticRoutes.length === 1 ? (semanticRoutes[0].body ?? '') : '';
  check('shell stages a semantic payload exactly once', (semanticCases.match(/this\.stage\(payload\)/g) ?? []).length, 1);
  check(
    'shell stages semantic payload before WASM',
    /if\s*\(\s*!this\.stage\(payload\)\s*\)\s*return\s*;/.test(semanticCases),
    true,
  );
  check(
    'shell passes semantic type and length to WASM',
    /(?:const|let)\s+action\s*=\s*this\.core\.mux_client_frame\(type,\s*payload\.length\)\s*;/.test(semanticCases),
    true,
  );
  check('shell calls the semantic WASM entry exactly once', (semanticCases.match(/\.mux_client_frame\s*\(/g) ?? []).length, 1);
  check(
    'shell dispatches clipboard and selection semantic actions',
    /if\s*\(\s*action\s*===\s*CLIENT_ACTION\.clipboard\s*\)\s*this\.onClipboardEffect\(\)\s*;/.test(semanticCases)
      && /else\s+if\s*\(\s*action\s*===\s*CLIENT_ACTION\.selection\s*\)\s*this\.onSelectionReply\(\)\s*;/.test(semanticCases)
      && (semanticCases.match(/this\.onClipboardEffect\(\)/g) ?? []).length === 1
      && (semanticCases.match(/this\.onSelectionReply\(\)/g) ?? []).length === 1,
    true,
  );
  check('shell leaves semantic payload parsing to WASM', /DataView|TextDecoder|payload\s*\[/.test(semanticCases), false);
  check(
    'shell has no duplicate bracketed-paste state',
    /this\.(?:bracketedPaste|bracketed_paste)\s*=/.test(executableShell),
    false,
  );
  const clipboardMethods = balancedBodiesAfter(executableShell, /\bonClipboardEffect\s*\([^)]*\)\s*\{/g);
  const emptyClipboardMethods = clipboardMethods.filter((method) => method.body?.trim() === '');
  check('shell has exactly one live clipboard effect method', clipboardMethods.length, 1);
  check('shell clipboard effect is implemented, not a placeholder', emptyClipboardMethods.length, 0);

  const sendPasteMethods = balancedBodiesAfter(executableShell, /\bsendPaste\s*\([^)]*\)\s*\{/g);
  const sendPasteBodies = balancedBodiesAfter(executableShell, /\bsendPaste\s*\(\s*text\s*\)\s*\{/g);
  check('shell has exactly one live sendPaste method', sendPasteMethods.length, 1);
  check('shell sendPaste has the expected text contract', sendPasteBodies.length, 1);
  const sendPaste = sendPasteBodies.length === 1 ? (sendPasteBodies[0].body ?? '') : '';
  check('shell paste has one begin call', (sendPaste.match(/mux_paste_begin\(\)/g) ?? []).length, 1);
  check('shell paste has one end call', (sendPaste.match(/mux_paste_end\(\)/g) ?? []).length, 1);
  check(
    'shell paste wraps the whole sendText with optional markers',
    /if\s*\(this\.core\.mux_paste_begin\(\)\s*>\s*0\)[\s\S]*?try\s*\{\s*this\.sendText\(text\)\s*;\s*\}\s*finally\s*\{\s*if\s*\(this\.core\.mux_paste_end\(\)\s*>\s*0\)/.test(sendPaste),
    true,
  );

  const called = wasmCalls(shell);
  check('shell calls something', called.length > 0, true);
  const absent = called.filter((n) => typeof e[n] !== 'function');
  check(`shell calls only real exports (${called.length} of them)`, absent.join(','), '');

  // --- the SHAPE mux.js depends on ---
  // WebAssembly.instantiate returns {module, instance} for bytes but the
  // bare Instance for an already-compiled Module. mux.js compiles first
  // (compileStreaming) and so takes the second shape; this file passes
  // bytes and takes the first. Destructuring the wrong one is silently
  // undefined, which is precisely how the page shipped unable to start
  // at all — every export above passing, and nothing running. Pin the
  // shape the shell relies on, since no other assertion here can see it.
  const mod = await WebAssembly.compile(bin);
  const fromModule = await WebAssembly.instantiate(mod, {});
  check('instantiate(Module) is an Instance', fromModule instanceof WebAssembly.Instance, true);
  check('instantiate(Module) has no .instance to destructure', fromModule.instance, undefined);
  check('instantiate(Module).exports is the ABI', typeof fromModule.exports.mux_init, 'function');
  const fromBytes = await WebAssembly.instantiate(bin, {});
  check('instantiate(bytes) is the OTHER shape', fromBytes.instance instanceof WebAssembly.Instance, true);

  // --- deinit / re-init ---
  check('clipboard before lifecycle reset', e.mux_client_frame(0x8f, stage(clipboard)), clientAction.clipboard);
  check('clipboard populated before lifecycle reset', e.mux_clipboard_len(), 4);
  check('modes populated before lifecycle reset', e.mux_bracketed_paste(), 1);
  check('selection before lifecycle reset request', e.mux_selection_request(301, 0, 0, 0, 0), 37);
  check('selection before lifecycle reset reply', e.mux_client_frame(0x90, stage(selectionReply(301, 0, 'reset'))), clientAction.selection);
  check('selection populated before lifecycle reset', e.mux_selection_len(), 5);
  e.mux_deinit();
  check('apply after deinit', e.mux_apply_frame(0x95, 0), -1);
  check('client frame after deinit', e.mux_client_frame(0x8d, 0), -1);
  check('selection id after deinit', e.mux_selection_id(), 0);
  check('selection status after deinit', e.mux_selection_status(), 3);
  check('selection len after deinit', e.mux_selection_len(), 0);
  check('selection ptr after deinit is input buffer', e.mux_selection_ptr(), e.mux_input_ptr());
  check('selection request after deinit', e.mux_selection_request(302, 0, 0, 0, 0), -1);
  check('selection request error clears output', e.mux_output_len(), 0);
  check('re-init', e.mux_init(80, 24), 0);
  check('re-init resets bracketed paste', e.mux_bracketed_paste(), 0);
  check('re-init resets clipboard', e.mux_clipboard_len(), 0);
  check('deinit and re-init reset selection id', e.mux_selection_id(), 0);
  check('deinit and re-init reset selection status', e.mux_selection_status(), 3);
  check('deinit and re-init reset selection len', e.mux_selection_len(), 0);
  e.mux_deinit();

  console.log(`verify: ${passed} passed, ${failed} failed`);
  process.exit(failed ? 1 : 0);
}

main().catch((err) => { console.error(err); process.exit(1); });