Ctrl K
ComunidadGuías…

test/clay-primary-agent.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var test = require('node:test');
var assert = require('node:assert/strict');
var childProcess = require('node:child_process');
var path = require('node:path');

function loadPlaywright() { try { return require('playwright'); } catch (error) { return null; } }

async function iconFace(page) {
  return page.locator('.icon-strip-brand').evaluate(function (el) {
    var face = getComputedStyle(el, '::before');
    var logo = el.querySelector('img');
    var box = logo.getBoundingClientRect();
    return { background: face.backgroundColor, shadow: face.boxShadow, border: face.border, image: logo.getAttribute('src'), width: box.width, height: box.height };
  });
}
async function geometry(page) {
  return page.evaluate(function () {
    return ['#main-area', '#sidebar-column', '.title-bar-content', '.header-worker-history-btn'].map(function (selector) {
      var box = document.querySelector(selector).getBoundingClientRect();
      return {selector: selector, x: box.x, y: box.y, width: box.width, height: box.height};
    });
  });
}
async function readingGeometry(page) {
  return page.evaluate(function () {
    return ['.msg-assistant .md-content', '#input', '#input-row', '#session-list .session-item-title'].map(function (selector) {
      var style = getComputedStyle(document.querySelector(selector));
      return {selector: selector, font: style.fontSize, line: style.lineHeight, padding: style.padding};
    });
  });
}
async function assertUserAlignment(page) {
  var rows = await page.locator('#messages > .msg-user:not(.msg-user-other)').evaluateAll(function (elements) {
    return elements.map(function (el) {
      var row = el.getBoundingClientRect();
      var bubble = el.querySelector('.bubble').getBoundingClientRect();
      var actions = el.querySelector('.msg-actions').getBoundingClientRect();
      var inset = parseFloat(getComputedStyle(el).paddingRight);
      return {rightGap: row.right - inset - bubble.right, actionsGap: row.right - inset - actions.right,
        actionsBelow: actions.top >= bubble.bottom, contained: bubble.left >= row.left && bubble.right <= row.right};
    });
  });
  assert.ok(rows.length > 0);
  rows.forEach(function (row) {
    assert.ok(Math.abs(row.rightGap) <= 1, 'user bubble reaches the right text edge');
    assert.ok(Math.abs(row.actionsGap) <= 1, 'message toolbar shares the right edge');
    assert.equal(row.actionsBelow, true, 'message toolbar sits below the bubble');
    assert.equal(row.contained, true);
  });
}
async function toolGrid(page) {
  return page.locator('#session-actions .palette-tile').evaluateAll(function (elements) {
    return elements.filter(function (element) { return element.getBoundingClientRect().width > 0; }).map(function (element) {
      var box = element.getBoundingClientRect();
      var label = element.querySelector('.tool-btn-label');
      var caption = label.getBoundingClientRect();
      var style = getComputedStyle(label);
      var icon = element.querySelector('.lucide').getBoundingClientRect();
      var text = document.createRange();
      text.selectNodeContents(label);
      return {
        label: label.textContent, x: box.x, y: box.y,
        iconWidth: icon.width, iconHeight: icon.height, font: style.fontSize,
        lines: caption.height / parseFloat(style.lineHeight),
        textFits: text.getBoundingClientRect().width <= caption.width + 0.5
      };
    });
  });
}
async function assertCanvasInset(page) {
  var inset = await page.evaluate(function () {
    var column = document.querySelector('#main-column').getBoundingClientRect();
    var canvas = document.querySelector('#main-panels').getBoundingClientRect();
    var header = document.querySelector('.title-bar-content').getBoundingClientRect();
    return {left: canvas.left - column.left, right: column.right - canvas.right, top: canvas.top - header.bottom, bottom: column.bottom - canvas.bottom, mobile: innerWidth <= 768};
  });
  assert.deepEqual(inset, {left: inset.mobile ? 4 : 0, right: inset.mobile ? 4 : 6, top: 0, bottom: inset.mobile ? 4 : 6, mobile: inset.mobile});
}
async function surface(page, selector) {
  return page.locator(selector).evaluate(function (el) {
    while (el) {
      var background = getComputedStyle(el).backgroundColor;
      if (background !== 'rgba(0, 0, 0, 0)') return background;
      el = el.parentElement;
    }
    return 'transparent';
  });
}
async function assertReachable(page, selector) {
  var visible = await page.locator(selector).evaluate(function (el) {
    var box = el.getBoundingClientRect();
    var target = document.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2);
    return box.width > 0 && box.height > 0 && box.x >= 0 && box.right <= innerWidth && box.y >= 0 && box.bottom <= innerHeight && (target === el || el.contains(target));
  });
  assert.equal(visible, true, selector + ' must remain visible and unobscured');
}
async function chromeContrast(page) {
  return page.evaluate(function () {
    var canvas = document.createElement('canvas');
    canvas.width = canvas.height = 1;
    var context = canvas.getContext('2d', {willReadFrequently: true});
    function luminance(color) {
      context.fillStyle = color;
      context.fillRect(0, 0, 1, 1);
      var rgb = Array.from(context.getImageData(0, 0, 1, 1).data).slice(0, 3).map(function (value) {
        value /= 255;
        return value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
      });
      return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722;
    }
    return ['.title-bar-project-name', '.title-bar-project-default', '#session-list .session-item-title', '#header-title'].map(function (selector) {
      var element = document.querySelector(selector);
      var style = getComputedStyle(element);
      var elementBackground = style.backgroundColor;
      if (elementBackground === 'rgba(0, 0, 0, 0)') elementBackground = getComputedStyle(document.querySelector('#sidebar-column')).backgroundColor;
      var background = luminance(elementBackground);
      var foreground = luminance(style.color);
      return {selector: selector, ratio: (Math.max(background, foreground) + 0.05) / (Math.min(background, foreground) + 0.05)};
    });
  });
}
async function roleLabelContrast(page) {
  return page.locator('.clay-role-label').evaluate(function (element) {
    var canvas = document.createElement('canvas');
    canvas.width = canvas.height = 1;
    var context = canvas.getContext('2d', {willReadFrequently: true});
    function luminance(color) {
      context.fillStyle = color;
      context.fillRect(0, 0, 1, 1);
      var rgb = Array.from(context.getImageData(0, 0, 1, 1).data).slice(0, 3).map(function (value) {
        value /= 255;
        return value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
      });
      return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722;
    }
    var style = getComputedStyle(element);
    var background = style.backgroundColor;
    var ancestor = element.parentElement;
    while (background === 'rgba(0, 0, 0, 0)' && ancestor) {
      background = getComputedStyle(ancestor).backgroundColor;
      ancestor = ancestor.parentElement;
    }
    var foreground = style.color;
    var backgroundLuminance = luminance(background);
    var foregroundLuminance = luminance(foreground);
    return {background: background, foreground: foreground, ratio: (Math.max(backgroundLuminance, foregroundLuminance) + 0.05) / (Math.min(backgroundLuminance, foregroundLuminance) + 0.05)};
  });
}

test('production Clay workspace DOM: identity, native button access, scoped surfaces and compact geometry', async function (t) {
  var playwright = loadPlaywright();
  if (!playwright) { t.skip('Playwright is not installed'); return; }
  var port = 33000 + process.pid % 10000;
  var server = childProcess.spawn(process.execPath, [path.join(__dirname, 'fixtures/session-folders-dom/serve.js'), String(port)], {stdio: ['ignore', 'pipe', 'pipe']});
  t.after(function () { if (!server.killed) server.kill('SIGTERM'); });
  await new Promise(function (resolve, reject) {
    var timer = setTimeout(function () { reject(new Error('fixture server did not start')); }, 5000);
    server.stdout.on('data', function (chunk) { if (String(chunk).indexOf('DOM harness on') !== -1) { clearTimeout(timer); resolve(); } });
    server.on('exit', function (code) { clearTimeout(timer); reject(new Error('fixture server exited ' + code)); });
  });
  var browser;
  try { browser = await playwright.chromium.launch(); } catch (error) { t.skip('Chromium is not available: ' + error.message); return; }
  t.after(async function () { await browser.close(); });
  var page = await browser.newPage({viewport: {width: 1280, height: 900}, reducedMotion: 'reduce'});
  page.setDefaultTimeout(10000);
  var pageErrors = [];
  page.on('pageerror', function (error) { pageErrors.push(error.message); });
  var base = 'http://127.0.0.1:' + port + '/clay-primary.html';
  async function open(query) {
    await page.goto(base + (query || ''), {waitUntil: 'networkidle'});
    await page.waitForSelector('body[data-ready="true"]');
  }
  // This identity fixture has no live conversation server. Native routing is
  // exercised directly; contextual handoff uses the authenticated browser fixture.
  async function enterWorkspace(selector) {
    var mateId = selector === '.icon-strip-brand' ? 'clay-built-in' : selector.match(/data-mate-id="([^"]+)"/)[1];
    await page.evaluate(function (id) { return window.__openMateWorkspace(id); }, mateId);
  }
  await open();
  var entry = page.locator('.icon-strip-brand');

  await t.test('click, Enter and Space open the popover; native entry uses the exact built-in artwork', async function () {
    var face = await iconFace(page);
    await entry.click();
    assert.deepEqual(await page.evaluate(function () { return window.__opened; }), [], 'first click keeps the current workspace');
    assert.equal(await page.locator('#contextual-clay').isVisible(), true);
    await entry.click();
    assert.deepEqual(await page.evaluate(function () { return window.__opened; }), [], 'an unready conversation cannot hand off');
    await page.keyboard.press('Escape');
    await enterWorkspace('.icon-strip-brand');
    assert.deepEqual(await page.evaluate(function () { return window.__opened; }), ['mate-clay-built-in']);
    assert.deepEqual(await page.evaluate(function () { return window.__panelCloses; }), ['terminal', 'files']);
    assert.equal(await entry.getAttribute('aria-current'), 'page');
    var activeFace = await iconFace(page);
    assert.notEqual(activeFace.background, face.background);
    assert.notEqual(activeFace.shadow, face.shadow);
    assert.equal(activeFace.border, face.border);
    assert.equal(activeFace.image, face.image);
    assert.equal(face.image, 'clay-studio-symbol.png');
    assert.equal(face.width, 38);
    assert.equal(await page.locator('button[data-mate-id="clay-built-in"]').count(), 0);
    assert.equal(await page.locator('#clay-primary-agent-label').count(), 0);
    assert.equal(await page.locator('.title-bar-mate-avatar').count(), 0);
    assert.equal(await page.locator('.title-bar-project-icon').evaluate(function (el) { return getComputedStyle(el).display; }), 'none');
    var roleBadge = page.locator('#title-bar-project-default');
    assert.equal(await roleBadge.textContent(), 'Lead Mate');
    assert.equal(parseFloat(await roleBadge.evaluate(function (el) { return getComputedStyle(el).fontSize; })), 10);
    assert.equal((await roleBadge.boundingBox()).height, 18);
    assert.equal(await roleBadge.evaluate(function (el) { return getComputedStyle(el).borderRadius; }), '5px');
    assert.equal(await roleBadge.locator('.clay-role-insignia').count(), 1);
    assert.equal((await roleBadge.locator('.clay-role-insignia').boundingBox()).width, 12);
    assert.equal(parseFloat(await roleBadge.locator('.clay-role-insignia').evaluate(function (el) { return getComputedStyle(el).paddingLeft; })), 0);
    for (var part of ['.clay-role-insignia', '.clay-role-label']) {
      assert.equal(await roleBadge.locator(part).evaluate(function (el) { return getComputedStyle(el).backgroundColor; }), 'rgba(0, 0, 0, 0)', 'Badge contents share one surface');
    }
    assert.equal(await roleBadge.locator('.clay-role-insignia').evaluate(function (el) { return getComputedStyle(el).color; }), 'rgb(224, 180, 91)');
    assert.equal(await roleBadge.locator('.clay-role-label').evaluate(function (el) { return getComputedStyle(el).color; }), 'rgb(255, 255, 255)');
    assert.equal(await roleBadge.locator('.clay-role-label').count(), 1);
    await roleBadge.locator('svg').waitFor({state: 'attached'});
    assert.equal(await roleBadge.locator('svg').count(), 1);
    assert.equal(await page.locator('#title-bar-project-default').isVisible(), true);
    for (var key of ['Enter', 'Space']) {
      await page.getByRole('button', {name: 'Open Studio project', exact: true}).click();
      await entry.focus();
      await page.keyboard.press('Tab');
      await page.keyboard.press('Shift+Tab');
      assert.equal(await entry.evaluate(function (el) { return el === document.activeElement && el.matches(':focus-visible'); }), true);
      await page.keyboard.press(key);
      assert.equal(await page.locator('#contextual-clay').isVisible(), true);
      await page.keyboard.press(key);
      assert.equal(await entry.getAttribute('aria-current'), null);
      await page.keyboard.press('Escape');
      await enterWorkspace('.icon-strip-brand');
      assert.equal(await entry.getAttribute('aria-current'), 'page');
    }
    assert.deepEqual(await page.evaluate(function () { return window.__opened; }), ['mate-clay-built-in', 'mate-clay-built-in', 'mate-clay-built-in']);
  });

  await t.test('Mate canvases share geometry and the pale mint default in both themes', async function () {
    for (var theme of ['light-theme', 'dark-theme']) {
      await open('?workspace=clay&theme=' + (theme === 'light-theme' ? 'light' : 'dark'));
      await enterWorkspace('[data-mate-id="designer"]');
      await assertCanvasInset(page);
      assert.equal(await surface(page, '.title-bar-content'), await surface(page, '#sidebar-column'));
      var green = await page.locator('#sidebar-column').evaluate(function (el) {
        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');
        context.fillStyle = getComputedStyle(el).backgroundColor;
        context.fillRect(0, 0, 1, 1);
        return Array.from(context.getImageData(0, 0, 1, 1).data);
      });
      assert.ok(green[1] > green[0] && green[1] > green[2], 'ordinary Mate surround has a green tint');
      assert.ok(Math.max.apply(Math, green.slice(0, 3)) - Math.min.apply(Math, green.slice(0, 3)) < 20, 'surround tint stays restrained');
      var mateContrasts = await chromeContrast(page);
      mateContrasts.forEach(function (result) { assert.ok(result.ratio >= 4.5, result.selector + ' Mate contrast'); });
      await page.screenshot({path: '/tmp/mate-canvas-' + theme + '.png'});
      var normal = await surface(page, '#sidebar-column');
      var reading = await surface(page, '#main-panels');
      var composer = await surface(page, '#input-row');
      var before = await geometry(page);
      var typography = await readingGeometry(page);
      var face = await iconFace(page);
      await enterWorkspace('.icon-strip-brand');
      assert.equal(await surface(page, '#sidebar-column'), normal);
      assert.equal(await surface(page, '.title-bar-content'), await surface(page, '#sidebar-column'));
      assert.equal(await surface(page, '#main-panels'), reading);
      assert.equal(await surface(page, '#input-row'), composer, 'Clay and other Mates share the restored Home writing surface');
      assert.deepEqual(await geometry(page), before);
      assert.deepEqual(await readingGeometry(page), typography);
      await assertCanvasInset(page);
      var activeFace = await iconFace(page);
      assert.notEqual(activeFace.background, face.background);
      assert.notEqual(activeFace.shadow, face.shadow);
      assert.equal(activeFace.image, face.image);
      var contrasts = await chromeContrast(page);
      contrasts.forEach(function (result) { assert.ok(result.ratio >= 4.5, result.selector + ' contrast ' + result.ratio); });
      var labelContrast = await roleLabelContrast(page);
      assert.notEqual(labelContrast.background, 'rgba(0, 0, 0, 0)', 'Lead Mate label background is composited');
      assert.ok(labelContrast.ratio >= 4.5, 'Lead Mate label contrast ' + labelContrast.ratio);
      await page.locator('.header-worker-history-btn').click();
      assert.equal(await page.getByRole('menu', {name: /Worker history for/}).isVisible(), true);
      await page.keyboard.press('Escape');
      assert.equal(await page.locator('.header-worker-history-btn').evaluate(function (el) { return el === document.activeElement; }), true);
      await enterWorkspace('[data-mate-id="designer"]');
      assert.equal(await surface(page, '#sidebar-column'), normal);
      assert.equal(await surface(page, '#input-row'), composer);
      assert.equal(await entry.getAttribute('aria-current'), null);
      assert.equal(await page.locator('.title-bar-mate-avatar').count(), 1);
      assert.equal(await page.locator('.title-bar-project-icon.is-clay-identity').count(), 0);
      assert.match(await page.locator('#title-bar-project-default').textContent(), /Product design/);
    }
  });

  await t.test('direct Clay transitions clear mutually exclusive identity styling', async function () {
    await open('?workspace=clay');
    await enterWorkspace('[data-mate-id="designer"]');
    assert.equal(await page.locator('#title-bar-project-dropdown').getAttribute('data-clay-identity'), null);
    assert.equal(await page.locator('#title-bar-project-dropdown').getAttribute('data-mate-bio'), 'true');
    assert.equal(await page.locator('#title-bar-project-default').evaluate(function (el) { return getComputedStyle(el).display; }), 'block');
    assert.equal(await page.locator('.title-bar-mate-avatar').count(), 1);

    await enterWorkspace('.icon-strip-brand');
    await enterWorkspace('[data-mate-id="quiet"]');
    assert.equal(await page.locator('#title-bar-project-dropdown').getAttribute('data-clay-identity'), null);
    assert.equal(await page.locator('#title-bar-project-dropdown').getAttribute('data-mate-bio'), null);
    assert.equal(await page.locator('#title-bar-project-default').evaluate(function (el) { return getComputedStyle(el).display; }), 'none');
    assert.equal(await page.locator('#title-bar-project-default').textContent(), '');
    assert.equal(await page.locator('.title-bar-mate-avatar').count(), 1);

    await enterWorkspace('.icon-strip-brand');
    await enterWorkspace('[data-mate-id="named-clay"]');
    assert.equal(await page.locator('#title-bar-project-dropdown').getAttribute('data-clay-identity'), null);
    assert.equal(await page.locator('#title-bar-project-dropdown').getAttribute('data-mate-bio'), 'true');
    assert.equal(await page.locator('.title-bar-mate-avatar').count(), 1);
    assert.equal(await page.locator('.title-bar-project-icon.is-clay-identity').count(), 0);
  });

  await t.test('settled Clay identity screenshots', async function () {
    await page.setViewportSize({width: 1280, height: 800});
    await open('?workspace=clay&theme=light');
    await page.screenshot({path: '/tmp/clay-identity-desktop-light.png'});
    await open('?workspace=clay&theme=dark');
    await page.screenshot({path: '/tmp/clay-identity-desktop-dark.png'});
    await page.setViewportSize({width: 1280, height: 800});
    await open('?workspace=clay&theme=light&sidebar=192');
    var sidebarBadge = page.locator('#title-bar-project-default');
    var sidebarBadgeBox = await sidebarBadge.boundingBox();
    assert.equal(sidebarBadgeBox.height, 18);
    assert.ok(sidebarBadgeBox.x >= 0 && sidebarBadgeBox.x + sidebarBadgeBox.width <= 192, 'Lead Mate badge fits the 192px sidebar');
    assert.equal((await page.locator('.title-bar-sidebar').boundingBox()).height, 48);
    await page.screenshot({path: '/tmp/clay-identity-sidebar-192-light.png'});
    await page.setViewportSize({width: 390, height: 800});
    await open('?workspace=clay&theme=light');
    await page.screenshot({path: '/tmp/clay-identity-narrow-light.png'});
    await page.setViewportSize({width: 1280, height: 800});
  });

  await t.test('late metadata, renaming, archived and unavailable built-ins cannot misidentify Clay', async function () {
    await open('?workspace=clay&late=1');
    assert.equal(await entry.isDisabled(), true);
    assert.equal(await page.locator('body').evaluate(function (el) { return el.classList.contains('clay-workspace-active'); }), false);
    await page.evaluate(function () { window.__loadMates(); });
    assert.equal(await entry.isEnabled(), true);
    assert.equal(await entry.getAttribute('aria-current'), 'page');
    await page.evaluate(function () {
      window.__store.set({cachedMatesList: [{id: 'clay-built-in', builtinKey: 'clay', name: 'Renamed', profile: {bio: 'A renamed companion'}}, {id: 'fake', name: 'Clay'}]});
    });
    assert.equal(await page.locator('#title-bar-project-name').textContent(), 'Renamed');
    assert.equal(await entry.getAttribute('aria-current'), 'page');
    await enterWorkspace('[data-mate-id="fake"]');
    assert.equal(await entry.getAttribute('aria-current'), null);
    await enterWorkspace('.icon-strip-brand');
    assert.equal(await page.evaluate(function () { return window.__store.get('currentSlug'); }), 'mate-clay-built-in');
    for (var unavailable of [[], [{id: 'clay-built-in', builtinKey: 'clay', archived: true}], [{id: 'fake', name: 'Clay'}]]) {
      await page.evaluate(function (mates) { window.__store.set({cachedMatesList: mates}); }, unavailable);
      assert.equal(await entry.isDisabled(), true);
      assert.equal(await entry.getAttribute('aria-current'), null);
      assert.equal(await entry.getAttribute('aria-label'), 'Clay is unavailable');
      assert.equal(await page.locator('body').evaluate(function (el) { return el.classList.contains('clay-workspace-active'); }), false);
    }
    await page.evaluate(function () { window.__loadMates(); });
    for (var state of [{homeShellVisible: true}, {homeShellVisible: false, dmMode: true}]) {
      await page.evaluate(function (next) { window.__store.set(next); }, state);
      assert.equal(await entry.getAttribute('aria-current'), null);
      assert.equal(await page.locator('body').evaluate(function (el) { return el.classList.contains('clay-workspace-active'); }), false);
    }
  });

  await t.test('desktop, narrow sidebar and mobile keep input and header controls reachable', async function () {
    for (var theme of ['light', 'dark']) {
      await open('?workspace=clay&theme=' + theme);
      for (var width of [1280, 980, 769, 390, 320]) {
        await page.setViewportSize({width: width, height: 900});
        if (width === 980) {
          await page.locator('#sidebar-column').evaluate(function (el) { el.style.width = '192px'; });
          await page.waitForFunction(function () { return document.querySelector('#sidebar-column').getBoundingClientRect().width === 192; });
        }
        assert.equal(await page.evaluate(function () { return document.documentElement.scrollWidth <= innerWidth; }), true, 'no horizontal overflow at ' + width);
        await assertReachable(page, '#send-btn');
        await assertReachable(page, '#input');
        await assertReachable(page, '.header-worker-history-btn');
        await assertReachable(page, '#find-in-session-btn');
        if (width > 768) await assertReachable(page, '.icon-strip-brand');
        await assertCanvasInset(page);
        var before = await geometry(page);
        await page.evaluate(function () { window.__store.set({currentSlug: 'mate-designer'}); });
        await assertCanvasInset(page);
        assert.deepEqual(await geometry(page), before, 'surface switching preserves layout at ' + width);
        await page.evaluate(function () { window.__store.set({currentSlug: 'mate-clay-built-in'}); });
      }
    }
  });

  await t.test('Mate tools keep two compact rows, with four columns for Clay and three for other Mates', async function () {
    await page.setViewportSize({width: 1280, height: 900});
    await open('?workspace=project&sidebar=192');
    var projectTools = await toolGrid(page);
    assert.equal(new Set(projectTools.map(function (tool) { return tool.x; })).size, 4);
    for (var mate of ['.icon-strip-brand', '[data-mate-id="designer"]']) {
      await enterWorkspace(mate);
      var mateTools = await toolGrid(page);
      assert.equal(mateTools.length, mate === '.icon-strip-brand' ? 7 : 6);
      assert.equal(new Set(mateTools.map(function (tool) { return tool.x; })).size, mate === '.icon-strip-brand' ? 4 : 3);
      assert.equal(new Set(mateTools.map(function (tool) { return tool.y; })).size, 2);
      mateTools.forEach(function (tool) {
        assert.equal(tool.iconWidth, 28, tool.label + ' keeps its compact icon face');
        assert.equal(tool.iconHeight, 28);
        assert.equal(tool.font, '10.5px');
        if (['Knowledge', 'Scheduled', 'Browser', 'Debates'].indexOf(tool.label) !== -1) {
          assert.ok(tool.lines <= 1.1, tool.label + ' stays on one line');
          if (mate !== '.icon-strip-brand') assert.equal(tool.textFits, true, tool.label + ' is not clipped');
        }
      });
    }
    await page.getByRole('button', {name: 'Open Studio project', exact: true}).click();
    assert.deepEqual(await toolGrid(page), projectTools, 'ordinary project tools retain their original layout');
    await enterWorkspace('.icon-strip-brand');
    var sidebar = page.locator('#sidebar-column');
    var width = (await sidebar.boundingBox()).width;
    await page.locator('#sidebar-toggle-btn').click();
    await page.waitForFunction(function () { return document.querySelector('#sidebar-column').getBoundingClientRect().width === 0; });
    await assertReachable(page, '#sidebar-expand-btn');
    await page.locator('#sidebar-expand-btn').click();
    await page.waitForFunction(function (expected) { return document.querySelector('#sidebar-column').getBoundingClientRect().width === expected; }, width);
    var row = page.locator('#session-list .session-item.active');
    assert.equal((await row.boundingBox()).height, 36);
    await row.hover();
    var star = row.locator('.session-folder-star-btn');
    await assertReachable(page, '#session-list .session-item.active .session-folder-star-btn');
    await star.focus();
    assert.equal(await star.evaluate(function (el) { return el === document.activeElement; }), true);
    await page.locator('.header-worker-history-btn').click();
    assert.equal(await page.getByRole('menu', {name: /Worker history for/}).getByRole('menuitem').count(), 2);
    await page.keyboard.press('Escape');
  });

  await t.test('Home-style Mate conversation preserves the real composer and resets for projects', async function () {
    await page.setViewportSize({width: 1280, height: 900});
    await open('?workspace=clay&composer&ghost');
    var input = page.locator('#input');
    var restored = await page.evaluate(function () {
      var reply = document.querySelector('.msg-assistant');
      var row = document.querySelector('#input-row');
      var field = document.querySelector('#input').getBoundingClientRect();
      var voice = document.querySelector('.stt-controls').getBoundingClientRect();
      var send = document.querySelector('#send-btn').getBoundingClientRect();
      var tools = document.querySelector('#attach-wrap').getBoundingClientRect();
      return { replyWidth: reply.getBoundingClientRect().width, replyLayout: getComputedStyle(reply).display,
        userHeader: getComputedStyle(document.querySelector('.msg-user .dm-bubble-header')).display,
        radius: getComputedStyle(row, '::before').borderRadius,
        aligned: Math.abs((voice.top + voice.bottom - field.top - field.bottom) / 2) <= 1 && Math.abs((send.top + send.bottom - field.top - field.bottom) / 2) <= 1,
        toolsBelow: tools.top >= field.bottom };
    });
    assert.equal(restored.replyWidth, 860);
    assert.equal(restored.replyLayout, 'grid');
    assert.equal(restored.userHeader, 'none');
    assert.equal(restored.radius, '22px');
    assert.equal(restored.aligned, true, 'voice and send belong to the writing row');
    assert.equal(restored.toolsBelow, true);
    await assertUserAlignment(page);
    var chooser = page.waitForEvent('filechooser');
    await page.locator('#attach-file-btn').click();
    assert.equal((await chooser).isMultiple(), true);
    for (var control of ['#ask-mate-btn', '#composer-add-worker-btn', '#schedule-btn']) {
      assert.equal(await page.locator(control).isVisible(), false, control + ' is absent from the Mate composer');
    }
    await page.evaluate(function () { document.activeElement.blur(); });
    await page.waitForTimeout(180);
    var restingFace = await page.locator('#input-row').evaluate(function (el) {
      var face = getComputedStyle(el, '::before');
      return {edge: face.borderColor, background: face.backgroundColor};
    });
    await input.focus();
    await page.waitForTimeout(180);
    assert.notEqual(await page.locator('#input-row').evaluate(function (el) { return getComputedStyle(el, '::before').borderColor; }), restingFace.edge);
    assert.equal(await page.locator('#input-row').evaluate(function (el) { return getComputedStyle(el, '::before').backgroundColor; }), restingFace.background, 'focus keeps the normal writing surface');
    var focused = await page.locator('#input-row').evaluate(function (el) {
      var outer = getComputedStyle(el);
      var face = getComputedStyle(el, '::before');
      var inputStyle = getComputedStyle(document.getElementById('input'));
      var ghostStyle = getComputedStyle(document.getElementById('ghost-suggestion'));
      return {outerShadow: outer.boxShadow, outerBackground: outer.backgroundColor,
        faceShadow: face.boxShadow, inputPadding: inputStyle.padding, ghostPadding: ghostStyle.padding,
        inputLine: inputStyle.lineHeight, ghostLine: ghostStyle.lineHeight};
    });
    assert.equal(focused.outerShadow, 'none', 'no square focus ring outside the rounded field');
    assert.equal(focused.outerBackground, 'rgba(0, 0, 0, 0)');
    assert.notEqual(focused.faceShadow, 'none', 'rounded writing surface retains subtle depth');
    assert.equal(focused.inputPadding, focused.ghostPadding, 'suggestions align with the caret');
    assert.equal(focused.inputLine, focused.ghostLine);
    assert.equal(await page.locator('#ghost-suggestion').isVisible(), true);
    await page.screenshot({path: '/tmp/mate-composer-focused.png'});
    await input.fill('First line');
    assert.equal(await page.locator('#ghost-suggestion').isVisible(), false);
    await input.press('Shift+Enter');
    await input.press('End');
    await input.press('x');
    assert.equal(await input.inputValue(), 'First line\nx');
    await input.press('Enter');
    await page.waitForFunction(function () { return window.__sent.some(function (message) { return message.type === 'message' && message.text === 'First line\nx'; }); });
    assert.equal(await input.inputValue(), '');
    await input.fill('Button send');
    await page.locator('#send-btn').click();
    assert.equal(await page.evaluate(function () { return window.__sent.filter(function (message) { return message.type === 'message'; }).length; }), 2);
    await page.evaluate(function () { window.__store.set({processing: true}); });
    await page.locator('#send-btn').click();
    assert.equal(await page.evaluate(function () { return window.__sent.slice(-1)[0].type; }), 'stop');
    await input.fill('Keep this draft');
    await page.evaluate(function () { window.__store.set({connected: false, processing: false}); });
    await page.locator('#send-btn').click();
    assert.equal(await input.inputValue(), 'Keep this draft');
    assert.match(await page.evaluate(function () { return window.__inputErrors[0]; }), /Not connected/);
    await page.locator('[data-slug="project"]').click();
    assert.equal(await page.locator('body').evaluate(function (el) { return el.classList.contains('mate-workspace-active'); }), false);
    assert.equal(await page.locator('#input-row').evaluate(function (el) { return getComputedStyle(el).display; }), 'flex');
    for (var wide of [false, true]) {
      await page.evaluate(function (enabled) { document.body.classList.toggle('wide-view', enabled); }, wide);
      for (var width of [1600, 1100, 390]) {
        await page.setViewportSize({width: width, height: 800});
        var composerWidth = await page.evaluate(function () {
          var area = document.getElementById('input-area');
          var style = getComputedStyle(area);
          return {
            available: area.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight),
            actual: document.getElementById('input-wrapper').getBoundingClientRect().width
          };
        });
        assert.ok(Math.abs(composerWidth.available - composerWidth.actual) <= 1, 'project composer fills its pane in both transcript layouts');
      }
    }
    await page.setViewportSize({width: 1280, height: 800});
    assert.equal(await page.locator('.msg-assistant').first().evaluate(function (el) { return getComputedStyle(el).display; }), 'flex');
    assert.equal(await input.inputValue(), 'Keep this draft');
    await enterWorkspace('[data-mate-id="designer"]');
    assert.equal(await page.locator('#input-row').evaluate(function (el) { return getComputedStyle(el).display; }), 'grid');
    await page.setViewportSize({width: 320, height: 700});
    await assertUserAlignment(page);
    await assertReachable(page, '#input');
    await assertReachable(page, '#stt-btn-picker');
    await page.locator('#input-more-btn').click();
    assert.equal(await page.locator('#input-more-sheet').isVisible(), true);
    await page.locator('.input-more-backdrop').click({position: {x: 5, y: 5}});
    await page.waitForFunction(function () { return document.getElementById('input-more-sheet').classList.contains('hidden'); });
    await page.screenshot({path: '/tmp/mate-conversation-mobile.png'});
    await page.setViewportSize({width: 1280, height: 900});
  });

  await t.test('Mate actions use collapsed identity rows for running, completed and failed tools', async function () {
    await page.setViewportSize({width: 1280, height: 900});
    await open('?workspace=clay&actions&layout=bubble');
    var group = page.locator('.mate-tool-group');
    assert.equal(await group.count(), 1);
    assert.equal(await group.getAttribute('data-session-mate-id'), 'clay-built-in');
    assert.equal(await group.locator('.dm-bubble-avatar').isVisible(), false, 'action summaries do not repeat the Mate avatar');
    assert.equal(await group.evaluate(function (el) { return getComputedStyle(el).display; }), 'flex');
    assert.equal(await group.locator('.tool-group-header').isVisible(), true, 'a single Mate tool has a summary');
    assert.equal(await group.locator('.tool-item').isVisible(), false, 'details start collapsed');
    await group.locator('.tool-group-header').click();
    assert.equal(await group.locator('.tool-item').isVisible(), true);
    await page.evaluate(function () { window.__actions.finish('action-1', false); });
    assert.equal(await group.evaluate(function (el) { return el.classList.contains('done'); }), true);
    assert.equal(await group.locator('.tool-item').isVisible(), true, 'completion respects an opened group');
    await group.locator('.tool-group-header').click();
    await page.screenshot({path: '/tmp/mate-actions-light.png'});
    await page.evaluate(function () { window.__actions.reset(); window.__actions.start('action-2'); window.__actions.finish('action-2', true); });
    var failed = page.locator('.mate-tool-group').last();
    await failed.locator('.tool-group-status-icon .err-icon svg').waitFor({state: 'visible'});
    assert.equal(await failed.locator('.err-icon').first().isVisible(), true);
    await failed.locator('.tool-group-header').click();
    await failed.locator('.tool-header').click();
    assert.equal(await failed.locator('.tool-result-block').isVisible(), true);
    assert.match(await failed.locator('.tool-result-block').textContent(), /Search unavailable/);
    await page.evaluate(function () { document.documentElement.classList.remove('light-theme'); });
    await page.setViewportSize({width: 390, height: 800});
    await failed.scrollIntoViewIfNeeded();
    assert.equal(await page.evaluate(function () { return document.documentElement.scrollWidth <= innerWidth; }), true);
    await page.screenshot({path: '/tmp/mate-actions-dark-mobile.png'});
    await page.setViewportSize({width: 1280, height: 900});
    await page.locator('[data-slug="project"]').click();
    await page.evaluate(function () { window.__store.set({activeSessionMateId: null, activeSessionMateName: null}); window.__actions.reset(); window.__actions.start('project-action'); });
    var projectGroup = page.locator('.tool-group').last();
    assert.equal(await projectGroup.evaluate(function (el) { return el.classList.contains('mate-tool-group'); }), false);
    assert.equal(await projectGroup.locator('.tool-item').isVisible(), true, 'ordinary project single tools retain their presentation');
  });

  await t.test('split preview keeps both production canvases and the Worker status clear', async function () {
    await page.setViewportSize({width: 1440, height: 1000});
    for (var theme of ['light', 'dark']) {
      await open('?workspace=clay&theme=' + theme + '&split=1');
      await page.frameLocator('iframe').locator('body[data-ready="true"]').waitFor();
      assert.equal(await page.locator('.split-pane').count(), 2);
      assert.equal(await page.frameLocator('iframe').locator('#input-wrapper').isVisible(), false);
      assert.equal(await page.frameLocator('iframe').locator('.worker-pane-status').isVisible(), true);
      assert.equal(await page.frameLocator('iframe').locator('#input').isDisabled(), true);
      assert.equal(await page.locator('#input').isEnabled(), true);
      assert.equal(await page.locator('#input').inputValue(), 'Let’s start with the workspace.');
      await assertReachable(page, '#send-btn');
      await assertReachable(page, '.header-worker-history-btn');
      await page.locator('.header-worker-history-btn').click();
      assert.equal(await page.getByRole('menu', {name: /Worker history for/}).getByRole('menuitem').count(), 2);
      await page.keyboard.press('Escape');
    }
    await page.getByRole('button', {name: 'Close Worker preview'}).click();
    assert.equal(await page.locator('.split-pane').count(), 1);
    await assertReachable(page, '#send-btn');
  });
  assert.deepEqual(pageErrors, []);
});