view mrjunejune/test/theme_and_webp_test.js @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents e02e2036ef84
children
line wrap: on
line source

const assert = require('node:assert/strict');
const fs = require('node:fs');
const net = require('node:net');
const os = require('node:os');
const path = require('node:path');
const { spawn } = require('node:child_process');

const RUNFILES = process.env.JS_BINARY__RUNFILES;
const WORKSPACE = process.env.JS_BINARY__WORKSPACE;
let port;
let baseUrl;
const playwrightPath = path.join(
  RUNFILES,
  WORKSPACE,
  'hg-web/e2e/node_modules/playwright-core',
);
const { chromium } = require(playwrightPath);
const browserSuite = process.env.MJJ_BROWSER_SUITE || 'all';
const runsSuite = name => browserSuite === 'all' || browserSuite === name;

function stopProcess(child) {
  if (!child || child.exitCode !== null) return Promise.resolve();
  child.kill('SIGTERM');
  return new Promise(resolve => {
    const timer = setTimeout(() => {
      if (child.exitCode === null) child.kill('SIGKILL');
    }, 3000);
    child.once('exit', () => {
      clearTimeout(timer);
      resolve();
    });
  });
}

async function waitForServer(child, logs) {
  const deadline = Date.now() + 15000;
  while (Date.now() < deadline) {
    if (child.exitCode !== null) {
      throw new Error(`Server exited with ${child.exitCode}\n${logs.join('')}`);
    }
    try {
      const response = await fetch(baseUrl);
      if (response.ok) return;
    } catch {
      // Keep waiting for startup.
    }
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  throw new Error(`Server startup timed out\n${logs.join('')}`);
}

function listFiles(root) {
  const files = [];
  for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
    const absolute = path.join(root, entry.name);
    if (entry.isDirectory()) files.push(...listFiles(absolute));
    else files.push(absolute);
  }
  return files;
}

function primitiveOwnershipViolations(source) {
  const violations = [];
  const stack = [];
  const voidElements = new Set([
    'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',
    'meta', 'param', 'source', 'track', 'wbr',
  ]);
  const ownerMap = {
    button: new Set(['zen-button']),
    select: new Set(['zen-field', 'zen-native-select', 'zen-select']),
    textarea: new Set(['zen-field', 'zen-textarea']),
  };

  for (const token of source.matchAll(
    /<!--[\s\S]*?-->|<\/?([a-zA-Z][\w-]*)\b([^>]*)>/g,
  )) {
    if (!token[1]) continue;
    const name = token[1].toLowerCase();
    if (token[0].startsWith('</')) {
      while (stack.length) {
        if (stack.pop() === name) break;
      }
      continue;
    }

    let owners = ownerMap[name];
    if (name === 'input') {
      const type = /\btype\s*=\s*["']?([^"'\s>]+)/i.exec(token[2])?.[1]
        ?.toLowerCase() || 'text';
      if (type === 'checkbox') {
        owners = new Set(['zen-checkbox', 'zen-field', 'zen-switch']);
      } else if (type === 'radio') {
        owners = new Set(['zen-field', 'zen-radio-group']);
      } else if (type === 'range') {
        owners = new Set(['zen-field', 'zen-slider']);
      } else if (type === 'date') {
        owners = new Set(['zen-calendar', 'zen-date-picker', 'zen-field']);
      } else {
        owners = new Set([
          'zen-combobox',
          'zen-command',
          'zen-field',
          'zen-input',
          'zen-input-group',
          'zen-input-otp',
        ]);
      }
    }
    if (owners) {
      const matchingOwners = stack.filter(ancestor => owners.has(ancestor));
      if (matchingOwners.length !== 1) {
        violations.push({
          control: name,
          owners: matchingOwners,
          source: token[0],
        });
      }
    }

    if (!voidElements.has(name) && !token[0].endsWith('/>')) {
      stack.push(name);
    }
  }
  return violations;
}

async function sampleTheme(browser, theme) {
  const context = await browser.newContext({ colorScheme: 'dark' });
  await context.addInitScript(value => {
    localStorage.setItem('theme-preference', value);
  }, theme);
  const page = await context.newPage();
  const errors = [];
  await page.route('**/api/conversations**', async route => {
    const request = route.request();
    const url = new URL(request.url());
    if (request.method() === 'POST' &&
        url.pathname === '/api/conversations') {
      await route.fulfill({
        status: 201,
        contentType: 'application/json',
        body: JSON.stringify({
          id: '11111111-1111-4111-8111-111111111111',
        }),
      });
      return;
    }
    if (request.method() === 'POST' && url.pathname.endsWith('/turns')) {
      await route.fulfill({
        status: 200,
        contentType: 'text/event-stream',
        body: [
          'event: turn.accepted',
          'data: {"request_id":"request-1"}',
          '',
          'event: assistant.delta',
          'data: {"request_id":"request-1","delta":"Start with "}',
          '',
          'event: assistant.delta',
          'data: {"request_id":"request-1","delta":"the tools."}',
          '',
          'event: assistant.completed',
          'data: {"request_id":"request-1","content":"Start with the tools."}',
          '',
          'event: turn.done',
          'data: {"request_id":"request-1","failed":false,"aborted":false}',
          '',
          '',
        ].join('\n'),
      });
      return;
    }
    await route.fulfill({
      status: 404,
      contentType: 'application/json',
      body: JSON.stringify({ error: { message: 'Not found' } }),
    });
  });
  page.on('pageerror', error => errors.push(`pageerror: ${error.message}`));
  page.on('console', message => {
    if (message.type() === 'error' &&
        !message.text().startsWith('Failed to load resource:')) {
      errors.push(`console: ${message.text()}`);
    }
  });
  page.on('requestfailed', request => {
    if (request.url().startsWith('blob:') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    if (request.method() === 'DELETE' &&
        request.url().includes('/api/download/') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    if (request.method() === 'POST' &&
        request.url().endsWith('/api/latex/render') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
  });
  page.on('response', response => {
    if (response.status() === 404 &&
        response.url().endsWith(
          '/api/conversations/00000000-0000-4000-8000-000000000000'
        )) {
      return;
    }
    if (response.status() >= 400) {
      errors.push(`response: ${response.status()} ${response.url()}`);
    }
  });

  await page.goto(baseUrl, { waitUntil: 'networkidle' });
  await page.waitForFunction(() => {
    const canvas = document.querySelector('#background');
    if (!canvas) return false;
    const data = canvas.getContext('2d').getImageData(
      0,
      0,
      canvas.width,
      canvas.height,
    ).data;
    for (let index = 3; index < data.length; index += 4) {
      if (data[index] >= 20) return true;
    }
    return false;
  });

  const sample = await page.evaluate(() => {
    const canvas = document.querySelector('#background');
    const data = canvas.getContext('2d').getImageData(
      0,
      0,
      canvas.width,
      canvas.height,
    ).data;
    let count = 0;
    let luminance = 0;
    for (let index = 0; index < data.length; index += 4) {
      if (data[index + 3] < 20) continue;
      count++;
      luminance +=
        data[index] * 0.2126 +
        data[index + 1] * 0.7152 +
        data[index + 2] * 0.0722;
    }
    const channels = value => value.match(/\d+(?:\.\d+)?/g)
      .slice(0, 3)
      .map(Number);
    const colorLuminance = value => {
      const converted = channels(value).map(channel => {
        const normalized = channel / 255;
        return normalized <= 0.04045
          ? normalized / 12.92
          : ((normalized + 0.055) / 1.055) ** 2.4;
      });
      return converted[0] * 0.2126 +
        converted[1] * 0.7152 +
        converted[2] * 0.0722;
    };
    const bodyStyle = getComputedStyle(document.body);
    const themeButtonStyle = getComputedStyle(
      document.querySelector('#themeToggle'),
    );
    const foreground = colorLuminance(bodyStyle.color);
    const contrastProbe = document.createElement('span');
    contrastProbe.style.background = 'var(--zen-color-surface)';
    document.body.append(contrastProbe);
    const background = colorLuminance(
      getComputedStyle(contrastProbe).backgroundColor,
    );
    contrastProbe.remove();
    return {
      backgroundRepeat: bodyStyle.backgroundRepeat,
      backgroundSize: bodyStyle.backgroundSize,
      bodyCoversViewport: document.body.getBoundingClientRect().height >=
        innerHeight,
      componentReady: Boolean(customElements.get('zen-button')),
      count,
      fontFamily: getComputedStyle(document.body).fontFamily,
      headerPaws: document.querySelectorAll(
        'header [data-zen-link-decoration]',
      ).length,
      luminance: count ? luminance / count : 0,
      textContrast: (Math.max(foreground, background) + 0.05) /
        (Math.min(foreground, background) + 0.05),
      rootTheme: document.documentElement.dataset.zenTheme || 'auto',
      enhancedLinks: document.querySelectorAll(
        'zen-link[effect="paw"] > a > zen-icon[name="paw"]',
      ).length,
      links: document.querySelectorAll('a').length,
      pawLinks: [...document.querySelectorAll('a')].filter(link =>
        !link.closest('zen-button') &&
        link.dataset.zenLinkEffect !== 'none'
      ).length,
      mainBackground: getComputedStyle(
        document.querySelector('main'),
      ).backgroundColor,
      themeButtonBackground: themeButtonStyle.backgroundColor,
      themeButtonBorder: themeButtonStyle.borderTopWidth,
      themeButtonShadow: themeButtonStyle.boxShadow,
      themeLabel: document.querySelector('#themeName')?.textContent,
    };
  });

  if (errors.length) throw new Error(`${theme}\n${errors.join('\n')}`);
  await context.close();
  return sample;
}

async function testThemeCycle(browser) {
  const context = await browser.newContext({ colorScheme: 'light' });
  const page = await context.newPage();
  await page.goto(baseUrl, { waitUntil: 'networkidle' });
  const expected = ['paper', 'ink', 'playful', 'auto'];
  for (const theme of expected) {
    await page.locator('#themeToggle').click();
    assert.equal(
      await page.evaluate(() =>
        document.documentElement.dataset.zenTheme || 'auto'
      ),
      theme,
    );
    assert.equal(
      await page.evaluate(() => localStorage.getItem('theme-preference')),
      theme,
    );
  }
  await context.close();
}

async function testPlainField(browser) {
  const page = await browser.newPage();
  await page.goto(`${baseUrl}/tools/markdown_to_html`, {
    waitUntil: 'networkidle',
  });
  await page.waitForFunction(() => customElements.get('zen-field'));
  const state = await page.locator('zen-field[appearance="plain"]').evaluate(
    field => {
      const label = field.querySelector('label');
      const textarea = field.querySelector('textarea');
      const probe = document.createElement('span');
      probe.style.border = '1px solid var(--accent)';
      document.body.append(probe);
      const accent = getComputedStyle(probe).borderColor;
      probe.remove();
      textarea.focus();
      return {
        borderColor: getComputedStyle(textarea).borderColor,
        display: getComputedStyle(field).display,
        labelFor: label.htmlFor,
        accent,
        textareaId: textarea.id,
      };
    },
  );
  assert.deepEqual(state, {
    accent: state.borderColor,
    borderColor: state.borderColor,
    display: 'contents',
    labelFor: 'input',
    textareaId: 'input',
  });
  await page.close();
}

async function testButtonScale(browser) {
  const page = await browser.newPage();
  for (const route of [
    '/talk',
    '/notes/login',
    '/tools/hls_player',
    '/tools/latex_editor',
    '/tools/markdown_to_html',
    '/tools/file_converter',
    '/offline.html',
  ]) {
    await page.goto(`${baseUrl}${route}`, { waitUntil: 'networkidle' });
    await page.waitForFunction(() => customElements.get('zen-button'));
    const controls = await page.locator(
      'zen-button[size] > :is(button, a):not(#themeToggle)',
    ).evaluateAll(elements => elements.map(control => {
      const host = control.parentElement;
      const style = getComputedStyle(control);
      const hostStyle = getComputedStyle(host);
      return {
        height: control.getBoundingClientRect().height,
        minimum: Number.parseFloat(
          hostStyle.getPropertyValue('--zenbu-control-height'),
        ) * Number.parseFloat(getComputedStyle(document.documentElement).fontSize),
        paddingBlock: style.paddingBlockStart,
        paddingVariable: Number.parseFloat(
          hostStyle.getPropertyValue('--zenbu-control-padding-block'),
        ) * Number.parseFloat(getComputedStyle(document.documentElement).fontSize),
        size: host.getAttribute('size'),
      };
    }));
    assert.ok(controls.length > 0, route);
    for (const control of controls) {
      assert.match(control.size, /^(?:xs|sm|md|lg|xl)$/);
      assert.ok(control.height >= control.minimum - 1, JSON.stringify(control));
      assert.ok(
        Math.abs(Number.parseFloat(control.paddingBlock) -
          control.paddingVariable) < 1,
        JSON.stringify(control),
      );
    }
  }
  await page.close();
}

async function testDynamicButtonOwnership(browser) {
  const page = await browser.newPage();
  await page.goto(baseUrl, { waitUntil: 'networkidle' });
  await page.evaluate(() => {
    window.dispatchEvent(new Event('beforeinstallprompt', {
      cancelable: true,
    }));
  });
  const state = await page.locator('#pwa-install-btn').evaluate(button => ({
    ownerCount: button.closest('zen-button') ? 1 : 0,
    appearance: button.closest('zen-button')?.getAttribute('appearance'),
    size: button.closest('zen-button')?.getAttribute('size'),
  }));
  assert.deepEqual(state, {
    appearance: 'plain',
    ownerCount: 1,
    size: 'md',
  });
  await page.close();
}

async function testResumePrint(browser) {
  const page = await browser.newPage();
  await page.emulateMedia({ media: 'print' });
  await page.goto(`${baseUrl}/resume`, { waitUntil: 'networkidle' });
  assert.equal(
    await page.locator('.info > a[href="/public/resume.pdf"]').isVisible(),
    false,
  );
  await page.close();
}

async function testComposerLab(browser) {
  const context = await browser.newContext({
    serviceWorkers: 'block',
    viewport: { width: 390, height: 844 },
  });
  const page = await context.newPage();
  await page.goto(`${baseUrl}/public/composer-lab.html`, {
    waitUntil: 'networkidle',
  });
  await page.waitForFunction(() =>
    customElements.get('mjj-composer') &&
    customElements.get('zen-field') &&
    customElements.get('zen-button')
  );

  const compact = page.locator('mjj-composer[compact]');
  const textarea = compact.locator('textarea');
  const submit = compact.locator('button[type="submit"]');
  await textarea.fill('Composer lab message');
  await textarea.press('Enter');
  await page.waitForFunction(() =>
    [...document.querySelectorAll('[data-output]')]
      .some(output => output.textContent.includes('Composer lab message'))
  );

  const geometry = await compact.evaluate(element => {
    const field = element.querySelector('zen-field');
    const button = element.querySelector('zen-button');
    const nativeButton = button.querySelector('button');
    const buttonBox = nativeButton.getBoundingClientRect();
    return {
      compactButton: Math.abs(buttonBox.width - buttonBox.height) < 1,
      fieldStyled: !field.hasAttribute('appearance'),
      oneButtonOwner: nativeButton.closest('zen-button') === button,
      textareaOwned: element.querySelector('textarea').closest('zen-field') === field,
    };
  });
  assert.deepEqual(geometry, {
    compactButton: true,
    fieldStyled: true,
    oneButtonOwner: true,
    textareaOwned: true,
  });
  assert.equal(await submit.getAttribute('aria-label'), 'Send message');
  await context.close();
}

async function testModalSandbox(browser) {
  const context = await browser.newContext({
    serviceWorkers: 'block',
    viewport: { width: 1280, height: 900 },
  });
  const page = await context.newPage();
  const errors = [];
  page.on('pageerror', error => errors.push(error.message));
  page.on('console', message => {
    if (message.type() === 'error') errors.push(message.text());
  });
  await page.goto(`${baseUrl}/public/component-sandbox.html`, {
    waitUntil: 'networkidle',
  });
  await page.waitForFunction(() =>
    customElements.get('mjj-content-modal') &&
    customElements.get('mjj-window-modal') &&
    [...document.querySelectorAll('mjj-content-modal, mjj-window-modal')]
      .every(modal => modal.hasAttribute('data-ready'))
  );
  await page.evaluate(() => document.fonts.ready);
  const modalCss = await (
    await fetch(`${baseUrl}/public/mjj-modal.css`, { cache: 'no-store' })
  ).text();
  assert.match(modalCss, /clip-path:\s*inset\(49% 45%\)/);
  assert.match(modalCss, /clip-path:\s*inset\(49% 0\)/);
  assert.match(modalCss, /scrollbar-gutter:\s*stable/);

  assert.equal(
    await page.locator('mjj-content-modal[data-invalid], mjj-window-modal[data-invalid]')
      .count(),
    0,
  );

  await page.getByRole('button', { name: 'Open article' }).click();
  const contentDialog = page.locator('mjj-content-modal dialog');
  await contentDialog.waitFor({ state: 'visible' });
  await contentDialog.evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  const contentMetrics = await contentDialog.evaluate(dialog => {
    const body = dialog.querySelector(':scope > [data-modal-body]');
    const children = [...dialog.children].map(child => {
      if (child.hasAttribute('data-modal-header')) return 'header';
      if (child.hasAttribute('data-modal-body')) return 'body';
      if (child.hasAttribute('data-modal-footer')) return 'footer';
      return child.localName;
    });
    const style = getComputedStyle(dialog);
    const bodyStyle = getComputedStyle(body);
    return {
      bodyScrollable: ['auto', 'scroll'].includes(bodyStyle.overflowY),
      borderRadius: style.borderRadius,
      children,
      fontFamily: style.fontFamily,
      labelled: dialog.getAttribute('aria-labelledby') ===
        dialog.querySelector('[data-modal-title]').id,
      overflowing: body.scrollHeight > body.clientHeight,
      shadow: style.boxShadow,
      scrollPrimitive: body.localName,
      scrollbarColor: bodyStyle.scrollbarColor,
      scrollbarGutter: bodyStyle.scrollbarGutter,
      width: dialog.getBoundingClientRect().width,
    };
  });
  assert.deepEqual(contentMetrics.children, ['header', 'body', 'footer']);
  assert.equal(contentMetrics.bodyScrollable, true);
  assert.equal(contentMetrics.borderRadius, '0px');
  assert.equal(contentMetrics.labelled, true);
  assert.equal(contentMetrics.fontFamily, '"Pixel Mplus"');
  assert.equal(contentMetrics.overflowing, true);
  assert.equal(contentMetrics.scrollPrimitive, 'zen-scroll-area');
  assert.notEqual(contentMetrics.scrollbarColor, 'auto');
  assert.match(contentMetrics.scrollbarGutter, /stable/);
  assert.notEqual(contentMetrics.shadow, 'none');
  assert.ok(contentMetrics.width <= 864);
  await page.getByRole('button', { name: 'Close article' }).click();
  await contentDialog.waitFor({ state: 'hidden' });

  await page.getByRole('button', { name: 'Open workspace' }).click();
  const windowDialog = page.locator('mjj-window-modal dialog');
  await windowDialog.waitFor({ state: 'visible' });
  await windowDialog.evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  const windowMetrics = await windowDialog.evaluate(dialog => {
    const body = dialog.querySelector(':scope > [data-modal-body]');
    const bodyStyle = getComputedStyle(body);
    const primary = dialog.querySelector('[data-modal-pane="primary"]');
    return {
      bodyColumns: bodyStyle.gridTemplateColumns,
      bodyScrollable: ['auto', 'scroll'].includes(bodyStyle.overflowY),
      borderRadius: getComputedStyle(dialog).borderRadius,
      hasStrictRegions:
        Boolean(dialog.querySelector(':scope > [data-modal-header]')) &&
        Boolean(body) &&
        Boolean(dialog.querySelector(':scope > [data-modal-footer]')),
      height: dialog.getBoundingClientRect().height,
      primaryShadow: getComputedStyle(primary).boxShadow,
      primaryOverflowing: primary.scrollHeight > primary.clientHeight,
      primaryScrollColor: getComputedStyle(primary).scrollbarColor,
      scrollPrimitive: body.localName,
      scrollPrimitives:
        dialog.querySelectorAll('zen-scroll-area[data-cyber-scroll]').length,
      scrollbarColor: bodyStyle.scrollbarColor,
      scrollbarGutter: bodyStyle.scrollbarGutter,
      shadow: getComputedStyle(dialog).boxShadow,
    };
  });
  assert.equal(windowMetrics.bodyScrollable, true);
  assert.equal(windowMetrics.borderRadius, '0px');
  assert.equal(windowMetrics.hasStrictRegions, true);
  assert.equal(windowMetrics.scrollPrimitive, 'zen-scroll-area');
  assert.notEqual(windowMetrics.scrollbarColor, 'auto');
  assert.match(windowMetrics.scrollbarGutter, /stable/);
  assert.match(windowMetrics.bodyColumns, /\d/);
  assert.ok(windowMetrics.height <= 768);
  assert.notEqual(windowMetrics.primaryShadow, 'none');
  assert.equal(windowMetrics.primaryOverflowing, true);
  assert.notEqual(windowMetrics.primaryScrollColor, 'auto');
  assert.equal(windowMetrics.scrollPrimitives, 3);
  assert.notEqual(windowMetrics.shadow, 'none');
  await page.getByRole('button', { name: 'Close workspace' }).click();
  await windowDialog.waitFor({ state: 'hidden' });
  assert.deepEqual(errors, []);
  await context.close();

  const mobileContext = await browser.newContext({
    serviceWorkers: 'block',
    viewport: { width: 390, height: 844 },
  });
  const mobilePage = await mobileContext.newPage();
  await mobilePage.goto(`${baseUrl}/public/component-sandbox.html`, {
    waitUntil: 'networkidle',
  });
  await mobilePage.evaluate(() => document.fonts.ready);
  await mobilePage.getByRole('button', { name: 'Open workspace' }).click();
  const mobileDialog = mobilePage.locator('mjj-window-modal dialog');
  await mobileDialog.waitFor({ state: 'visible' });
  await mobileDialog.evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  const mobileMetrics = await mobileDialog.evaluate(dialog => {
    const body = dialog.querySelector('[data-modal-body]');
    const dialogBox = dialog.getBoundingClientRect();
    return {
      bodyColumns: getComputedStyle(body).gridTemplateColumns,
      fits:
        dialogBox.left >= 0 &&
        dialogBox.top >= 0 &&
        dialogBox.right <= innerWidth &&
        dialogBox.bottom <= innerHeight,
      pageFits: document.documentElement.scrollWidth <= innerWidth,
    };
  });
  assert.equal(mobileMetrics.fits, true);
  assert.equal(mobileMetrics.pageFits, true);
  assert.doesNotMatch(mobileMetrics.bodyColumns, /\s/);
  assert.equal(
    await mobilePage.locator('mjj-window-modal').getAttribute(
      'data-component-layer',
    ),
    '2',
  );
  await mobileContext.close();
}

async function testJrpgPage(browser) {
  const context = await browser.newContext({
    colorScheme: 'light',
    viewport: { width: 1280, height: 900 },
  });
  await context.addInitScript(key => {
    sessionStorage.setItem(
      key,
      '00000000-0000-4000-8000-000000000000',
    );
  }, 'mjj-jrpg-conversation-id');
  const page = await context.newPage();
  const errors = [];
  page.on('pageerror', error => errors.push(`pageerror: ${error.message}`));
  page.on('console', message => {
    if (message.type() === 'error' &&
        !message.text().startsWith('Failed to load resource:')) {
      errors.push(`console: ${message.text()}`);
    }
  });
  page.on('requestfailed', request => {
    if (request.url().startsWith('blob:') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    if (request.method() === 'DELETE' &&
        request.url().includes('/api/download/') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    if (request.method() === 'POST' &&
        request.url().endsWith('/api/latex/render') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    if (request.url().includes('/api/auth/login') &&
        request.failure()?.errorText === 'net::ERR_ABORTED') {
      return;
    }
    errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
  });
  page.on('response', response => {
    if (response.status() === 404 &&
        response.url().endsWith(
          '/api/conversations/00000000-0000-4000-8000-000000000000'
        )) {
      return;
    }
    if (response.url().includes('/api/auth/login') && response.status() >= 400) {
      return; /* expected: login attempt with invalid credentials in modal test */
    }
    if (response.status() >= 400) {
      errors.push(`response: ${response.status()} ${response.url()}`);
    }
  });

  for (const expected of [
    { panel: 'resume', title: 'Resume' },
    { panel: 'tools', title: 'Tools' },
    { panel: 'blog', title: 'Blogs' },
  ]) {
    const html = await (
      await fetch(`${baseUrl}/jrpg?panel=${expected.panel}`)
    ).text();
    assert.match(
      html,
      new RegExp(`data-initial-panel="${expected.panel}"`),
    );
    assert.match(html, new RegExp(`data-preview-title>${expected.title}<`));
    assert.doesNotMatch(html, /__MJJ_[A-Z_]+__/);
  }
  const conversationHtml = await (
    await fetch(`${baseUrl}/jrpg?panel=conversations`)
  ).text();
  assert.match(conversationHtml, /data-initial-panel="conversations"/);
  assert.match(
    conversationHtml,
    /<mjj-conversation-archive\s+aria-label="Conversations"/,
  );

  await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
  await page.waitForFunction(() =>
    customElements.get('mjj-composer') &&
    customElements.get('mjj-jrpg-menu') &&
    customElements.get('mjj-jrpg-chat') &&
    customElements.get('mjj-jrpg-preview')
  );
  await page.waitForFunction(() =>
    [...document.querySelectorAll('.jrpg-workspace dialog')].every(dialog => {
      const owner = dialog.closest('mjj-content-modal, mjj-window-modal');
      return owner?.hasAttribute('data-ready');
    })
  );
  assert.deepEqual(
    await page.evaluate(() =>
      [...document.querySelectorAll('.jrpg-workspace dialog')]
        .map(dialog =>
          dialog.closest('mjj-content-modal, mjj-window-modal')?.localName)),
    [
      'mjj-window-modal',
      'mjj-content-modal',
      'mjj-content-modal',
      'mjj-window-modal',
      'mjj-window-modal',
    ],
    'every JRPG dialog has a Layer 2 owner',
  );
  await page.evaluate(() => document.fonts.ready);
  const desktop = await page.evaluate(() => {
    const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
    const utility = document.querySelector('.jrpg-utility').getBoundingClientRect();
    const composer = document.querySelector('mjj-composer')
      .getBoundingClientRect();
    const composerField = document.querySelector('.jrpg-composer zen-field')
      .getBoundingClientRect();
    const composerFieldStyle = getComputedStyle(
      document.querySelector('.jrpg-composer zen-field'),
    );
    const composerLabel = document.querySelector('.jrpg-composer label')
      .getBoundingClientRect();
    const composerTextarea = document.querySelector('#jrpg-message')
      .getBoundingClientRect();
    const menuElement = document.querySelector('mjj-jrpg-menu');
    const menu = menuElement.getBoundingClientRect();
    const character = document.querySelector('[data-character]');
    const epiAvatar = document.querySelector(
      '.jrpg-message[data-speaker="Epi"] .jrpg-message-avatar',
    );
    const characterBox = character.getBoundingClientRect();
    const sceneBarStyle = getComputedStyle(
      document.querySelector('.jrpg-scene'),
      '::before',
    );
    const messageScroller = document.querySelector('zen-message-scroller')
      .getBoundingClientRect();
    const messageViewport = document.querySelector('.jrpg-message-viewport')
      .getBoundingClientRect();
    const workspace = document.querySelector('.jrpg-workspace')
      .getBoundingClientRect();
    const brand = document.querySelector('.jrpg-brand').getBoundingClientRect();
    const frameControls = document.querySelector('.jrpg-frame-controls')
      .getBoundingClientRect();
    const frameTelemetry = document.querySelector('.jrpg-frame-telemetry')
      .getBoundingClientRect();
    const telemetryValues = [
      ...document.querySelectorAll('.jrpg-frame-telemetry > *'),
    ].map(value => value.getBoundingClientRect());
    const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
    const main = document.querySelector('.jrpg-main').getBoundingClientRect();
    const colorProbe = document.createElement('span');
    colorProbe.style.background = 'var(--zenbu-sys-color-surface-page)';
    colorProbe.style.color =
      'var(--zenbu-sys-color-action-primary-background)';
    colorProbe.style.borderColor = 'var(--zenbu-sys-color-focus-outer)';
    const surfaceProbe = document.createElement('span');
    surfaceProbe.style.background =
      'var(--zenbu-sys-color-surface-default)';
    surfaceProbe.style.color = 'var(--zenbu-sys-color-text-primary)';
    surfaceProbe.style.borderColor = 'var(--zenbu-sys-color-border-strong)';
    const sunkenProbe = document.createElement('span');
    sunkenProbe.style.background =
      'var(--zenbu-sys-color-surface-sunken)';
    const panelProbe = document.createElement('span');
    panelProbe.style.background = 'var(--mjj-jrpg-panel-strong)';
    document.body.append(colorProbe, surfaceProbe, sunkenProbe, panelProbe);
    const probeStyle = getComputedStyle(colorProbe);
    const surfaceProbeStyle = getComputedStyle(surfaceProbe);
    const sunkenProbeStyle = getComputedStyle(sunkenProbe);
    const panelProbeStyle = getComputedStyle(panelProbe);
    const computedFonts = [
      document.body,
      document.querySelector('.jrpg-brand h1'),
      document.querySelector('.jrpg-message p'),
      document.querySelector('#jrpg-message'),
      document.querySelector('.jrpg-composer button[type="submit"]'),
    ].map(element => getComputedStyle(element).fontFamily);
    const nonPixelText = [document.body, ...document.body.querySelectorAll('*')]
      .filter(element => [...element.childNodes].some(node =>
        node.nodeType === Node.TEXT_NODE && node.textContent.trim()
      ))
      .filter(element =>
        getComputedStyle(element).fontFamily !== '"Pixel Mplus"'
      )
      .map(element => element.localName);
    const pageStyle = getComputedStyle(document.body);
    const submitStyle = getComputedStyle(
      document.querySelector('.jrpg-composer button[type="submit"]'),
    );
    const colors = {
      canvas: probeStyle.backgroundColor,
      conversation: getComputedStyle(
        document.querySelector('mjj-jrpg-chat'),
      ).backgroundColor,
      conversationBorder: getComputedStyle(
        document.querySelector('mjj-jrpg-chat'),
      ).borderTopColor,
      conversationPanel: panelProbeStyle.backgroundColor,
      conversationText: getComputedStyle(
        document.querySelector('.jrpg-message p'),
      ).color,
      focus: probeStyle.borderColor,
      frame: surfaceProbeStyle.borderColor,
      menu: getComputedStyle(
        document.querySelector('mjj-jrpg-menu'),
      ).backgroundColor,
      page: pageStyle.backgroundColor,
      primary: probeStyle.color,
      primaryText: getComputedStyle(
        document.querySelector('.jrpg-preview-panel h2'),
      ).color,
      sunken: sunkenProbeStyle.backgroundColor,
      surface: surfaceProbeStyle.backgroundColor,
      submit: submitStyle.backgroundColor,
      text: surfaceProbeStyle.color,
      utility: getComputedStyle(
        document.querySelector('.jrpg-utility'),
      ).backgroundColor,
    };
    colorProbe.remove();
    surfaceProbe.remove();
    sunkenProbe.remove();
    panelProbe.remove();
    const closeTo = (value, expected) => Math.abs(value - expected) < 0.01;
    const frameAlignment = [
      closeTo((scene.left - workspace.left) / workspace.width, 0.032),
      closeTo((scene.top - workspace.top) / workspace.height, 0.094),
      closeTo(scene.width / workspace.width, 0.548),
      closeTo(scene.height / workspace.height, 0.57),
      closeTo((utility.left - workspace.left) / workspace.width, 0.604),
      closeTo(utility.width / workspace.width, 0.365),
      closeTo((composer.top - workspace.top) / workspace.height, 0.72),
      closeTo(composer.width / workspace.width, 0.682),
      closeTo((menu.left - workspace.left) / workspace.width, 0.734),
      closeTo(menu.width / workspace.width, 0.234),
    ].every(Boolean);
    const borderless = [
      '.jrpg-shell',
      '.jrpg-scene',
      '.jrpg-utility',
      'mjj-jrpg-chat',
      'mjj-composer',
      'mjj-jrpg-menu',
      '.jrpg-preview-panel',
      '.jrpg-message',
      '.jrpg-work-showcase a',
      'mjj-jrpg-menu button',
    ].every(selector => {
      const style = getComputedStyle(document.querySelector(selector));
      return style.borderTopWidth === '0px' &&
        style.borderRightWidth === '0px' &&
        style.borderBottomWidth === '0px' &&
        style.borderLeftWidth === '0px';
    });
    return {
      art: getComputedStyle(document.querySelector('.jrpg-workspace'))
        .backgroundImage,
      borderless,
      brandInSystemSlot:
        closeTo((brand.left - workspace.left) / workspace.width, 0.054) &&
        closeTo((brand.top - workspace.top) / workspace.height, 0.022) &&
        document.querySelector('.jrpg-brand').parentElement.classList
          .contains('jrpg-workspace'),
      brandCoversSystemLabel:
        brand.left <= workspace.left + workspace.width * 0.062 &&
        brand.right >= workspace.left + workspace.width * 0.102 &&
        brand.top <= workspace.top + workspace.height * 0.03 &&
        brand.bottom >= workspace.top + workspace.height * 0.06,
      characterLoaded: character.complete && character.naturalWidth > 0,
      epiAvatarLoaded:
        epiAvatar?.complete &&
        epiAvatar.naturalWidth > 0 &&
        epiAvatar.getAttribute('aria-hidden') === 'true',
      colors,
      composerBelowScene: composer.top >= scene.bottom,
      composerTextareaFills: (
        Math.abs(composerTextarea.left - composerField.left) < 1 &&
        Math.abs(composerTextarea.right - composerField.right) < 1 &&
        Math.abs(composerTextarea.bottom - composerField.bottom) < 1 &&
        composerTextarea.height > 60
      ),
      composerUsesZenStyles:
        getComputedStyle(document.querySelector('.jrpg-composer textarea'))
          .borderTopWidth !== '0px' &&
        submitStyle.borderTopWidth !== '0px',
      composerTextareaGeometry: {
        fieldBottom: composerField.bottom,
        fieldDisplay: composerFieldStyle.display,
        fieldGridRows: composerFieldStyle.gridTemplateRows,
        fieldLeft: composerField.left,
        fieldRight: composerField.right,
        fieldTop: composerField.top,
        labelBottom: composerLabel.bottom,
        labelTop: composerLabel.top,
        textareaBottom: composerTextarea.bottom,
        textareaHeight: composerTextarea.height,
        textareaLeft: composerTextarea.left,
        textareaRight: composerTextarea.right,
        textareaTop: composerTextarea.top,
      },
      fillsViewport: shell.left === 0 &&
        shell.top === 0 &&
        Math.abs(shell.right - innerWidth) < 1 &&
        Math.abs(shell.bottom - innerHeight) < 1 &&
        main.left === 0 &&
        main.top === 0 &&
        Math.abs(main.right - innerWidth) < 1 &&
        Math.abs(main.bottom - innerHeight) < 1,
      frameAlignment,
      frameChromeAligned:
        closeTo((frameControls.left - workspace.left) / workspace.width, 0.866) &&
        closeTo((frameTelemetry.left - workspace.left) / workspace.width, 0.085) &&
        closeTo((frameTelemetry.top - workspace.top) / workspace.height, 0.928),
      fonts: computedFonts,
      fontLoaded: [...document.fonts].some(font =>
        font.family.replaceAll('"', '') === 'Pixel Mplus' &&
        font.status === 'loaded'
      ),
      menuRightOfComposer: menu.left >= composer.right,
      messageViewportFills: (
        Math.abs(messageViewport.top - messageScroller.top) < 1 &&
        Math.abs(messageViewport.bottom - messageScroller.bottom) < 1 &&
        messageViewport.height > 192
      ),
      noOverflow: document.documentElement.scrollWidth <= innerWidth,
      nonPixelText,
      questLabelAbsent: !document.body.textContent.includes('QUEST 01'),
      previewLabelsRemoved:
        !/CHARACTER RECORD|ITEM INVENTORY|QUEST ARCHIVE/.test(
          document.querySelector('.jrpg-preview-panel').textContent,
        ),
      statsRemoved:
        !document.querySelector('.jrpg-preview-stats') &&
        !document.querySelector('[data-preview-type]'),
      telemetryCovered:
        document.querySelectorAll('.jrpg-frame-telemetry > *').length === 4 &&
        document.querySelector('[data-frame-network]').textContent
          .includes('ONLINE') &&
        document.querySelector('[data-frame-uptime]').textContent
          .match(/^\d{2}:\d{2}:\d{2}$/) &&
        [...document.querySelectorAll('.jrpg-frame-telemetry > *')]
          .every(item => !/TEMP:|USER:|STATUS:|UPTIME:/.test(item.textContent)),
      telemetryValuesAligned:
        closeTo(
          (telemetryValues[0].left + telemetryValues[0].width / 2 -
            frameTelemetry.left) / frameTelemetry.width,
          0.12,
        ) &&
        closeTo(
          (telemetryValues[1].left + telemetryValues[1].width / 2 -
            frameTelemetry.left) / frameTelemetry.width,
          0.325,
        ) &&
        closeTo(
          (telemetryValues[2].left + telemetryValues[2].width / 2 -
            frameTelemetry.left) / frameTelemetry.width,
          0.605,
        ) &&
        closeTo(
          (telemetryValues[3].left + telemetryValues[3].width / 2 -
            frameTelemetry.left) / frameTelemetry.width,
          0.88,
        ),
      shibaInFrontOfBar:
        sceneBarStyle.backgroundImage.includes('bar-ink.webp') &&
        sceneBarStyle.backgroundSize === 'cover' &&
        Math.abs(parseFloat(sceneBarStyle.height) - scene.height) < 1 &&
        Number(getComputedStyle(character.parentElement).zIndex) >
          Number(sceneBarStyle.zIndex) &&
        characterBox.bottom > scene.top + scene.height * 0.65,
      terminalScrollbar: (
        getComputedStyle(
          document.querySelector('.jrpg-message-viewport'),
        ).scrollbarWidth === 'thin' &&
        getComputedStyle(
          document.querySelector('.jrpg-message-viewport'),
        ).scrollbarColor !== 'auto'
      ),
      theme: document.documentElement.dataset.zenTheme,
      utilityRightOfScene: utility.left >= scene.right,
      workspaceFillsViewport:
        Math.abs(workspace.left) < 1 &&
        Math.abs(workspace.top) < 1 &&
        Math.abs(workspace.right - innerWidth) < 1 &&
        Math.abs(workspace.bottom - innerHeight) < 1,
    };
  });
  assert.match(desktop.art, /background-frame-2\.webp/);
  assert.equal(desktop.borderless, true);
  assert.equal(desktop.brandInSystemSlot, true);
  assert.equal(desktop.brandCoversSystemLabel, true);
  assert.equal(desktop.characterLoaded, true);
  assert.equal(desktop.epiAvatarLoaded, true);
  assert.equal(desktop.colors.page, desktop.colors.canvas);
  assert.equal(desktop.colors.conversation, 'rgba(0, 0, 0, 0)');
  assert.equal(desktop.colors.conversationText, desktop.colors.text);
  assert.equal(desktop.colors.menu, 'rgba(0, 0, 0, 0)');
  assert.equal(desktop.colors.primaryText, desktop.colors.primary);
  assert.equal(desktop.colors.submit, desktop.colors.primary);
  assert.equal(desktop.composerUsesZenStyles, true);
  assert.equal(desktop.colors.utility, 'rgba(0, 0, 0, 0)');
  assert.equal(desktop.composerBelowScene, true);
  assert.equal(
    desktop.composerTextareaFills,
    true,
    JSON.stringify(desktop.composerTextareaGeometry),
  );
  assert.equal(desktop.fillsViewport, true);
  assert.equal(desktop.frameAlignment, true);
  assert.equal(desktop.frameChromeAligned, true);
  assert.ok(desktop.fonts.every(font => font === '"Pixel Mplus"'));
  assert.equal(desktop.fontLoaded, true);
  assert.equal(desktop.menuRightOfComposer, true);
  assert.equal(desktop.messageViewportFills, true);
  assert.equal(desktop.noOverflow, true);
  assert.deepEqual(desktop.nonPixelText, []);
  assert.equal(desktop.questLabelAbsent, true);
  assert.equal(desktop.previewLabelsRemoved, true);
  assert.equal(desktop.statsRemoved, true);
  assert.equal(desktop.telemetryCovered, true);
  assert.equal(desktop.telemetryValuesAligned, true);
  assert.equal(desktop.shibaInFrontOfBar, true);
  assert.equal(desktop.terminalScrollbar, true);
  assert.equal(desktop.theme, 'cyberpunk');
  assert.equal(desktop.utilityRightOfScene, true);
  assert.equal(desktop.workspaceFillsViewport, true);
  await page.locator('.jrpg-brand a').focus();
  assert.equal(
    await page.locator('.jrpg-brand a').evaluate(element =>
      getComputedStyle(element).outlineColor
    ),
    desktop.colors.focus,
  );
  await page.locator('.jrpg-composer button[type="submit"]').focus();
  assert.equal(
    await page.locator('.jrpg-composer button[type="submit"]').evaluate(element =>
      getComputedStyle(element).outlineColor
    ),
    desktop.colors.focus,
  );
  assert.equal(
    await page.locator('meta[name="theme-color"]').first().getAttribute('content'),
    desktop.colors.canvas,
  );
  assert.equal(
    await page.locator('.jrpg-frame-controls :is(button, a)').count(),
    3,
  );
  assert.equal(
    await page.locator('.jrpg-frame-controls a').getAttribute('href'),
    '/',
  );

  // Account slot: guest session shows a login button (modal trigger)
  assert.ok(
    await page.locator('[data-frame-account]').count() > 0,
    'account slot exists in telemetry',
  );
  await page.waitForFunction(() =>
    document.querySelector('[data-frame-account]')?.querySelector('button[data-login-open]') !== null
  );
  const loginBtnState = await page.locator('[data-frame-account] button[data-login-open]').evaluate(btn => ({
    text: btn.textContent.trim(),
    ownerCount: btn.closest('zen-button') ? 1 : 0,
    appearance: btn.closest('zen-button')?.getAttribute('appearance'),
    size: btn.closest('zen-button')?.getAttribute('size'),
  }));
  assert.match(loginBtnState.text, /LOGIN/i);
  assert.equal(loginBtnState.ownerCount, 1, 'login button has zen-button owner');
  assert.equal(loginBtnState.appearance, 'plain');
  assert.equal(loginBtnState.size, 'xs');

  // Login modal: opens on button click, focuses username, closes on cancel
  await page.locator('[data-frame-account] button[data-login-open]').click();
  await page.locator('[data-login-dialog]').waitFor({ state: 'visible' });
  assert.equal(
    await page.locator('[data-login-dialog]').getAttribute('aria-modal'),
    'true',
    'login dialog is modal',
  );
  // Username input should receive initial focus
  await page.waitForFunction(() => document.activeElement?.id === 'jrpg-login-username');
  // Cancel closes dialog and clears password
  await page.locator('#jrpg-login-password').fill('dummypassword');
  await page.locator('[data-login-cancel]').click();
  await page.locator('[data-login-dialog]').waitFor({ state: 'hidden' });
  await page.waitForFunction(() => !document.querySelector('#jrpg-login-password')?.value);
  assert.equal(
    await page.locator('#jrpg-login-password').inputValue(),
    '',
    'password cleared on close',
  );

  // Invalid credentials show error message
  await page.locator('[data-frame-account] button[data-login-open]').click();
  await page.locator('[data-login-dialog]').waitFor({ state: 'visible' });
  await page.locator('#jrpg-login-username').fill('nosuchuser');
  await page.locator('#jrpg-login-password').fill('nosuchpassword123');
  let loginErrorShown = false;
  const loginResponsePromise = page.waitForResponse(resp =>
    resp.url().includes('/api/auth/login')
  );
  await page.locator('[data-login-submit]').click();
  const loginResp = await loginResponsePromise;
  if (loginResp.status() >= 400) {
    await page.waitForFunction(() => {
      const el = document.querySelector('[data-login-error]');
      return el && !el.hidden && el.textContent.trim().length > 0;
    });
    loginErrorShown = true;
  }
  assert.equal(loginErrorShown, true, 'error element shown on bad credentials');
  assert.equal(
    await page.locator('#jrpg-login-password').inputValue(),
    '',
    'password cleared after failed login attempt',
  );
  // Close modal via X button (data-zen-close)
  await page.getByRole('button', { name: 'Close sign-in dialog' }).click();
  await page.locator('[data-login-dialog]').waitFor({ state: 'hidden' });

  // Archive opens only from the bottom destination menu.
  assert.equal(
    await page.locator('[data-toggle-archive]').count(),
    0,
    'redundant chat-heading archive shortcut is removed',
  );
  assert.equal(
    await page.locator('.jrpg-panel-heading').count(),
    0,
    'visible conversation heading is removed',
  );
  await page.locator('button[data-preview="conversations"]').click();
  await page.locator('.jrpg-utility mjj-conversation-archive').waitFor({ state: 'visible' });
  const archiveGeometry = await page.evaluate(() => {
    const utilityElement = document.querySelector('.jrpg-utility');
    const archiveElement = utilityElement.querySelector('mjj-conversation-archive');
    const utility = utilityElement.getBoundingClientRect();
    const archive = archiveElement.getBoundingClientRect();
    const archiveStyle = getComputedStyle(archiveElement);
    const previewPanel = utilityElement.querySelector('.jrpg-preview-panel');
    const panel = previewPanel.getBoundingClientRect();
    const previewPanelStyle = getComputedStyle(previewPanel);
    const panelPadding = {
      bottom: parseFloat(previewPanelStyle.paddingBottom),
      left: parseFloat(previewPanelStyle.paddingLeft),
      right: parseFloat(previewPanelStyle.paddingRight),
      top: parseFloat(previewPanelStyle.paddingTop),
    };
    const previewContent = previewPanel.querySelector('[data-preview-content]');
    const renderedPanelChildren = Array.from(previewPanel.children)
      .filter(element => getComputedStyle(element).display !== 'none')
      .map(element => element.localName);
    return {
      archive: {
        bottom: archive.bottom,
        height: archive.height,
        left: archive.left,
        right: archive.right,
        top: archive.top,
        width: archive.width,
      },
      fits: Math.abs(archive.left - panel.left - panelPadding.left) < 1 &&
        Math.abs(archive.top - panel.top - panelPadding.top) < 1 &&
        Math.abs(panel.right - panelPadding.right - archive.right) < 1 &&
        Math.abs(panel.bottom - panelPadding.bottom - archive.bottom) < 1,
      integrated: archiveStyle.position === 'static' &&
        archiveStyle.zIndex === 'auto' &&
        archiveStyle.backgroundColor === 'rgba(0, 0, 0, 0)' &&
        archiveStyle.backgroundImage === 'none' &&
        archiveStyle.boxShadow === 'none' &&
        previewPanelStyle.display === 'block' &&
        archiveElement.parentElement === previewPanel &&
        previewContent.hidden &&
        renderedPanelChildren.length === 1 &&
        renderedPanelChildren[0] === 'mjj-conversation-archive',
      renderedPanelChildren,
      panel: {
        bottom: panel.bottom,
        height: panel.height,
        left: panel.left,
        right: panel.right,
        top: panel.top,
        width: panel.width,
      },
      panelPadding,
      utility: {
        bottom: utility.bottom,
        height: utility.height,
        left: utility.left,
        right: utility.right,
        top: utility.top,
        width: utility.width,
      },
    };
  });
  assert.equal(
    archiveGeometry.fits,
    true,
    `conversation archive fills the existing utility content panel: ${JSON.stringify(archiveGeometry)}`,
  );
  assert.equal(
    archiveGeometry.integrated,
    true,
    `conversation archive renders as utility content instead of an overlay: ${JSON.stringify(archiveGeometry)}`,
  );
  // Archive status or empty message should be visible
  await page.waitForFunction(() => {
    const status = document.querySelector('[data-archive-status]');
    return status && !status.hidden;
  });
  // New conversation button exists inside archive
  assert.ok(
    await page.locator('[data-archive-new]').isVisible(),
    'archive new button visible',
  );
  // Conversations menu button should be pressed
  assert.equal(
    await page.locator('button[data-preview="conversations"]').getAttribute('aria-pressed'),
    'true',
  );
  await page.evaluate(() => {
    document.querySelector('mjj-conversation-archive')?.addConversation({
      id: 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee',
      title: 'Modal migration fixture',
      turn_count: 2,
    });
  });
  await page.getByRole('button', {
    name: 'Rename "Modal migration fixture"',
  }).click();
  await page.locator('[data-archive-rename-dialog]').waitFor({ state: 'visible' });
  assert.equal(
    await page.locator('[data-archive-rename-dialog]').evaluate(dialog =>
      dialog.closest('mjj-window-modal')?.localName),
    'mjj-window-modal',
  );
  await page.locator('[data-archive-rename-cancel]').click();
  await page.locator('[data-archive-rename-dialog]').waitFor({ state: 'hidden' });

  await page.getByRole('button', {
    name: 'Delete "Modal migration fixture"',
  }).click();
  await page.locator('[data-archive-delete-dialog]').waitFor({ state: 'visible' });
  assert.equal(
    await page.locator('[data-archive-delete-dialog]').evaluate(dialog =>
      dialog.closest('mjj-content-modal')?.localName),
    'mjj-content-modal',
  );
  await page.locator('[data-archive-delete-cancel]').click();
  await page.locator('[data-archive-delete-dialog]').waitFor({ state: 'hidden' });

  // Archive: close via close button, navigates back to resume panel
  await page.locator('[data-archive-close]').click();
  await page.locator('.jrpg-utility mjj-conversation-archive').waitFor({ state: 'hidden' });
  // mjj-jrpg-chat must remain visible (always in scene)
  assert.equal(
    await page.locator('mjj-jrpg-chat').isVisible(),
    true,
  );

  // Quota element is present (may be shown for guest session)
  assert.ok(
    await page.locator('[data-quota]').count() > 0,
    'quota element exists',
  );

  await page.getByRole('button', { name: 'Minimize interface' }).click();
  assert.equal(
    await page.locator('.jrpg-scene').evaluate(
      scene => getComputedStyle(scene).visibility,
    ),
    'hidden',
  );
  await page.getByRole('button', { name: 'Restore interface' }).click();
  assert.equal(
    await page.locator('.jrpg-scene').evaluate(
      scene => getComputedStyle(scene).visibility,
    ),
    'visible',
  );
  assert.equal(await page.locator('[data-work-showcase] a').count(), 8);
  assert.equal(
    await page.getByRole('button', { name: /Inspect/ }).count(),
    0,
  );
  assert.match(
    await page.locator('[data-work-showcase]').textContent(),
    /Copilot Tasks/,
  );
  assert.match(
    await page.locator('[data-work-showcase]').textContent(),
    /AIX Harness \/ Copilot/,
  );
  assert.equal(
    await page.locator('[data-turn-position]').textContent(),
    'START',
  );
  assert.equal(
    await page.getByRole('button', { name: 'Previous turn' }).isDisabled(),
    true,
  );
  assert.equal(
    await page.getByRole('button', { name: 'Next turn' }).isDisabled(),
    true,
  );

  await page.getByRole('button', { name: 'Tools', exact: true }).click();
  assert.equal(
    await page.locator('[data-preview-title]').textContent(),
    'Tools',
  );
  assert.equal(await page.locator('[data-preview-link]').count(), 0);
  assert.equal(
    await page.getByRole('button', { name: 'Tools', exact: true })
      .getAttribute('aria-pressed'),
    'true',
  );
  await page.waitForFunction(() =>
    document.querySelectorAll('[data-latest-tool]').length === 5
  );
  assert.match(
    await page.locator('[data-work-showcase]').textContent(),
    /Online LaTeX Editor/,
  );
  assert.match(
    await page.locator('[data-work-showcase]').textContent(),
    /Personal Notes/,
  );
  await page.locator('[data-latest-tool]').first().click();
  await page.locator('[data-functional-tool="markdown"]').waitFor();
  assert.equal(
    await page.locator('.jrpg-detail-dialog').evaluate(dialog =>
      dialog.closest('mjj-content-modal, mjj-window-modal')?.localName),
    'mjj-window-modal',
    'tool details use the window modal',
  );
  assert.equal(
    await page.locator('[data-dialog-title]').textContent(),
    'MarkDown to HTML',
  );
  await page.locator('[data-markdown-source]').fill('# Modal Markdown');
  await page.waitForFunction(() =>
    document.querySelector('[data-markdown-output] h1')?.textContent ===
      'Modal Markdown'
  );
  assert.equal(await page.locator('.jrpg-tool-pane').count(), 2);
  const toolWorkspace = await page.evaluate(() => {
    const dialogElement = document.querySelector(
      '.jrpg-detail-dialog[data-detail-mode="tools"]',
    );
    const dialog = dialogElement.getBoundingClientRect();
    const windows = [...document.querySelectorAll('.jrpg-tool-window')]
      .map(window => window.getBoundingClientRect());
    const windowShadows = [...document.querySelectorAll('.jrpg-tool-window')]
      .map(window => getComputedStyle(window).boxShadow);
    return {
      dialogHeight: dialogElement.offsetHeight,
      dialogWidth: dialogElement.offsetWidth,
      compact: dialogElement.offsetWidth <= innerWidth * 0.9 &&
        dialogElement.offsetHeight <= innerHeight * 0.8,
      viewportHeight: innerHeight,
      viewportWidth: innerWidth,
      stacked: windows.length === 2 &&
        windows[0].bottom < windows[1].top &&
        Math.abs(windows[0].left - windows[1].left) < 1,
      noOpenPageAction: !document.querySelector('[data-preview-link]'),
      noFooter: !document.querySelector('[data-dialog-actions]'),
      pixelIcons: document.querySelectorAll('.jrpg-tool-window zen-icon').length >= 2 &&
        [...document.querySelectorAll('.jrpg-tool-window zen-icon svg')]
          .every(icon =>
            getComputedStyle(icon).shapeRendering.toLowerCase() === 'crispedges'
          ),
      separateGlows: windowShadows.length === 2 &&
        windowShadows.every(shadow => shadow !== 'none'),
      wideWindows: windows.every(window => window.width > dialog.width * 0.8),
    };
  });
  assert.equal(toolWorkspace.compact, true, JSON.stringify(toolWorkspace));
  assert.equal(toolWorkspace.stacked, true);
  assert.equal(toolWorkspace.noOpenPageAction, true);
  assert.equal(toolWorkspace.noFooter, true);
  assert.equal(toolWorkspace.pixelIcons, true);
  assert.equal(toolWorkspace.separateGlows, true);
  assert.equal(toolWorkspace.wideWindows, true);
  await page.locator('.jrpg-detail-dialog').evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-latest-tool]').first().click();
  await page.locator('[data-functional-tool="markdown"]').waitFor();
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-latest-tool]').nth(1).click();
  await page.locator('[data-functional-tool="converter"]').waitFor();
  assert.equal(
    await page.locator('[data-converter-input]').count(),
    2,
  );
  const converterFixture = path.join(
    RUNFILES,
    WORKSPACE,
    'mrjunejune/test/shiba.webp',
  );
  assert.equal(fs.existsSync(converterFixture), true);
  await page.locator('[data-converter-input="image"]').setInputFiles(
    converterFixture,
  );
  const conversionResponse = page.waitForResponse(response =>
    response.url().endsWith('/api/convert/image-to-webp') &&
    response.request().method() === 'POST'
  );
  await page.locator('[data-convert-kind="image"]').click();
  assert.equal((await conversionResponse).status(), 200);
  await page.locator('[data-converter-download="image"]').waitFor();
  const cleanupResponse = page.waitForResponse(response =>
    response.url().includes('/api/download/') &&
    response.request().method() === 'DELETE'
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  assert.equal((await cleanupResponse).status(), 204);
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-latest-tool]').nth(1).click();
  await page.locator('[data-functional-tool="converter"]').waitFor();
  await page.locator('[data-converter-input="image"]').setInputFiles(
    converterFixture,
  );
  const lateConversionResponse = page.waitForResponse(response =>
    response.url().endsWith('/api/convert/image-to-webp') &&
    response.request().method() === 'POST'
  );
  const lateCleanupResponse = page.waitForResponse(response =>
    response.url().includes('/api/download/') &&
    response.request().method() === 'DELETE'
  );
  await page.locator('[data-convert-kind="image"]').click();
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  assert.equal((await lateConversionResponse).status(), 200);
  assert.equal((await lateCleanupResponse).status(), 204);
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-latest-tool]').nth(2).click();
  await page.locator('[data-functional-tool="hls"]').waitFor();
  await page.locator('[data-hls-sample]').click();
  await page.waitForFunction(() =>
    ['ready', 'error'].includes(
      document.querySelector('[data-hls-status]')?.dataset.state,
    )
  );
  assert.ok(
    ['ready', 'error'].includes(
      await page.locator('[data-hls-status]').getAttribute('data-state'),
    ),
  );
  assert.match(
    await page.locator('[data-hls-status]').textContent(),
    /ready|unsupported|failed/i,
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-latest-tool]').nth(3).click();
  await page.locator('[data-functional-tool="latex"]').waitFor();
  await page.locator('[data-latex-compile]').click();
  await page.locator('[data-latex-source]').fill(
    '\\documentclass{article}\\begin{document}Updated modal PDF\\end{document}',
  );
  assert.equal(await page.locator('[data-latex-download]').isHidden(), true);
  await page.locator('[data-latex-compile]').click();
  await page.waitForFunction(() =>
    document.querySelector('[data-latex-status]')?.dataset.state === 'ready'
  );
  assert.match(
    await page.locator('[data-latex-preview]').getAttribute('src'),
    /^blob:/,
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-latest-tool]').nth(4).click();
  await page.waitForFunction(() =>
    document.querySelector('[data-tool-content] h1')?.textContent ===
      'Personal Notes'
  );
  assert.match(
    await page.locator('[data-tool-content]').textContent(),
    /private browser-based writing workspace/,
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.getByRole('button', { name: 'Resume', exact: true }).click();
  await page.locator('[data-resume-modal]').click();
  assert.equal(
    await page.locator('.jrpg-detail-dialog').evaluate(dialog =>
      dialog.closest('mjj-content-modal, mjj-window-modal')?.localName),
    'mjj-content-modal',
    'resume details use the content modal',
  );
  await page.locator('[data-resume-content] .sub-header').first().waitFor();
  const resumeDossier = await page.locator('[data-resume-dossier]').evaluate(
    dossier => ({
      fontFamily: getComputedStyle(dossier).fontFamily,
      text: dossier.textContent,
    }),
  );
  assert.equal(resumeDossier.fontFamily, '"Pixel Mplus"');
  assert.match(resumeDossier.text, /Copilot Tasks/);
  assert.match(resumeDossier.text, /Microsoft/);
  assert.match(resumeDossier.text, /Meta/);
  const resumeModalGeometry = await page.locator('.jrpg-detail-dialog').evaluate(
    dialog => {
      const body = dialog.querySelector('[data-modal-body]').getBoundingClientRect();
      const dossier = dialog.querySelector('[data-resume-dossier]')
        .getBoundingClientRect();
      const buttons = [...dialog.querySelectorAll(
        '[data-modal-header] zen-button > :is(button, a)',
      )].map(control => control.getBoundingClientRect());
      return {
        bodyFilled:
          dossier.width / body.width > 0.9 &&
          Math.abs(dossier.left - body.left) < 32,
        equalHeaderControls:
          buttons.length === 2 &&
          Math.abs(buttons[0].height - buttons[1].height) < 1,
        headerControlHeights: buttons.map(button => button.height),
        footerAbsent: !dialog.querySelector('[data-modal-footer]'),
        headerPdfLinks: dialog.querySelectorAll(
          '[data-modal-header] a[href="/public/resume.pdf"]',
        ).length,
        visibleBodyPdfLinks: [...dialog.querySelectorAll(
          '[data-modal-body] a[href="/public/resume.pdf"]',
        )].filter(link => getComputedStyle(link).display !== 'none').length,
        pdfLinks: dialog.querySelectorAll('a[href="/public/resume.pdf"]').length,
      };
    },
  );
  assert.equal(resumeModalGeometry.bodyFilled, true);
  assert.equal(
    resumeModalGeometry.equalHeaderControls,
    true,
    JSON.stringify(resumeModalGeometry),
  );
  assert.equal(resumeModalGeometry.footerAbsent, true);
  assert.equal(resumeModalGeometry.headerPdfLinks, 1);
  assert.equal(resumeModalGeometry.visibleBodyPdfLinks, 0);
  assert.equal(resumeModalGeometry.pdfLinks, 1);
  await page.locator('.jrpg-detail-dialog').evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  assert.equal(
    await page.locator('[data-resume-content] a[target="_blank"]').count() > 0,
    true,
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.getByRole('button', { name: 'Blogs', exact: true }).click();
  await page.waitForFunction(() =>
    document.querySelectorAll('[data-latest-blog]').length === 4
  );
  assert.match(
    await page.locator('[data-work-showcase]').textContent(),
    /Websocket Demystified/,
  );
  assert.match(
    await page.locator('[data-work-showcase]').textContent(),
    /Creating Network Library in C/,
  );
  await page.locator('[data-latest-blog]').first().click();
  await page.locator('[data-blog-content] h1').waitFor();
  assert.equal(
    await page.locator('[data-blog-content] h1').textContent(),
    'WebSocket Demystified',
  );
  assert.equal(
    await page.locator('[data-blog-content]').evaluate(
      article => getComputedStyle(article).fontFamily,
    ),
    '"Pixel Mplus"',
  );
  const blogWidth = await page.locator('.jrpg-detail-dialog').evaluate(dialog => {
    const body = dialog.querySelector('[data-modal-body]').getBoundingClientRect();
    const browser = dialog.querySelector('[data-blog-browser]')
      .getBoundingClientRect();
    return browser.width / body.width;
  });
  assert.ok(blogWidth > 0.9);
  await page.locator('.jrpg-detail-dialog').evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();
  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });

  await page.locator('[data-blog-archive]').click();
  await page.waitForFunction(() =>
    document.querySelectorAll('[data-blog-entry]').length === 9
  );
  assert.equal(await page.locator('[data-blog-entry]').count(), 9);
  await page.locator('[data-blog-entry]').nth(1).click();
  await page.waitForFunction(() =>
    document.querySelector('[data-blog-content] h1')?.textContent ===
      'Creating Network Library in C'
  );
  await page.getByRole('button', {
    name: 'Close destination details',
  }).click();

  await page.evaluate(() => {
    window.__jrpgStreamEvents = [];
    document.querySelector('mjj-jrpg-shell').addEventListener(
      'mjj-jrpg-stream-event',
      event => window.__jrpgStreamEvents.push(event.detail),
    );
  });
  await page.locator('#jrpg-message').fill('Where should I begin?');
  await page.locator('#jrpg-message').press('Enter');
  await page.locator('.jrpg-message[data-speaker="June"]').last().waitFor();
  const streamingMessage = page.locator(
    '.jrpg-message[data-speaker="Epi"][data-streaming="true"]',
  ).last();
  await streamingMessage.waitFor();
  await page.locator('[data-composer-cancel-owner]').waitFor({ state: 'hidden' });
  await streamingMessage.waitFor();
  assert.notEqual(
    await streamingMessage.locator('p').textContent(),
    'hello traveler',
  );
  await page.getByRole('button', { name: 'Blogs', exact: true }).click();
  assert.equal(
    await page.locator('.jrpg-message[data-speaker="June"] p').last()
      .textContent(),
    'Where should I begin?',
  );
  await page.waitForFunction(() =>
    document.querySelector('mjj-jrpg-character')?.getAttribute('state') ===
      'default' &&
    document.querySelectorAll('.jrpg-message[data-speaker="Epi"]').length > 1
  );
  assert.equal(
    await page.locator('.jrpg-message[data-speaker="Epi"] p').last()
      .textContent(),
    'hello traveler',
  );
  assert.equal(
    await page.locator(
      '.jrpg-message[data-speaker="Epi"][data-streaming="true"]',
    ).count(),
    0,
  );
  assert.deepEqual(
    await page.evaluate(() => {
      const event = window.__jrpgStreamEvents.find(
        item => item.type === 'tool.started',
      );
      return {
        name: event?.data.tool?.name,
        type: event?.type,
      };
    }),
    {
      name: 'mock-search',
      type: 'tool.started',
    },
  );
  assert.equal(
    await page.locator('[data-turn-position]').textContent(),
    'TURN 1 / 1',
  );
  assert.equal(
    await page.getByRole('button', { name: 'Previous turn' }).isEnabled(),
    true,
  );
  assert.equal(
    await page.getByRole('button', { name: 'Next turn' }).isDisabled(),
    true,
  );
  await page.getByRole('button', { name: 'Previous turn' }).click();
  assert.equal(
    await page.locator('[data-turn-position]').textContent(),
    'START',
  );
  assert.equal(
    await page.locator('.jrpg-message[data-speaker="June"]:visible').count(),
    0,
  );
  assert.equal(
    await page.locator('.jrpg-message[data-speaker="Epi"]:visible').count(),
    1,
  );
  await page.getByRole('button', { name: 'Next turn' }).click();
  assert.equal(
    await page.locator('[data-turn-position]').textContent(),
    'TURN 1 / 1',
  );
  assert.equal(
    await page.locator('.jrpg-message[data-speaker="June"]:visible').count(),
    1,
  );
  assert.equal(await page.locator('#jrpg-message').isEnabled(), true);

  await page.waitForFunction(() => navigator.serviceWorker?.controller);
  await page.reload({ waitUntil: 'networkidle' });
  await page.evaluate(() => document.fonts.ready);
  const controlledReload = await page.evaluate(() => {
    const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
    return {
      art: getComputedStyle(document.querySelector('.jrpg-workspace'))
        .backgroundImage,
      fillsViewport: shell.left === 0 &&
        shell.top === 0 &&
        Math.abs(shell.right - innerWidth) < 1 &&
        Math.abs(shell.bottom - innerHeight) < 1,
      fontLoaded: document.fonts.check('16px "Pixel Mplus"'),
      fontFamily: getComputedStyle(
        document.querySelector('.jrpg-brand h1'),
      ).fontFamily,
      theme: document.documentElement.dataset.zenTheme,
    };
  });
  assert.match(controlledReload.art, /background-frame-2\.webp/);
  assert.equal(controlledReload.fillsViewport, true);
  assert.equal(controlledReload.fontLoaded, true);
  assert.equal(controlledReload.fontFamily, '"Pixel Mplus"');
  assert.equal(controlledReload.theme, 'cyberpunk');
  assert.deepEqual(errors, []);
  await context.close();

  const mobileContext = await browser.newContext({
    colorScheme: 'light',
    viewport: { width: 360, height: 640 },
  });
  const mobilePage = await mobileContext.newPage();
  await mobilePage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
  await mobilePage.waitForFunction(() =>
    customElements.get('mjj-composer') &&
    customElements.get('mjj-jrpg-menu')
  );
  const mobile = await mobilePage.evaluate(() => {
    const workspace = document.querySelector('.jrpg-workspace')
      .getBoundingClientRect();
    const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
    const utility = document.querySelector('.jrpg-utility').getBoundingClientRect();
    const composer = document.querySelector('mjj-composer')
      .getBoundingClientRect();
    const brand = document.querySelector('.jrpg-brand').getBoundingClientRect();
    const brandLink = document.querySelector('.jrpg-brand a').getBoundingClientRect();
    const turnNavigation = document.querySelector('.jrpg-turn-navigation');
    const previousTurn = turnNavigation.querySelector('[data-turn-previous]')
      .getBoundingClientRect();
    const turnPosition = turnNavigation.querySelector('[data-turn-position]')
      .getBoundingClientRect();
    const nextTurn = turnNavigation.querySelector('[data-turn-next]')
      .getBoundingClientRect();
    const composerForm = document.querySelector('.jrpg-composer')
      .getBoundingClientRect();
    const composerTextarea = document.querySelector('#jrpg-message')
      .getBoundingClientRect();
    const sendButton = document.querySelector(
      '.jrpg-composer button[type="submit"]',
    ).getBoundingClientRect();
    const sendOwner = document.querySelector(
      '.jrpg-composer zen-button:has(button[type="submit"])',
    );
    const composerField = document.querySelector(
      '.jrpg-composer [data-composer-control] > zen-field',
    );
    const composerLabel = composerField.querySelector('label');
    const sendLabel = document.querySelector('[data-composer-action-label]');
    const menuElement = document.querySelector('mjj-jrpg-menu');
    const menu = menuElement.getBoundingClientRect();
    const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
    const hamburger = document.querySelector('[data-mobile-menu-toggle]');
    const hamburgerBox = hamburger?.getBoundingClientRect();
    const dialog = document.querySelector('[data-mobile-destination-dialog]');
    const destinationContent = document.querySelector(
      '[data-mobile-destination-content]',
    );
    const closeTo = (value, expected, tol = 0.025) =>
      Math.abs(value - expected) < tol;
    const wsW = workspace.width;
    const wsH = workspace.height;
    const mobileArt = getComputedStyle(
      document.querySelector('.jrpg-workspace'),
    ).backgroundImage;
    const mobileArtIsCorrect = mobileArt.includes('background-frame-mobile.webp');
    const aperturesTolerance = [
      closeTo((scene.left - workspace.left) / wsW, 0.048),
      closeTo((scene.top - workspace.top) / wsH, 0.073),
      closeTo(scene.width / wsW, 0.904),
      closeTo(scene.height / wsH, 0.712),
      closeTo((composer.left - workspace.left) / wsW, 0.048),
      closeTo((composer.top - workspace.top) / wsH, 0.796),
      closeTo(composer.width / wsW, 0.904),
      closeTo(composer.height / wsH, 0.177),
      closeTo((hamburgerBox.left - workspace.left) / wsW, 0.872),
      closeTo((hamburgerBox.top - workspace.top) / wsH, 0.013),
      closeTo(hamburgerBox.width / wsW, 0.094),
      closeTo(hamburgerBox.height / wsH, 0.053),
    ].every(Boolean);
    const hamburgerPresent = hamburger !== null &&
      hamburger.getAttribute('aria-expanded') === 'false' &&
      hamburgerBox !== null &&
      hamburgerBox.width > 0 &&
      hamburgerBox.height > 0;
    const composerVisible = composer.width > 0 && composer.height > 0;
    return {
      aperturesTolerance,
      brandAligned:
        closeTo((brand.top - workspace.top) / wsH, 0.013) &&
        closeTo(brand.height / wsH, 0.053) &&
        Math.abs(
          brandLink.left + brandLink.width / 2 -
          (workspace.left + workspace.width / 2)
        ) < 2,
      brandVisible:
        getComputedStyle(document.querySelector('.jrpg-brand')).display ===
          'grid' &&
        document.querySelector('.jrpg-brand').textContent.includes('MrJuneJune'),
      composerContentAligned:
        composerForm.left >= composer.left &&
        composerForm.top >= composer.top &&
        composer.right - composerForm.right <= 8 &&
        composer.bottom - composerForm.bottom <= 8 &&
        composerForm.left - composer.left <= 8 &&
        composerForm.top - composer.top <= 8 &&
        sendButton.bottom < composerTextarea.bottom &&
        composerTextarea.bottom - sendButton.bottom <= 16,
      composerGeometry: {
        composer: {
          bottom: composer.bottom,
          left: composer.left,
          right: composer.right,
          top: composer.top,
        },
        form: {
          bottom: composerForm.bottom,
          left: composerForm.left,
          right: composerForm.right,
          top: composerForm.top,
        },
        send: {
          bottom: sendButton.bottom,
          left: sendButton.left,
          right: sendButton.right,
          top: sendButton.top,
        },
        textarea: {
          bottom: composerTextarea.bottom,
          left: composerTextarea.left,
          right: composerTextarea.right,
          top: composerTextarea.top,
        },
      },
      composerFieldUsesSpace:
        composerTextarea.left === composerForm.left &&
        composerTextarea.right === composerForm.right &&
        sendButton.left > composerTextarea.left &&
        sendButton.right < composerTextarea.right &&
        sendButton.top > composerTextarea.top &&
        sendButton.bottom < composerTextarea.bottom,
      composerLabelHidden:
        composerLabel.getBoundingClientRect().width <= 1 &&
        composerLabel.getBoundingClientRect().height <= 1,
      composerVisible,
      dialogClosed: !dialog.open,
      fillsViewport: shell.left === 0 &&
        shell.top === 0 &&
        Math.abs(shell.right - innerWidth) < 1 &&
        Math.abs(shell.bottom - innerHeight) < 1,
      fillsWidth: shell.left === 0 &&
        Math.abs(shell.right - innerWidth) < 1,
      hamburgerPresent,
      initialScroll: scrollY,
      mediaMatches: matchMedia(
        '(max-width: 52rem) and (orientation: portrait)',
      ).matches,
      menuInModal: menuElement.parentElement === destinationContent,
      menuHidden: menu.width === 0 && menu.height === 0,
      modalOwner: destinationContent.closest('mjj-window-modal')?.localName,
      mobileArtIsCorrect,
      noOverflow: document.documentElement.scrollWidth <= innerWidth,
      sendButtonCompact:
        Math.abs(sendButton.width - sendButton.height) < 1 &&
        sendButton.width <= 44 &&
        getComputedStyle(sendLabel).display === 'none',
      telemetryHidden: getComputedStyle(
        document.querySelector('.jrpg-frame-telemetry'),
      ).display === 'none',
      turnNavigationAligned:
        Math.abs(
          turnPosition.left + turnPosition.width / 2 -
          (workspace.left + workspace.width / 2)
        ) < 2 &&
        Math.abs(
          previousTurn.top + previousTurn.height / 2 -
          (nextTurn.top + nextTurn.height / 2)
        ) < 1 &&
        previousTurn.left < turnPosition.left &&
        nextTurn.right > turnPosition.right,
      turnNavigationGeometry: {
        nextCenter: nextTurn.left + nextTurn.width / 2,
        positionCenter: turnPosition.left + turnPosition.width / 2,
        previousCenter: previousTurn.left + previousTurn.width / 2,
        workspaceCenter: workspace.left + workspace.width / 2,
      },
      topLeftClear: document.elementFromPoint(10, 10)?.classList
        .contains('jrpg-workspace'),
      utilityHidden: utility.width === 0 && utility.height === 0,
      utilityInModal: document.querySelector('.jrpg-utility').parentElement ===
        destinationContent,
      usesCyberScroll:
        destinationContent.localName === 'zen-scroll-area' &&
        destinationContent.hasAttribute('data-cyber-scroll') &&
        getComputedStyle(destinationContent).scrollbarColor !== 'auto',
      usesDesignSystemControls:
        !composerField.hasAttribute('appearance') &&
        !sendOwner.hasAttribute('appearance'),
      workspaceFills: Math.abs(workspace.left) < 1 &&
        Math.abs(workspace.top) < 1 &&
        Math.abs(workspace.right - innerWidth) < 1 &&
        Math.abs(workspace.bottom - innerHeight) < 1,
    };
  });
  assert.equal(mobile.aperturesTolerance, true, 'mobile apertures within tolerance at 360x640');
  assert.equal(mobile.brandAligned, true, 'MrJuneJune aligns in the mobile top bar');
  assert.equal(mobile.brandVisible, true, 'MrJuneJune is visible on mobile');
  assert.equal(
    mobile.composerContentAligned,
    true,
    `composer content aligns inside frame aperture: ${JSON.stringify(mobile.composerGeometry)}`,
  );
  assert.equal(mobile.composerFieldUsesSpace, true, 'composer field fills space beside send control');
  assert.equal(mobile.composerLabelHidden, true, 'Message label remains accessible but not visible');
  assert.equal(mobile.composerVisible, true, 'composer visible');
  assert.equal(mobile.dialogClosed, true, 'destination modal starts closed');
  assert.equal(mobile.fillsViewport, true, 'shell fills viewport');
  assert.equal(mobile.fillsWidth, true, 'shell fills width');
  assert.equal(mobile.hamburgerPresent, true, 'top-right menu trigger starts collapsed');
  assert.equal(mobile.initialScroll, 0, 'no initial scroll');
  assert.equal(mobile.mobileArtIsCorrect, true, 'mobile background URL is background-frame-mobile.webp');
  assert.equal(mobile.menuHidden, true, 'menu stays out of the two-aperture frame');
  assert.equal(
    mobile.menuInModal,
    true,
    `menu is hosted by the mobile modal: ${JSON.stringify(mobile)}`,
  );
  assert.equal(mobile.noOverflow, true, 'no horizontal overflow at 360x640');
  assert.equal(mobile.sendButtonCompact, true, 'mobile send control is a compact square icon button');
  assert.equal(mobile.telemetryHidden, true, 'mobile frame omits desktop telemetry');
  assert.equal(
    mobile.turnNavigationAligned,
    true,
    `turn navigation is centered and level: ${JSON.stringify(mobile.turnNavigationGeometry)}`,
  );
  assert.equal(mobile.utilityHidden, true, 'destination view stays out of the frame');
  assert.equal(mobile.utilityInModal, true, 'destination view is hosted by the mobile modal');
  assert.equal(mobile.modalOwner, 'mjj-window-modal');
  assert.equal(mobile.usesCyberScroll, true, 'JRPG modal uses the shared cyber scroll area');
  assert.equal(mobile.usesDesignSystemControls, true, 'composer uses styled Zenbu controls');
  assert.equal(mobile.workspaceFills, true, 'workspace fills full viewport');
  assert.equal(mobile.topLeftClear, true, 'hidden dialog triggers do not cover frame art');

  // The top-right trigger opens the modal menu.
  await mobilePage.locator('[data-mobile-menu-toggle]').click();
  await mobilePage.locator('[data-mobile-destination-dialog]').waitFor({
    state: 'visible',
  });
  await mobilePage.locator('[data-mobile-destination-dialog]').evaluate(dialog =>
    Promise.all(dialog.getAnimations().map(animation => animation.finished))
  );
  assert.equal(
    await mobilePage.locator('[data-mobile-menu-toggle]')
      .getAttribute('aria-expanded'),
    'true',
    'menu trigger reflects the open modal',
  );
  assert.equal(
    await mobilePage.locator('mjj-jrpg-menu').isVisible(),
    true,
    'destination menu is visible inside the modal',
  );

  // Every destination replaces the menu inside the same modal.
  await mobilePage.locator('button[data-preview="conversations"]').click();
  await mobilePage.locator('mjj-conversation-archive').waitFor({
    state: 'visible',
  });
  assert.equal(
    await mobilePage.locator('[data-mobile-destination-title]').textContent(),
    'Conversations',
    'conversation archive is a named modal destination',
  );
  assert.equal(
    await mobilePage.locator('[data-mobile-destination-dialog]').isVisible(),
    true,
    'conversation archive remains in the modal',
  );
  assert.equal(
    await mobilePage.locator('.jrpg-mobile-destination-header').evaluate(
      header => {
        const items = [...header.children].filter(
          item => getComputedStyle(item).display !== 'none',
        );
        const centers = items.map(item => {
          const rect = item.getBoundingClientRect();
          return rect.top + rect.height / 2;
        });
        return Math.max(...centers) - Math.min(...centers) < 2;
      },
    ),
    true,
    'mobile modal header controls share one row',
  );

  await mobilePage.locator('[data-mobile-destination-back]').click();
  assert.equal(
    await mobilePage.locator('mjj-jrpg-menu').isVisible(),
    true,
    'modal back action returns to destinations',
  );
  await mobilePage.locator('button[data-preview="resume"]').click();
  assert.equal(
    await mobilePage.locator('[data-preview-content]').isVisible(),
    true,
    'resume view renders in the mobile modal',
  );
  assert.equal(
    await mobilePage.locator('[data-mobile-destination-title]').textContent(),
    'Resume',
    'modal title follows the selected destination',
  );
  await mobilePage.getByRole('button', {
    name: 'Close destination menu',
  }).click();
  await mobilePage.locator('[data-mobile-destination-dialog]').waitFor({
    state: 'hidden',
  });
  assert.ok(
    await mobilePage.locator('[data-mobile-menu-toggle]').evaluate(el =>
      el.tabIndex >= 0
    ),
    'mobile menu trigger remains keyboard accessible',
  );
  await mobileContext.close();

  // Test at 390x844
  const mobile390Context = await browser.newContext({
    colorScheme: 'light',
    viewport: { width: 390, height: 844 },
  });
  const mobile390Page = await mobile390Context.newPage();
  await mobile390Page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
  const mobile390 = await mobile390Page.evaluate(() => {
    const workspace = document.querySelector('.jrpg-workspace')
      .getBoundingClientRect();
    const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
    const mobileArt = getComputedStyle(
      document.querySelector('.jrpg-workspace'),
    ).backgroundImage;
    return {
      fillsViewport: shell.left === 0 &&
        shell.top === 0 &&
        Math.abs(shell.right - innerWidth) < 1 &&
        Math.abs(shell.bottom - innerHeight) < 1,
      mobileArtIsCorrect: mobileArt.includes('background-frame-mobile.webp'),
      noOverflow: document.documentElement.scrollWidth <= innerWidth,
      workspaceFills: Math.abs(workspace.left) < 1 &&
        Math.abs(workspace.top) < 1 &&
        Math.abs(workspace.right - innerWidth) < 1 &&
        Math.abs(workspace.bottom - innerHeight) < 1,
    };
  });
  assert.equal(mobile390.fillsViewport, true, 'fills viewport at 390x844');
  assert.equal(mobile390.mobileArtIsCorrect, true, 'mobile art at 390x844');
  assert.equal(mobile390.noOverflow, true, 'no overflow at 390x844');
  assert.equal(mobile390.workspaceFills, true, 'workspace fills at 390x844');
  await mobile390Context.close();

  // Test at 320x568 (narrow)
  const mobile320Context = await browser.newContext({
    colorScheme: 'light',
    viewport: { width: 320, height: 568 },
  });
  const mobile320Page = await mobile320Context.newPage();
  await mobile320Page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
  const mobile320 = await mobile320Page.evaluate(() => {
    const workspace = document.querySelector('.jrpg-workspace')
      .getBoundingClientRect();
    const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
    const composer = document.querySelector('mjj-composer')
      .getBoundingClientRect();
    const menu = document.querySelector('mjj-jrpg-menu').getBoundingClientRect();
    const mobileArt = getComputedStyle(
      document.querySelector('.jrpg-workspace'),
    ).backgroundImage;
    return {
      composerPresent: composer.width > 0 && composer.height > 0,
      fillsViewport: shell.left === 0 &&
        shell.top === 0 &&
        Math.abs(shell.right - innerWidth) < 1 &&
        Math.abs(shell.bottom - innerHeight) < 1,
      menuHidden: menu.width === 0 && menu.height === 0,
      mobileArtIsCorrect: mobileArt.includes('background-frame-mobile.webp'),
      noOverflow: document.documentElement.scrollWidth <= innerWidth,
      workspaceFills: Math.abs(workspace.left) < 1 &&
        Math.abs(workspace.top) < 1 &&
        Math.abs(workspace.right - innerWidth) < 1 &&
        Math.abs(workspace.bottom - innerHeight) < 1,
    };
  });
  assert.equal(mobile320.composerPresent, true, 'composer present at 320x568');
  assert.equal(mobile320.fillsViewport, true, 'fills viewport at 320x568');
  assert.equal(mobile320.menuHidden, true, 'menu remains modal-only at 320x568');
  assert.equal(mobile320.mobileArtIsCorrect, true, 'mobile art at 320x568');
  assert.equal(mobile320.noOverflow, true, 'no overflow at 320x568');
  assert.equal(mobile320.workspaceFills, true, 'workspace fills at 320x568');
  await mobile320Context.close();

  const shortContext = await browser.newContext({
    colorScheme: 'dark',
    viewport: { width: 900, height: 500 },
  });
  const shortPage = await shortContext.newPage();
  await shortPage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
  const shortLayout = await shortPage.evaluate(() => {
    const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
    const character = document.querySelector('mjj-jrpg-character')
      .getBoundingClientRect();
    const chat = document.querySelector('mjj-jrpg-chat')
      .getBoundingClientRect();
    return {
      characterInside: character.left >= scene.left &&
        character.right <= scene.right,
      chatInside: chat.left >= scene.left && chat.right <= scene.right,
      noSceneOverflow: document.querySelector('.jrpg-scene').scrollWidth <=
        document.querySelector('.jrpg-scene').clientWidth,
    };
  });
  assert.deepEqual(shortLayout, {
    characterInside: true,
    chatInside: true,
    noSceneOverflow: true,
  });
  await shortContext.close();

  const forcedColorsContext = await browser.newContext({
    forcedColors: 'active',
    viewport: { width: 1280, height: 900 },
  });
  const forcedColorsPage = await forcedColorsContext.newPage();
  await forcedColorsPage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
  assert.deepEqual(
    await forcedColorsPage.evaluate(() => ({
      sceneBar: getComputedStyle(
        document.querySelector('.jrpg-scene'),
        '::before',
      ).backgroundImage,
      workspace: getComputedStyle(
        document.querySelector('.jrpg-workspace'),
      ).backgroundImage,
    })),
    {
      sceneBar: 'none',
      workspace: 'none',
    },
  );
  await forcedColorsContext.close();
}

async function testHlsPlayer(browser, siteRoot) {
  const page = await browser.newPage();
  const errors = [];
  const mediaRequests = [];
  let testingExpectedFailure = false;
  let testingExpectedReload = false;
  page.on('pageerror', error => errors.push(`pageerror: ${error.message}`));
  page.on('console', message => {
    if (message.type() !== 'error') return;
    if (testingExpectedFailure &&
        message.text().startsWith('Failed to load resource:')) return;
    errors.push(`console: ${message.text()}`);
  });
  page.on('requestfailed', request => {
    if (testingExpectedReload &&
        request.failure()?.errorText === 'net::ERR_ABORTED') return;
    errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
  });
  page.on('request', request => {
    if (/\.(?:m3u8|m4s|mp4)(?:\?|$)/.test(request.url())) {
      mediaRequests.push(request.url());
    }
  });

  const playlistResponse = await fetch(
    `${baseUrl}/public/hls-sample/master.m3u8`,
  );
  assert.equal(playlistResponse.status, 200);
  assert.match(
    playlistResponse.headers.get('content-type') || '',
    /^application\/vnd\.apple\.mpegurl/,
  );
  assert.match(
    await playlistResponse.text(),
    /#EXT-X-STREAM-INF:.*CODECS="vp09\.00\.10\.08,opus"/,
  );

  const variantResponse = await fetch(
    `${baseUrl}/public/hls-sample/vp9-stream.m3u8`,
  );
  assert.equal(variantResponse.status, 200);
  assert.match(
    await variantResponse.text(),
    /#EXT-X-MAP:URI="vp9-init\.mp4"/,
  );

  const segmentResponse = await fetch(
    `${baseUrl}/public/hls-sample/vp9-segment000.m4s`,
  );
  assert.equal(segmentResponse.status, 200);
  assert.match(
    segmentResponse.headers.get('content-type') || '',
    /^video\/iso\.segment/,
  );
  const transportStreamResponse = await fetch(
    `${baseUrl}/public/hls-sample/h264-ts-segment000.ts`,
  );
  assert.equal(transportStreamResponse.status, 200);
  assert.match(
    transportStreamResponse.headers.get('content-type') || '',
    /^video\/mp2t/,
  );
  assert.ok((await transportStreamResponse.arrayBuffer()).byteLength > 0);

  await page.goto(
    `${baseUrl}/tools/hls_player?url=${encodeURIComponent('/public/hls-sample/master.m3u8')}`,
    {
      waitUntil: 'networkidle',
    },
  );
  await page.getByRole('button', { name: 'Sample', exact: true }).waitFor();
  assert.equal(
    await page.locator('#hlsUrl').evaluate(input => input.defaultValue),
    '/public/hls-sample/h264-ts-stream.m3u8',
  );
  await page.waitForFunction(() => {
    const status = document.querySelector('#hlsStatus');
    return status?.dataset.state === 'ready' ||
      status?.dataset.state === 'error';
  }, null, { timeout: 15000 });
  const terminalState = await page.locator('#hlsStatus').getAttribute('data-state');
  if (terminalState !== 'ready') {
    throw new Error(
      `HLS player failed: ${await page.locator('#hlsStatus').textContent()}\n${errors.join('\n')}`,
    );
  }
  assert.match(await page.locator('#hlsStatus').textContent(), /Stream ready/);
  assert.equal(
    await page.locator('[data-detail="mode"]').textContent(),
    'hls.js',
  );
  assert.match(
    await page.locator('[data-detail="segments"]').textContent(),
    /Managed by hls\.js|adaptive levels/,
  );
  await page.waitForFunction(() => {
    const video = document.querySelector('#hlsVideo');
    return video.readyState >= HTMLMediaElement.HAVE_METADATA &&
      Number.isFinite(video.duration) &&
      video.duration >= 14;
  });
  const firstFrame = await page.locator('#hlsVideo').evaluate(video => {
    const canvas = document.createElement('canvas');
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    const context = canvas.getContext('2d');
    context.drawImage(video, 0, 0);
    const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
    let total = 0;
    for (let index = 0; index < pixels.length; index += 4) {
      total += pixels[index] + pixels[index + 1] + pixels[index + 2];
    }
    return total / (canvas.width * canvas.height * 3);
  });
  assert.ok(firstFrame > 10, `Initial HLS frame is black: ${firstFrame}`);
  await page.locator('#hlsVideo').evaluate(video => video.play());
  await page.waitForFunction(() => document.querySelector('#hlsVideo').currentTime > 0.2);
  await page.locator('#hlsVideo').evaluate(video => video.pause());
  assert.ok(mediaRequests.some(url => url.endsWith('/hls-sample/master.m3u8')));
  assert.ok(mediaRequests.some(url => url.endsWith('-stream.m3u8')));
  assert.ok(mediaRequests.some(url => url.endsWith('-init.mp4')));
  assert.ok(mediaRequests.some(url => /-segment\d+\.m4s$/.test(url)));

  await page.evaluate(() => {
    window.hlsPlayer.hls.trigger(window.Hls.Events.ERROR, {
      fatal: true,
      type: window.Hls.ErrorTypes.MEDIA_ERROR,
      details: 'testRuntimeFailure',
      error: new Error('runtime segment failed'),
    });
  });
  await page.locator('#hlsStatus[data-state="error"]').waitFor();
  assert.match(
    await page.locator('#hlsStatus').textContent(),
    /runtime segment failed/,
  );
  await page.locator('#hlsUrl').fill('/public/hls-sample/master.m3u8');
  await page.getByRole('button', { name: 'Load', exact: true }).click();
  await page.locator('#hlsStatus[data-state="ready"]').waitFor();

  let delayedSegment = true;
  await page.route('**/hls-sample/vp9-segment000.m4s', async route => {
    if (delayedSegment) {
      delayedSegment = false;
      await new Promise(resolve => setTimeout(resolve, 250));
    }
    await route.continue();
  });
  testingExpectedReload = true;
  await page.locator('#hlsUrl').fill('/public/hls-sample/master.m3u8');
  await page.getByRole('button', { name: 'Load', exact: true }).click();
  await page.waitForTimeout(25);
  await page.getByRole('button', { name: 'Load', exact: true }).click();
  await page.locator('#hlsStatus[data-state="ready"]').waitFor();
  testingExpectedReload = false;
  assert.match(await page.locator('#hlsStatus').textContent(), /Stream ready/);

  await page.evaluate(() => {
    window.__hlsObjectUrls = { created: [], revoked: [] };
    const createObjectURL = URL.createObjectURL.bind(URL);
    const revokeObjectURL = URL.revokeObjectURL.bind(URL);
    URL.createObjectURL = value => {
      const url = createObjectURL(value);
      window.__hlsObjectUrls.created.push(url);
      return url;
    };
    URL.revokeObjectURL = url => {
      window.__hlsObjectUrls.revoked.push(url);
      revokeObjectURL(url);
    };
  });
  const localFiles = listFiles(path.join(siteRoot, 'public/hls-sample'));
  await page.locator('#hlsFiles').setInputFiles(localFiles);
  await page.waitForFunction(() =>
    document.querySelector('#hlsUrl')?.value.startsWith('Local: ')
  );
  await page.locator('#hlsStatus[data-state="ready"]').waitFor();
  assert.match(await page.locator('#hlsUrl').inputValue(), /^Local: /);
  assert.equal(
    await page.locator('[data-detail="mode"]').textContent(),
    'hls.js',
  );
  await page.locator('#hlsVideo').evaluate(video => video.play());
  await page.waitForFunction(() => document.querySelector('#hlsVideo').currentTime > 0.3);
  await page.locator('#hlsVideo').evaluate(video => video.pause());
  const incompleteLocalFiles = localFiles.filter(file =>
    file.endsWith('master.m3u8') ||
    file.endsWith('vp9-stream.m3u8')
  );
  await page.locator('#hlsFiles').setInputFiles(incompleteLocalFiles);
  await page.locator('#hlsStatus[data-state="error"]').waitFor();
  assert.match(
    await page.locator('#hlsStatus').textContent(),
    /Local HLS file is missing/,
  );
  const objectUrlCounts = await page.evaluate(() => ({
    created: window.__hlsObjectUrls.created.length,
    revoked: window.__hlsObjectUrls.revoked.length,
  }));
  assert.ok(objectUrlCounts.created > 10, JSON.stringify(objectUrlCounts));
  assert.ok(
    objectUrlCounts.revoked >= objectUrlCounts.created,
    JSON.stringify(objectUrlCounts),
  );

  await page.route('**/invalid.m3u8', route => route.fulfill({
    status: 404,
    body: 'not found',
  }));
  testingExpectedFailure = true;
  await page.locator('#hlsUrl').fill(`${baseUrl}/invalid.m3u8`);
  await page.getByRole('button', { name: 'Load', exact: true }).click();
  await page.locator('#hlsStatus[data-state="error"]').waitFor();
  assert.match(
    await page.locator('#hlsStatus').textContent(),
    /404|manifestLoadError|Playlist request failed/,
  );

  if (errors.length) throw new Error(errors.join('\n'));
  await page.close();
}

function findFreePort() {
  return new Promise((resolve, reject) => {
    const socket = net.createServer();
    socket.once('error', reject);
    socket.listen(0, '127.0.0.1', () => {
      const address = socket.address();
      socket.close(error => {
        if (error) reject(error);
        else resolve(String(address.port));
      });
    });
  });
}

/* ------------------------------------------------------------------ */
/* Conversation URL routing regression tests (Fixes 1–4)              */
/* ------------------------------------------------------------------ */

async function testRoleGreetings(browser) {
  const cases = [
    {
      name: 'guest',
      session: {
        kind: 'guest',
        csrfToken: 'guest-greeting-csrf',
        quota: null,
      },
      expected: 'Hi, my name is Epi. June usually calls me Epi-chan.',
    },
    {
      name: 'invited',
      session: {
        kind: 'user',
        username: 'friend',
        role: 'member',
        mustChangePassword: false,
        csrfToken: 'friend-greeting-csrf',
        quota: null,
      },
      expected: 'Since June invited you, I can be a little more casual.',
    },
    {
      name: 'admin',
      session: {
        kind: 'user',
        username: 'june',
        role: 'admin',
        mustChangePassword: false,
        csrfToken: 'admin-greeting-csrf',
        quota: null,
      },
      expected: 'Hi June! I’m Epi-chan.',
    },
  ];

  for (const testCase of cases) {
    const context = await browser.newContext({ serviceWorkers: 'block' });
    const page = await context.newPage();
    await page.route('**/api/auth/session', route => route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify(testCase.session),
    }));
    await page.route('**/api/conversations**', route => route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ conversations: [], cursor: null }),
    }));
    await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'domcontentloaded' });
    const greeting = page.locator('[data-greeting]');
    await greeting.waitFor({ state: 'visible' });
    if (testCase.name === 'guest') {
      await page.locator('[data-greeting][data-streaming="true"]').waitFor({
        state: 'visible',
      });
    }
    await page.waitForFunction(() => {
      const item = document.querySelector('[data-greeting]');
      return item && !item.hasAttribute('data-streaming');
    });
    assert.match(
      await greeting.locator('p').textContent(),
      new RegExp(testCase.expected.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
      `${testCase.name} receives its role-specific Epi greeting`,
    );
    await context.close();
  }
}

async function testConversationRouting(browser) {
  const CONV_A = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa';
  const CONV_B = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb';
  const GUEST_SESSION = {
    kind: 'guest', csrfToken: 'test-csrf',
    quota: {
      turnsLimit: 10, turnsUsed: 0, turnsRemaining: 10,
      outputTokensLimit: 10000, outputTokensUsed: 0,
      outputTokensReserved: 0, outputTokensRemaining: 10000,
      resetsAt: Math.floor(Date.now() / 1000) + 86400,
    },
  };

  /* Returning without a conversation URL stays fresh until the user chooses one. */
  {
    const context = await browser.newContext({ serviceWorkers: 'block' });
    await context.addInitScript(id => {
      sessionStorage.setItem('mjj-jrpg-conversation-id', id);
    }, CONV_A);
    const page = await context.newPage();
    let detailRequestCount = 0;
    await page.route('**/api/auth/session', route => route.fulfill({
      status: 200, contentType: 'application/json',
      body: JSON.stringify(GUEST_SESSION),
    }));
    await page.route('**/api/conversations**', route => {
      const url = new URL(route.request().url());
      if (url.pathname === '/api/conversations') {
        return route.fulfill({
          status: 200, contentType: 'application/json',
          body: JSON.stringify({
            conversations: [{
              id: CONV_A,
              title: 'Earlier quest',
              turn_count: 2,
            }],
            cursor: null,
          }),
        });
      }
      return route.fulfill({
        status: 404, contentType: 'application/json',
        body: JSON.stringify({ error: { message: 'Not found' } }),
      });
    });
    await page.route(`**/api/conversations/${CONV_A}`, route => {
      detailRequestCount += 1;
      return route.fulfill({
        status: 200, contentType: 'application/json',
        body: JSON.stringify({
          id: CONV_A,
          title: 'Earlier quest',
          turns: [{ role: 'assistant', content: 'Welcome back.' }],
        }),
      });
    });

    await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
    assert.equal(detailRequestCount, 0, 'latest conversation is not opened automatically');
    assert.equal(
      new URL(page.url()).searchParams.has('conversation'),
      false,
      'returning visit remains a fresh conversation URL',
    );
    await page.waitForFunction(() => {
      const greeting = document.querySelector('[data-greeting] p');
      return greeting?.textContent.includes('Open Conversations');
    });
    assert.equal(
      await page.evaluate(() =>
        sessionStorage.getItem('mjj-jrpg-conversation-id')),
      null,
      'stale active conversation state is cleared',
    );

    await page.locator('button[data-preview="conversations"]').click();
    const selectedConversation = page.waitForResponse(response =>
      new URL(response.url()).pathname === `/api/conversations/${CONV_A}`);
    await page.locator('[data-conv-open]').click();
    assert.equal((await selectedConversation).status(), 200);
    assert.ok(detailRequestCount >= 1, 'conversation opens after explicit selection');
    await page.waitForFunction(id =>
      new URL(window.location.href).searchParams.get('conversation') === id,
    CONV_A);
    assert.equal(
      new URL(page.url()).searchParams.get('conversation'),
      CONV_A,
      'explicit selection writes the conversation URL',
    );
    await context.close();
  }

  /* ---- Fix 3: malformed ?conversation param must not fall back to sessionStorage ---- */
  {
    const storedId = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc';
    const context = await browser.newContext({ serviceWorkers: 'block' });
    await context.addInitScript(id => {
      sessionStorage.setItem('mjj-jrpg-conversation-id', id);
    }, storedId);
    const page = await context.newPage();
    const storedIdFetched = [];
    await page.route('**/api/auth/session', route => route.fulfill({
      status: 200, contentType: 'application/json',
      body: JSON.stringify(GUEST_SESSION),
    }));
    await page.route('**/api/conversations**', route => {
      const url = new URL(route.request().url());
      if (url.pathname.endsWith(`/${storedId}`)) {
        storedIdFetched.push(route.request().url());
      }
      if (url.pathname === '/api/conversations') {
        return route.fulfill({
          status: 200, contentType: 'application/json',
          body: JSON.stringify({ conversations: [], cursor: null }),
        });
      }
      return route.fulfill({
        status: 404, contentType: 'application/json',
        body: JSON.stringify({ error: { message: 'Not found' } }),
      });
    });
    await page.route('**/api/conversations/claim', route => route.fulfill({
      status: 503,
      contentType: 'application/json',
      body: JSON.stringify({ error: { message: 'Service unavailable' } }),
    }));
    await page.goto(`${baseUrl}/jrpg?conversation=not-a-valid-uuid`, {
      waitUntil: 'networkidle',
    });
    assert.equal(
      storedIdFetched.length, 0,
      'Fix 3: malformed ?conversation param must not trigger sessionStorage fallback',
    );
    await context.close();
  }

  /* ---- Fix 4: legacy claim: key retained on 5xx, button re-renders and re-fires ---- */
  {
    const legacyStoredId = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd';
    const context = await browser.newContext({ serviceWorkers: 'block' });
    /* Seed CONVERSATION_STORAGE_KEY so init migrates it to CONVERSATION_LEGACY_KEY */
    await context.addInitScript(id => {
      sessionStorage.setItem('mjj-jrpg-conversation-id', id);
    }, legacyStoredId);
    const page = await context.newPage();
    const pageErrors = [];
    page.on('pageerror', e => {
      if (!e.message.startsWith('Failed to load resource:')) {
        pageErrors.push(`pageerror: ${e.message}`);
      }
    });
    await page.route('**/api/auth/session', route => route.fulfill({
      status: 200, contentType: 'application/json',
      body: JSON.stringify({
        kind: 'user', username: 'tester', role: 'member',
        csrfToken: 'test-csrf', quota: null,
      }),
    }));
    await page.route('**/api/conversations**', route => {
      const url = new URL(route.request().url());
      const req = route.request();
      if (url.pathname === '/api/conversations' && req.method() === 'GET') {
        return route.fulfill({
          status: 200, contentType: 'application/json',
          body: JSON.stringify({ conversations: [], cursor: null }),
        });
      }
      if (url.pathname === `/api/conversations/${legacyStoredId}` && req.method() === 'GET') {
        return route.fulfill({
          status: 404, contentType: 'application/json',
          body: JSON.stringify({ error: { message: 'Not found' } }),
        });
      }
      if (url.pathname === '/api/conversations/claim' && req.method() === 'POST') {
        return route.fulfill({
          status: 503, contentType: 'application/json',
          body: JSON.stringify({ error: { message: 'Service unavailable' } }),
        });
      }
      return route.fulfill({
        status: 404, contentType: 'application/json',
        body: JSON.stringify({ error: { message: 'Not found' } }),
      });
    });
    await page.route('**/api/conversations/claim', route => route.fulfill({
      status: 503,
      contentType: 'application/json',
      body: JSON.stringify({ error: { message: 'Service unavailable' } }),
    }));
    await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });

    /* Open archive panel so the legacy claim status is visible */
    await page.locator('button[data-preview="conversations"]').click();
    await page.locator('mjj-conversation-archive').waitFor({ state: 'visible' });

    /* Init should have called showLegacyClaim() — wait for the button */
    await page.waitForFunction(
      () => document.querySelector('[data-archive-claim]') !== null,
      null, { timeout: 5000 },
    );

    /* First claim attempt — returns 503 */
    const firstClaimResponse = page.waitForResponse(
      response => response.url().includes('/api/conversations/claim'),
    );
    await page.locator('[data-archive-claim]').click();
    const firstClaimStatus = (await firstClaimResponse).status();
    assert.equal(
      firstClaimStatus,
      503,
      `claim fixture returns the mocked service failure, got ${firstClaimStatus}`,
    );

    /* Wait for the retry state to appear: showLegacyClaim re-rendered with error msg */
    await page.waitForFunction(
      () => {
        const status = document.querySelector('[data-archive-status]');
        return status && !status.hidden && status.textContent.includes('Claim failed');
      },
      null, { timeout: 5000 },
    );

    /* Key must be retained in sessionStorage on 5xx */
    const keyAfterFail = await page.evaluate(
      () => sessionStorage.getItem('mjj-jrpg-legacy-claim'),
    );
    assert.equal(
      keyAfterFail, legacyStoredId,
      'Fix 4: CONVERSATION_LEGACY_KEY must be retained after 5xx failure',
    );

    /* Button must be present and enabled for retry */
    await page.locator('[data-archive-claim]').waitFor({ state: 'visible' });

    /* Tag the current button so we can detect when it's replaced by a second re-render */
    await page.locator('[data-archive-claim]').evaluate(btn => {
      btn.dataset.claimGen = '1';
    });

    /* Second click: must dispatch event again — proves no {once:true} on button */
    await page.locator('[data-archive-claim]').click();

    /* Wait for the button to be replaced (showLegacyClaim called again = handler ran) */
    await page.waitForFunction(
      () => {
        const btn = document.querySelector('[data-archive-claim]');
        return btn !== null && btn.dataset.claimGen !== '1';
      },
      null, { timeout: 5000 },
    );

    assert.deepEqual(pageErrors, [], 'no page errors in legacy claim test');
    await context.close();
  }

  /* ---- Fix 1: popstate during active stream converges URL and UI ---- */
  {
    let releaseStream = () => {};
    const streamGate = new Promise(resolve => { releaseStream = resolve; });

    const context = await browser.newContext();
    const page = await context.newPage();
    const pageErrors = [];
    page.on('pageerror', e => {
      if (!e.message.startsWith('Failed to load resource:')) {
        pageErrors.push(`pageerror: ${e.message}`);
      }
    });
    /* Suppress expected abort-network errors from the held-then-aborted stream */
    page.on('requestfailed', request => {
      if (request.failure()?.errorText === 'net::ERR_ABORTED') return;
      pageErrors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
    });

    await page.route('**/api/auth/session', route => route.fulfill({
      status: 200, contentType: 'application/json',
      body: JSON.stringify(GUEST_SESSION),
    }));
    await page.route('**/api/conversations**', async route => {
      const url = new URL(route.request().url());
      const req = route.request();
      if (url.pathname === '/api/conversations' && req.method() === 'GET') {
        return route.fulfill({
          status: 200, contentType: 'application/json',
          body: JSON.stringify({ conversations: [], cursor: null }),
        });
      }
      if (url.pathname === '/api/conversations' && req.method() === 'POST') {
        return route.fulfill({
          status: 201, contentType: 'application/json',
          body: JSON.stringify({ id: CONV_A }),
        });
      }
      if (url.pathname.endsWith('/turns') && req.method() === 'POST') {
        /* Hold the SSE stream until the test releases it */
        await streamGate;
        try { await route.abort(); } catch { /* request may already be aborted */ }
        return;
      }
      return route.fulfill({
        status: 404, contentType: 'application/json',
        body: JSON.stringify({ error: { message: 'Not found' } }),
      });
    });

    await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
    await page.waitForFunction(
      () => !document.querySelector('mjj-composer textarea')?.disabled,
    );

    /* Submit — creates conversation via pushState and starts stream */
    await page.locator('#jrpg-message').fill('routing race test');
    await page.locator('#jrpg-message').press('Enter');

    /* Stream is now active */
    await page.locator('[data-composer-cancel-owner]').waitFor({ state: 'visible' });

    const urlDuringStream = page.url();
    assert.ok(
      urlDuringStream.includes('conversation='),
      `Fix 1: URL must have ?conversation during stream: ${urlDuringStream}`,
    );

    /* Navigate back while stream is active — URL already changes here */
    await page.goBack();

    /* Stream abort + pending-popstate application: cancel control disappears */
    await page.locator('[data-composer-cancel-owner]').waitFor({ state: 'hidden' });

    /* URL and UI must converge to the pre-stream state */
    const finalUrl = page.url();
    assert.ok(
      !finalUrl.includes('conversation='),
      `Fix 1: URL must not have ?conversation after popstate back: ${finalUrl}`,
    );
    assert.equal(
      await page.locator(
        '.jrpg-message[data-streaming="true"]:not([data-greeting])',
      ).count(),
      0,
      'Fix 1: no streaming message must remain after popstate back',
    );

    releaseStream(); /* clean up the held route */
    assert.deepEqual(pageErrors, [], 'no page errors in popstate-during-stream test');
    await context.close();
  }

  /* ---- Fix 2: openSeq incremented on no-conv popstate, invalidates pending open ---- */
  {
    let resolveConvBFetch = () => {};
    const convBFetchGate = new Promise(resolve => { resolveConvBFetch = resolve; });

    const context = await browser.newContext();
    const page = await context.newPage();
    const pageErrors = [];
    page.on('pageerror', e => {
      if (!e.message.startsWith('Failed to load resource:')) {
        pageErrors.push(`pageerror: ${e.message}`);
      }
    });

    await page.route('**/api/auth/session', route => route.fulfill({
      status: 200, contentType: 'application/json',
      body: JSON.stringify(GUEST_SESSION),
    }));
    await page.route('**/api/conversations**', async route => {
      const url = new URL(route.request().url());
      const req = route.request();
      /* Empty archive: no auto-open, no gate deadlock */
      if (url.pathname === '/api/conversations' && req.method() === 'GET') {
        return route.fulfill({
          status: 200, contentType: 'application/json',
          body: JSON.stringify({ conversations: [], cursor: null }),
        });
      }
      /* The archive-open click will trigger this; hold it with the gate */
      if (url.pathname === `/api/conversations/${CONV_B}` && req.method() === 'GET') {
        await convBFetchGate;
        return route.fulfill({
          status: 200, contentType: 'application/json',
          body: JSON.stringify({ id: CONV_B, title: 'Conv B', turns: [] }),
        });
      }
      return route.fulfill({
        status: 404, contentType: 'application/json',
        body: JSON.stringify({ error: { message: 'Not found' } }),
      });
    });

    /* Empty archive so init completes without auto-opening any conversation */
    await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });

    /* Open archive panel — this pushes a history entry (conversations panel) */
    await page.locator('button[data-preview="conversations"]').click();
    await page.locator('mjj-conversation-archive').waitFor({ state: 'visible' });

    /* Inject Conv B into the archive list without triggering an API fetch */
    await page.evaluate(id => {
      document.querySelector('mjj-conversation-archive')
        ?.addConversation({ id, title: 'Conv B', turn_count: 0 });
    }, CONV_B);

    await page.waitForFunction(
      id => [...document.querySelectorAll('[data-conv-item]')]
        .some(item => item._convId === id),
      CONV_B,
    );

    /* Click Conv B — archive-open fires, fetch held by convBFetchGate */
    await page.evaluate(id => {
      for (const item of document.querySelectorAll('[data-conv-item]')) {
        if (item._convId === id) {
          item.querySelector('[data-conv-open]')?.click();
          break;
        }
      }
    }, CONV_B);

    /* Brief pause so archive-open can increment openSeq and start the fetch */
    await page.waitForTimeout(80);

    /* Navigate back — popstate fires with no-conv state, openSeq is incremented */
    await page.goBack();
    await page.waitForTimeout(80);

    /* Release the held fetch — the archive-open handler should ignore it */
    resolveConvBFetch();
    await page.waitForTimeout(300);

    /* currentConversationId must remain null (no aria-current item) */
    const currentAriaItem = await page.evaluate(() => {
      const btn = document.querySelector('[data-conv-open][aria-current="true"]');
      return btn ? (btn.closest('[data-conv-item]')?._convId ?? null) : null;
    });
    assert.equal(
      currentAriaItem, null,
      'Fix 2: pending archive-open must not overwrite no-conv popstate',
    );

    assert.deepEqual(pageErrors, [], 'no page errors in openSeq test');
    await context.close();
  }
}

(async () => {
  assert.ok(RUNFILES);
  assert.ok(WORKSPACE);
  const runfilesWorkspace = path.join(RUNFILES, WORKSPACE);
  const serverBinary = path.join(
    runfilesWorkspace,
    'mrjunejune/mrjunejune_server_debug',
  );
  const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src');
  const fakeSidecar = path.join(
    runfilesWorkspace,
    'mrjunejune/test/inference_bridge_fake_sidecar',
  );
  const tempDirectory = fs.mkdtempSync(
    path.join(os.tmpdir(), 'mrjunejune-theme-test-'),
  );
  const chromiumPath = path.resolve(process.env.CHROMIUM_PATH);
  const serverLogs = [];
  let server;
  let browser;

  try {
    port = await findFreePort();
    baseUrl = `http://127.0.0.1:${port}`;
    const pngFiles = listFiles(siteRoot).filter(file => file.endsWith('.png'));
    assert.deepEqual(pngFiles, [], `PNG files leaked into runfiles: ${pngFiles}`);
    for (const file of listFiles(siteRoot).filter(file =>
      file.endsWith('.html')
    )) {
      const source = fs.readFileSync(file, 'utf8');
      for (const primitive of source.matchAll(/<zen-button\b([^>]*)>/g)) {
        assert.match(
          primitive[1],
          /\bsize=["'](?:xs|sm|md|lg|xl)["']/,
          `${path.relative(siteRoot, file)} has an unsized zen-button`,
        );
      }
      assert.deepEqual(
        primitiveOwnershipViolations(source),
        [],
        `${path.relative(siteRoot, file)} violates primitive ownership`,
      );
    }
    for (const file of listFiles(siteRoot).filter(file =>
      /\.(?:css|html|js)$/.test(file) &&
      !file.includes(`${path.sep}public${path.sep}design-system${path.sep}`)
    )) {
      const source = fs.readFileSync(file, 'utf8');
      assert.doesNotMatch(
        source,
        /var\(\s*--zen-/,
        `${path.relative(siteRoot, file)} consumes a deprecated token`,
      );
    }

    server = spawn(serverBinary, [], {
      cwd: runfilesWorkspace,
      env: {
        ...process.env,
        MRJUNEJUNE_PORT: port,
        MRJUNEJUNE_DB_PATH: path.join(tempDirectory, 'site.db'),
        MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar,
        MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar,
        MRJUNEJUNE_ALLOW_GUEST_INFERENCE: '1',
      },
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    server.stdout.on('data', chunk => serverLogs.push(chunk.toString()));
    server.stderr.on('data', chunk => serverLogs.push(chunk.toString()));
    await waitForServer(server, serverLogs);

    const home = await (await fetch(baseUrl)).text();
    const dogGame = await (
      await fetch(`${baseUrl}/public/dog-game.js`)
    ).text();
    const manifest = await (
      await fetch(`${baseUrl}/public/manifest.json`)
    ).text();
    const serviceWorker = await (
      await fetch(`${baseUrl}/sw.js`)
    ).text();
    for (const source of [home, dogGame, manifest]) {
      assert.doesNotMatch(source, /\.png(?:["')]|$)/i);
    }
    assert.match(serviceWorker, /v37-network-first/);
    assert.match(
      await (
        await fetch(`${baseUrl}/public/pwa-register.js`)
      ).text(),
      /register\('\/sw\.js', \{ scope: '\/' \}\)/,
    );
    assert.ok(serviceWorker.includes("startsWith('/tools/hls_player')"));
    assert.ok(serviceWorker.includes("url.hostname === 'localhost'"));
    assert.ok(serviceWorker.includes("url.hostname === '127.0.0.1'"));
    assert.ok(serviceWorker.includes("DEVELOPMENT_SANDBOX_PATHS"));
    assert.ok(serviceWorker.includes("'/public/component-sandbox'"));
    assert.ok(serviceWorker.includes("'/public/mjj-modal.'"));
    assert.ok(serviceWorker.includes('networkFirst(request, acceptsHtml)'));
    assert.ok(serviceWorker.includes('staleWhileRevalidate(event, request)'));
    assert.ok(serviceWorker.includes("fetch(request, { cache: 'no-cache' })"));
    assert.doesNotMatch(
      await (
        await fetch(`${baseUrl}/public/pwa-register.js`)
      ).text(),
      /confirm\(/,
    );
    assert.ok(!serviceWorker.includes("startsWith('/jrpg')"));

    for (const asset of [
      'sprite_shiba0.webp',
      'dog-treat.webp',
      'start-large.webp',
      'icon-192.webp',
      'vscode.webp',
    ]) {
      const response = await fetch(`${baseUrl}/public/${asset}`);
      assert.equal(response.status, 200, asset);
      assert.ok((await response.arrayBuffer()).byteLength > 0, asset);
    }

    browser = await chromium.launch({
      executablePath: chromiumPath,
      headless: true,
      args: ['--no-sandbox'],
    });
    if (runsSuite('core')) {
      const paper = await sampleTheme(browser, 'paper');
      const ink = await sampleTheme(browser, 'ink');
      const playful = await sampleTheme(browser, 'playful');
      const automatic = await sampleTheme(browser, 'auto');
      assert.equal(paper.rootTheme, 'paper');
      assert.equal(ink.rootTheme, 'ink');
      assert.equal(playful.rootTheme, 'playful');
      assert.equal(automatic.rootTheme, 'auto');
      assert.equal(paper.themeLabel, 'Paper');
      assert.equal(ink.themeLabel, 'Ink');
      assert.equal(playful.themeLabel, 'Playful');
      assert.equal(automatic.themeLabel, 'Auto');
      for (const sample of [paper, ink, playful, automatic]) {
        assert.ok(sample.count > 0);
        assert.equal(sample.backgroundRepeat, 'no-repeat');
        assert.equal(sample.backgroundSize, 'cover');
        assert.equal(sample.bodyCoversViewport, true);
        assert.equal(sample.componentReady, true);
        assert.ok(sample.links > 0);
        assert.equal(sample.enhancedLinks, sample.pawLinks);
        assert.match(sample.fontFamily, /More/);
        assert.equal(sample.headerPaws, 0);
        assert.ok(sample.textContrast >= 4.5, JSON.stringify(sample));
        assert.equal(sample.mainBackground, 'rgba(0, 0, 0, 0)');
        assert.equal(sample.themeButtonBackground, 'rgba(0, 0, 0, 0)');
        assert.equal(sample.themeButtonBorder, '0px');
        assert.equal(sample.themeButtonShadow, 'none');
      }
      assert.ok(paper.luminance < 175, JSON.stringify(paper));
      assert.ok(playful.luminance < 175, JSON.stringify(playful));
      assert.ok(ink.luminance > 200, JSON.stringify(ink));
      await testThemeCycle(browser);
      await testPlainField(browser);
      await testButtonScale(browser);
      await testDynamicButtonOwnership(browser);
      await testResumePrint(browser);
      await testComposerLab(browser);
      await testModalSandbox(browser);
    }
    if (runsSuite('jrpg')) await testJrpgPage(browser);
    if (runsSuite('routing')) await testConversationRouting(browser);
    if (runsSuite('greeting')) await testRoleGreetings(browser);
    if (runsSuite('hls')) await testHlsPlayer(browser, siteRoot);
  } finally {
    if (browser) await browser.close();
    await stopProcess(server);
    fs.rmSync(tempDirectory, { recursive: true, force: true });
  }
})().catch(error => {
  console.error(error.stack || error);
  process.exitCode = 1;
});