Ctrl K
ComunidadGuías…

test/clay-control-activity-browser.test.cjs

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

Archivo original ↗Preguntar a KiBot
var test = require('node:test'), assert = require('node:assert/strict');
var cp = require('node:child_process'), path = require('node:path'), playwright = require('playwright');
var child, browser, origin, page, frame, events;
test.before(async function () {
  child = cp.fork(path.join(__dirname, 'fixtures/contextual-clay-server.cjs'), [], { env: Object.assign({}, process.env, { CLAY_CONTEXT_ACCESS_FIXTURE: '1' }), stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
  var output = ''; child.stdout.on('data', function (data) { output += data; }); child.stderr.on('data', function (data) { output += data; });
  var info = await new Promise(function (resolve, reject) {
    var timer = setTimeout(function () { reject(new Error(output)); }, 15000);
    child.once('message', function (data) { clearTimeout(timer); resolve(data); });
    child.once('exit', function () { clearTimeout(timer); reject(new Error(output)); });
  });
  origin = 'http://127.0.0.1:' + info.port; browser = await playwright.chromium.launch();
});
test.after(async function () { if (browser) await browser.close(); if (child) child.kill('SIGTERM'); });
test.beforeEach(async function () {
  page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); page.setDefaultTimeout(6000); events = [];
  page.on('websocket', function (ws) { ws.on('framereceived', function (event) {
    try { var data = JSON.parse(event.payload); if (data.type === 'clay_control_activity' || data.type === 'clay_navigation_apply') events.push(data); } catch (error) {}
  }); });
  await page.goto(origin + '/fixture/login?to=alpha');
  await page.locator('.icon-strip-brand:not([disabled])').waitFor(); await page.locator('.icon-strip-brand').click();
  frame = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
  await page.waitForFunction(function () { var f = document.querySelector('#contextual-clay iframe[data-canonical="true"]'); return f && f.conversation && f.conversation.identity().ready; });
  await page.request.get(origin + '/fixture/activity-reset');
  var header = page.locator('#contextual-clay > header');
  var box = await header.boundingBox();
  await page.mouse.move(box.x + 100, box.y + 15); await page.mouse.down(); await page.mouse.move(870, box.y + 15); await page.mouse.up();
});
test.afterEach(async function () { if (page) { await page.request.get(origin + '/fixture/activity-release'); await page.request.get(origin + '/fixture/activity-reset'); await page.close(); } });
async function propose(mode) {
  var pending = page.request.get(origin + '/fixture/activity?mode=' + mode);
  pending.catch(function () {});
  await frame.locator('.ask-user-container:not(.answered)').last().waitFor();
  await page.locator('#clay-control-activity').waitFor();
  return { pending: pending, review: frame.locator('.ask-user-container:not(.answered)').last() };
}
async function answer(proposal, choice, keyboard) {
  var option = proposal.review.getByRole('button', { name: choice || 'Apply exact change', exact: true });
  if (keyboard) { await option.focus(); await page.keyboard.press('Space'); }
  else await option.click();
  var submit = proposal.review.getByRole('button', { name: 'Submit', exact: true });
  if (keyboard) { await submit.focus(); await page.keyboard.press('Enter'); } else await submit.click();
}
async function phase(value) {
  await page.waitForFunction(async function (expected) { var m = await import('./modules/store.js'); var a = m.store.get('clayControlActivity'); return a && a.message.phase === expected; }, value);
}
async function geometry() {
  return page.evaluate(function () {
    var element = document.getElementById('clay-control-activity'), row = document.querySelector('.session-item[data-session-id]');
    var rect = element.getBoundingClientRect(), target = row ? row.getBoundingClientRect() : {left:0,right:0,top:0,bottom:0}, style = getComputedStyle(element);
    var identity = element.querySelector('.clay-control-identity'), name = element.querySelector('.clay-control-name');
    var bubble = element.querySelector('.clay-control-bubble').getBoundingClientRect();
    var composer = document.querySelector('#input-area').getBoundingClientRect();
    var panel = document.getElementById('contextual-clay').getBoundingClientRect();
    function box(r) { return { left: r.left, right: r.right, top: r.top, bottom: r.bottom }; }
    return { rect: box(rect), target: box(target), bubble: box(bubble), composer: box(composer), panel: box(panel),
      targeted: element.dataset.targeted, pointerEvents: style.pointerEvents, live: element.getAttribute('aria-live'),
      tipX: rect.left + parseFloat(style.getPropertyValue('--tip-x')), tipY: rect.top + parseFloat(style.getPropertyValue('--tip-y')),
      avatarBottom: element.querySelector('img').getBoundingClientRect().bottom,
      identity: box(identity.getBoundingClientRect()), name: name.textContent, grouped: identity.contains(element.querySelector('img')) && identity.contains(name),
      identityHeight: identity.getBoundingClientRect().height, nameWidth: name.clientWidth, nameOverflow: getComputedStyle(name).textOverflow,
      avatarAlt: element.querySelector('img').alt, statusCount: document.querySelectorAll('#clay-control-activity [role=status]').length,
      cursorTransform: getComputedStyle(element.querySelector('.clay-control-pointer')).transform };
  });
}
function disjoint(a, b) { return a.right <= b.left || a.left >= b.right || a.bottom <= b.top || a.top >= b.bottom; }
test('real review/running/persisted events place the pointer on the exact visible session and follow layout', async function (t) {
  var p = await propose('hold'); await phase('review');
  var g = await geometry(); t.diagnostic(JSON.stringify(g));
  assert.equal(g.targeted, 'true'); assert.equal(g.pointerEvents, 'none'); assert.equal(g.live, 'polite');
  assert.ok(Math.abs(g.tipX - (g.target.right - 2)) < 1); assert.ok(Math.abs(g.tipY - (g.target.top + g.target.bottom) / 2) < 1);
  assert.equal(g.grouped, true); assert.equal(g.name, 'Clay'); assert.equal(g.identityHeight, 26);
  assert.equal(g.avatarAlt, ''); assert.equal(g.statusCount, 0); assert.ok(g.nameWidth > 20);
  assert.ok(g.bubble.top >= g.identity.bottom + 3); assert.ok(g.bubble.top >= g.avatarBottom); assert.ok(disjoint(g.rect, g.target)); assert.ok(disjoint(g.rect, g.composer)); assert.ok(disjoint(g.rect, g.panel));
  await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.dataset.savedOrigin = row.dataset.controlSessionOrigin; row.dataset.controlSessionOrigin = 'replaced'; });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
  await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.dataset.controlSessionOrigin = row.dataset.savedOrigin; });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'true'; });
  await page.evaluate(function () { var row = document.querySelector('.session-item[data-session-id]').getBoundingClientRect(); var cover = document.createElement('div'); cover.id = 'fixture-occlusion'; cover.style.cssText = 'position:fixed;z-index:500;left:' + row.left + 'px;top:' + row.top + 'px;width:' + row.width + 'px;height:' + row.height + 'px;background:var(--bg)'; document.body.appendChild(cover); });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
  await page.locator('#fixture-occlusion').evaluate(function (element) { element.remove(); });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'true'; });
  await page.evaluate(function () {
    var row = document.querySelector('.session-item[data-session-id]').getBoundingClientRect();
    var obstacle = document.createElement('div'); obstacle.id = 'fixture-placement'; obstacle.setAttribute('role', 'dialog');
    obstacle.style.cssText = 'position:fixed;left:' + (row.right + 4) + 'px;top:' + (row.top + row.height / 2 + 85) + 'px;width:250px;height:100px;'; document.body.appendChild(obstacle);
  });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').classList.contains('is-flipped'); });
  var flipped = await geometry(); assert.equal(flipped.targeted, 'true'); assert.match(flipped.cursorTransform, /-1/);
  assert.ok(flipped.bubble.top > flipped.identity.bottom); assert.ok(Math.abs(flipped.tipY - g.tipY) < 1); assert.ok(disjoint(flipped.rect, flipped.target));
  await page.screenshot({ path: '/tmp/clay-control-pill-flipped.png' });
  await page.locator('#fixture-placement').evaluate(function (element) { element.remove(); });
  await page.evaluate(function () {
    var row = document.querySelector('.session-item[data-session-id]'), marker = document.createElement('span'); marker.id = 'fixture-row-home';
    row.before(marker); document.body.appendChild(row); row.style.cssText = 'position:fixed;left:550px;top:350px;width:120px;z-index:150;';
  });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').classList.contains('is-reversed'); });
  var reversed = await geometry(); assert.equal(reversed.targeted, 'true'); assert.match(reversed.cursorTransform, /-1/);
  assert.ok(Math.abs(reversed.tipX - (reversed.target.left + 2)) < 1); assert.ok(Math.abs(reversed.identity.right - reversed.rect.right) < 1);
  assert.ok(reversed.bubble.top > reversed.identity.bottom); assert.ok(disjoint(reversed.rect, reversed.target));
  await page.screenshot({ path: '/tmp/clay-control-pill-reversed.png' });
  await page.evaluate(function () { var row = document.querySelector('.session-item[data-session-id]'); document.getElementById('fixture-row-home').replaceWith(row); row.removeAttribute('style'); });
  await answer(p); await phase('running');
  await page.screenshot({ path: '/tmp/clay-control-activity-desktop-light.png' });
  await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.style.marginTop = '90px'; window.dispatchEvent(new Event('resize')); });
  await page.waitForTimeout(80); var moved = await geometry(); assert.ok(moved.tipY > g.tipY + 80);
  await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.hidden = true; });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
  await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.hidden = false; row.style.marginTop = '1600px'; window.dispatchEvent(new Event('scroll')); });
  await page.waitForTimeout(80); assert.equal((await geometry()).targeted, 'false');
  await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.remove(); });
  await page.waitForTimeout(80); assert.equal((await geometry()).targeted, 'false');
  var released = await (await page.request.get(origin + '/fixture/activity-release')).json(); t.diagnostic(JSON.stringify(released));
  assert.equal(released.released, true); var result = await (await p.pending).json(); assert.equal(result.persisted, true); await phase('persisted');
});
test('real decline, persistence failure and uncertain provider response retain truthful outcomes', async function () {
  for (var mode of ['normal', 'fail', 'unknown']) {
    var p = await propose(mode); await answer(p, mode === 'normal' ? 'Keep unchanged' : null);
    var result = await (await p.pending).json();
    assert.equal(result.status, mode === 'normal' ? 'cancelled' : 'failed');
    await phase(mode === 'normal' ? 'cancelled' : mode === 'unknown' ? 'unknown' : 'failed');
  }
});
test('narrow light/dark and reduced motion retain a non-interactive status, keyboard reviews and writing', async function () {
  await page.setViewportSize({ width: 390, height: 844 });
  await page.emulateMedia({ reducedMotion: 'reduce' });
  var p = await propose('hold'); await phase('review');
  assert.equal((await geometry()).targeted, 'false'); await answer(p, null, true); await phase('running');
  await page.screenshot({ path: '/tmp/clay-control-activity-narrow-light.png' });
  await page.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
  await frame.locator('body').evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
  await page.screenshot({ path: '/tmp/clay-control-activity-narrow-dark.png' });
  var g = await geometry(); assert.ok(g.rect.left >= 0 && g.rect.right <= 390); assert.ok(disjoint(g.rect, g.composer));
  assert.equal(await page.locator('#clay-control-activity img').evaluate(function (img) { return getComputedStyle(img).animationName; }), 'none');
  await frame.locator('#input').fill('A preserved keyboard draft');
  assert.equal(await frame.locator('#input').inputValue(), 'A preserved keyboard draft');
  await page.request.get(origin + '/fixture/activity-release'); await p.pending;
  await page.setViewportSize({ width: 1280, height: 800 }); await page.screenshot({ path: '/tmp/clay-control-activity-desktop-dark.png' });
});
test('Stop withdraws immediately and delayed replies cannot revive the activity', async function () {
  var p = await propose('hold'); await answer(p); await phase('running');
  await frame.locator('#input').fill('Keep this draft after Stop');
  await page.request.get(origin + '/fixture/stream');
  await frame.locator('#generation-stop:visible, #send-btn.stop:visible').first().click();
  await page.locator('#clay-control-activity').waitFor({ state: 'detached' });
  await page.request.get(origin + '/fixture/activity-release'); await p.pending;
  await page.waitForTimeout(350); assert.equal(await page.locator('#clay-control-activity').count(), 0);
});
test('a delayed older result cannot overwrite a newer reviewed activity', async function () {
  var first = await propose('hold'); await answer(first); await phase('running');
  var second = await propose('normal'); await phase('review'); await answer(second);
  assert.equal((await (await second.pending).json()).status, 'failed'); await phase('failed');
  var current = await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; });
  await page.request.get(origin + '/fixture/activity-release'); await first.pending;
  await page.waitForTimeout(150);
  assert.equal(await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; }), current);
  await phase('failed');
});
test('reviewed navigation separates service delivery and actual rendered conversation evidence', async function (t) {
  var pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha'); pending.catch(function () {});
  var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor();
  await phase('review'); await answer({ review: review }); var result = await (await pending).json();
  assert.equal(result.delivered, true); assert.equal(result.visible, 'unknown');
  await page.waitForTimeout(500); t.diagnostic(JSON.stringify(events.map(function (item) { return { domain: item.domain, phase: item.phase }; })));
  assert.ok(events.some(function (item) { return item.domain === 'screen' && item.phase === 'running'; }));
  assert.equal(events.some(function (item) { return item.domain === 'screen' && ['persisted', 'completed'].includes(item.phase); }), false);
  var observed = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + result.deliveryId)).json();
  assert.equal(observed.visible, true); assert.equal(observed.renderStatus, 'rendered');
  assert.ok(events.some(function (item) { return item.domain === 'screen' && item.phase === 'rendered'; }));
});

test('delayed source packets cannot cross a same-ID source replacement, document reload or live Mate replacement', async function () {
  var p = await propose('normal'); await phase('review');
  var old = events.find(function (item) { return item.phase === 'review'; });
  await answer(p, 'Keep unchanged'); await p.pending;
  var originalLocalId = old.sourceSessionId;
  async function reopen() {
    await page.reload(); await page.locator('.icon-strip-brand:not([disabled])').waitFor(); await page.locator('.icon-strip-brand').click();
    frame = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
    await page.waitForFunction(function () { var f = document.querySelector('#contextual-clay iframe[data-canonical="true"]'); return f && f.conversation && f.conversation.identity().ready; });
  }
  async function replay(message) {
    await frame.locator('body').evaluate(function (body, packet) { window.parent.postMessage({ type: 'clay-control-presentation', message: packet }, location.origin); }, message);
    await page.waitForTimeout(100);
  }
  try {
    var priorViewer = old.recipientViewerRef;
    await page.locator('#contextual-clay iframe[data-canonical="true"]').evaluate(function (element) { element.contentWindow.location.reload(); });
    await page.waitForFunction(function (viewer) { var f = document.querySelector('#contextual-clay iframe[data-canonical="true"]'); return f && f.conversation && f.conversation.document === f.contentDocument && f.conversation.identity().ready && f.conversation.identity().viewerRef !== viewer; }, priorViewer);
    await replay(Object.assign({}, old, { order: Number.MAX_SAFE_INTEGER, sequence: Number.MAX_SAFE_INTEGER }));
    await phase('cancelled');
    var afterReload = await propose('normal'); await phase('review'); await answer(afterReload, 'Keep unchanged'); await afterReload.pending;
    var replaced = await (await page.request.get(origin + '/fixture/activity-source-replace')).json();
    assert.equal(replaced.localId, originalLocalId); assert.notEqual(replaced.origin, old.sourceSessionOriginId);
    await reopen(); await replay(old);
    assert.equal(await page.locator('#clay-control-activity').count(), 0, 'An unseen delayed packet from the old durable source cannot render');
    var fresh = await propose('normal'); await phase('review');
    var id = await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; });
    await replay(Object.assign({}, old, { order: Number.MAX_SAFE_INTEGER, sequence: Number.MAX_SAFE_INTEGER }));
    assert.equal(await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; }), id, 'Identity rejection happens before receipt order changes');
    await answer(fresh, 'Keep unchanged'); await fresh.pending; await phase('cancelled');
    var beforeMate = events.filter(function (item) { return item.id === id; }).at(-1);
    await page.request.get(origin + '/fixture/activity-mate-replace'); await reopen(); await replay(beforeMate);
    assert.equal(await page.locator('#clay-control-activity').count(), 0);
    var next = await propose('normal'); await phase('review'); await answer(next, 'Keep unchanged'); await next.pending; await phase('cancelled');
    var live = events.filter(function (item) { return item.phase === 'review'; }).at(-1);
    assert.notEqual(live.sourceMateStamp, beforeMate.sourceMateStamp);
    // Preserve the live connection and origin to isolate the registry stamp guard.
    await replay(Object.assign({}, live, { sourceMateStamp: beforeMate.sourceMateStamp, order: Number.MAX_SAFE_INTEGER, sequence: Number.MAX_SAFE_INTEGER }));
    await phase('cancelled');
  } finally { await page.request.get(origin + '/fixture/activity-source-restore'); }
});

test('ordinary restricted navigation retains its real long Mate identity across compact themes and image failure', async function () {
  await page.locator('.icon-strip-project-mate').nth(1).click();
  var ordinary = page.frameLocator('#contextual-clay iframe[data-canonical="false"]').last();
  await ordinary.locator('#input-wrapper:not([inert])').waitFor();
  await page.waitForFunction(function () { var f = Array.from(document.querySelectorAll('#contextual-clay iframe[data-canonical="false"]')).find(function (item) { return !item.hidden; }); return f && f.conversation && f.conversation.identity().ready; });
  await page.locator('[data-clay-context]').filter({ hasText: 'Alpha integration · Next message' }).waitFor();
  await ordinary.locator('#input').fill('Review this selected conversation.'); await ordinary.locator('#send-btn').click();
  await ordinary.locator('#messages').getByText('Review this selected conversation.', { exact: true }).waitFor();
  await page.locator('[data-clay-context]').filter({ hasText: 'Alpha integration · Shared' }).waitFor();
  async function call(name, args) { return (await page.request.get(origin + '/fixture/context-tool?index=1&name=' + name + '&args=' + encodeURIComponent(JSON.stringify(args || {})))).json(); }
  var context = await call('read_current_context'); assert.equal(context.status, 'shared_snapshot');
  var nav = call('open_context_conversation', { contextRef: context.contextRef, requestId: 'pill-long-name', why: 'Open the reviewed conversation' });
  var review = ordinary.locator('.ask-user-container:not(.answered)').last(); await review.waitFor();
  await review.getByRole('button', { name: 'Apply exact change', exact: true }).click(); await review.getByRole('button', { name: 'Submit', exact: true }).click();
  assert.equal((await nav).delivered, true);
  await page.locator('#clay-control-activity .clay-control-identity').waitFor();
  var actor = await page.locator('#clay-control-activity .clay-control-name').textContent();
  assert.match(actor, /Release planning/);
  var avatar = await page.locator('#clay-control-activity img').getAttribute('src');
  assert.doesNotMatch(avatar, /lead-clay/);
  for (var variant of [{ width: 1280, theme: 'light' }, { width: 1280, theme: 'dark' }, { width: 390, theme: 'light' }, { width: 390, theme: 'dark' }]) {
    await page.setViewportSize({ width: variant.width, height: variant.width === 390 ? 844 : 800 });
    await page.evaluate(async function (theme) { (await import('./modules/theme.js')).applyTheme('clay-' + theme); }, variant.theme);
    await page.waitForTimeout(50);
    var g = await geometry(); assert.equal(g.name, actor); assert.equal(g.grouped, true); assert.equal(g.nameOverflow, 'ellipsis');
    assert.ok(g.nameWidth > 100); assert.ok(g.identity.right <= g.rect.right); assert.ok(g.bubble.top >= g.identity.bottom + 3);
    assert.ok(g.rect.left >= 0 && g.rect.right <= variant.width); assert.ok(disjoint(g.rect, g.composer));
    await page.screenshot({ path: '/tmp/clay-control-pill-ordinary-' + variant.width + '-' + variant.theme + '.png' });
  }
  await page.route('**/fixture/missing-avatar.png', function (route) { return route.abort(); });
  await page.locator('#clay-control-activity img').evaluate(function (image) { image.src = '/fixture/missing-avatar.png'; });
  await page.locator('#clay-control-activity img').waitFor({ state: 'hidden' });
  assert.equal(await page.locator('#clay-control-activity img').isVisible(), false);
  assert.equal(await page.locator('#clay-control-activity .clay-control-name').textContent(), actor);
  assert.equal(await page.locator('#clay-control-activity .clay-control-bubble').isVisible(), true);
});

async function objectProposal(kind) {
  var pending = page.request.get(origin + '/fixture/activity-object?kind=' + kind); pending.catch(function () {});
  var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await phase('review');
  return { pending: pending, review: review };
}
async function uncoverWorkbench() {
  await page.setViewportSize({ width: 1600, height: 900 });
  var handle = await page.locator('#contextual-clay > [data-clay-resize="s"]').boundingBox();
  await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2); await page.mouse.down(); await page.mouse.move(handle.x + handle.width / 2, handle.y - 280, { steps: 8 }); await page.mouse.up();
  var header = page.locator('#contextual-clay > header'), box = await header.boundingBox();
  await page.mouse.move(box.x + 45, box.y + 16); await page.mouse.down(); await page.mouse.move(160, 500, { steps: 8 }); await page.mouse.up();
  assert.ok((await header.boundingBox()).y > 400);
}
async function targetGeometry(kind) {
  for (var attempt = 0; attempt < 120; attempt++) {
  var value = await page.evaluate(async function (expected) {
    var state = (await import('./modules/store.js')).store;
    var targets = await import('./modules/clay-control-targets.js');
    var activity = state.get('clayControlActivity'), el = document.getElementById('clay-control-activity');
    if (!activity || !activity.message.target || activity.message.target.kind !== expected || !el || el.dataset.targeted !== 'true') return false;
    var target = targets.controlTarget(activity.message.target); if (!target || el.getAnimations().some(function (a) { return a.playState === 'running'; })) return false;
    var rect = target.getBoundingClientRect(), box = el.getBoundingClientRect(), style = getComputedStyle(el), reversed = el.classList.contains('is-reversed');
    var x = box.left + parseFloat(style.getPropertyValue('--tip-x')), y = box.top + parseFloat(style.getPropertyValue('--tip-y'));
    if (!Number.isFinite(x) || !Number.isFinite(y)) return false;
    if (Math.abs(x - (reversed ? rect.left + 2 : rect.right - 2)) >= 1 || Math.abs(y - (rect.top + rect.height / 2)) >= 1) return false;
    return { kind: activity.message.target.kind, x: x, y: y, left: rect.left, right: rect.right, middle: rect.top + rect.height / 2, reversed: reversed };
  }, kind);
  if (value) return value;
  await page.waitForTimeout(50);
  }
  await page.screenshot({ path: '/tmp/clay-object-failure.png' });

  throw new Error('The exact ' + kind + ' target did not acquire a visible pointer');
}
function exactTip(g) { assert.ok(Math.abs(g.x - (g.reversed ? g.left + 2 : g.right - 2)) < 1); assert.ok(Math.abs(g.y - g.middle) < 1); }

test('visible Mate and owned project rail adapters use real profile and assignment proposals', async function () {
  var p = await objectProposal('mate'); exactTip(await targetGeometry('mate'));
  await page.screenshot({ path: '/tmp/clay-control-adapter-mate.png' });
  await answer(p, 'Keep unchanged'); assert.equal((await (await p.pending).json()).status, 'cancelled');
  var result = await (await page.request.get(origin + '/fixture/activity-object?kind=assignment')).json(); assert.equal(result.status, 'proposed');
  exactTip(await targetGeometry('project'));
  await page.screenshot({ path: '/tmp/clay-control-adapter-project-assignment.png' });
  var card = frame.locator('[data-assignment-id]').last();
  await card.getByRole('button', { name: /Cancel|Decline|Keep/i }).click();
  await phase('cancelled');
});

test('file, Issue and Log adapters require the actual visible object revision', async function () {
  var seeded = await (await page.request.get(origin + '/fixture/activity-object?kind=seed')).json(); assert.ok(seeded.issue.ref, JSON.stringify(seeded));
  await uncoverWorkbench();
  for (var kind of ['file', 'issue', 'log']) {
    await page.evaluate(async function (item) {
      if (item === 'file') (await import('./modules/filebrowser.js')).openFile('activity-target.txt');
      if (item === 'issue') (await import('./modules/issues.js')).openIssues();
      if (item === 'log') (await import('./modules/project-logs.js')).openProjectLogs();
    }, kind);
    var visible = kind === 'file' ? '#file-viewer-path .current' : kind === 'issue' ? '.issue-row' : '.project-log-row';
    await page.locator(visible).first().waitFor();
    var p = await objectProposal(kind); exactTip(await targetGeometry(kind === 'file' ? 'file' : 'record'));
    await page.screenshot({ path: '/tmp/clay-control-adapter-' + kind + '.png' });
    if (kind === 'file') {
      await page.emulateMedia({ reducedMotion: 'reduce', colorScheme: 'dark' });
      await page.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
      await page.setViewportSize({ width: 390, height: 844 });
      await page.waitForFunction(function () { var e = document.getElementById('clay-control-activity'); return e && e.dataset.targeted === 'false' && e.classList.contains('is-inline'); });
      var visibleStatus = await page.locator('#clay-control-activity').evaluate(function (element) { var r = element.getBoundingClientRect(); var hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2); return hit && element.parentElement.contains(hit) && element.parentElement.id === 'file-viewer'; }); assert.equal(visibleStatus, true);
      var narrow = await geometry(); assert.equal(narrow.name, 'Clay'); assert.equal(narrow.grouped, true); assert.ok(narrow.rect.right <= 390); assert.ok(disjoint(narrow.rect, narrow.composer));
      await page.screenshot({ path: '/tmp/clay-control-adapter-file-narrow-dark-reduced.png' });
      await page.setViewportSize({ width: 1600, height: 900 });
      await page.emulateMedia({ reducedMotion: 'no-preference', colorScheme: 'light' });
      await page.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-light'); });
    }
    if (kind !== 'file') {
      await page.locator(visible).first().evaluate(function (row) { row.dataset.savedRevision = row.dataset.controlRevision; row.dataset.controlRevision = 'replaced'; });
      await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
      await page.locator(visible).first().evaluate(function (row) { row.dataset.controlRevision = row.dataset.savedRevision; });
      await targetGeometry('record');
    }
    await answer(p, 'Keep unchanged'); assert.equal((await (await p.pending).json()).status, 'cancelled');
  }
});

test('navigation receipt cannot confirm an invisible transcript or a different durable origin', async function () {
  await page.locator('#messages').evaluate(function (element) { element.style.visibility = 'hidden'; });
  var pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha');
  var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
  var delivery = await (await pending).json(); assert.equal(delivery.delivered, true);
  await page.waitForTimeout(5200);
  var receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
  assert.equal(receipt.visible, 'unknown'); assert.equal(receipt.renderStatus, 'unknown');
  assert.equal(events.some(function (event) { return event.phase === 'rendered'; }), false);
  await page.locator('#messages').evaluate(function (element) { element.style.visibility = ''; element.dataset.controlSessionOrigin = 'old-rendered-origin'; });
  pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha');
  review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
  delivery = await (await pending).json(); await page.waitForTimeout(5200);
  receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
  assert.equal(receipt.visible, 'unknown'); assert.equal(receipt.renderStatus, 'unknown');
  assert.equal(events.some(function (event) { return event.phase === 'rendered'; }), false);
});

test('project schedule row adapter follows the final human creation and exact task revision', async function () {
  await uncoverWorkbench();
  var p = await objectProposal('schedule'); await answer(p);
  assert.equal((await (await p.pending).json()).status, 'proposed');
  await page.getByRole('button', { name: 'Create task', exact: true }).click();
  await page.locator('.scheduled-task-row[data-task-id]').first().waitFor().catch(async function (error) { console.log(await page.locator('#scheduled-tasks-panel').textContent()); throw error; });
  if (!(await page.locator('#contextual-clay').isVisible())) await page.locator('.icon-strip-brand').click();
  await uncoverWorkbench();
  p = await objectProposal('schedule'); exactTip(await targetGeometry('schedule'));
  await page.screenshot({ path: '/tmp/clay-control-adapter-schedule.png' });
  await page.locator('.scheduled-task-row').first().evaluate(function (row) { row.dataset.controlRevision = 'replacement-task-revision'; });
  await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
  await answer(p, 'Keep unchanged'); assert.equal((await (await p.pending).json()).status, 'cancelled');
});

test('Git path and shell terminal adapters use authorised repository and terminal identities', async function () {
  await page.setViewportSize({ width: 1600, height: 900 });
  var p = await objectProposal('terminal_create'); await answer(p);
  var created = await (await p.pending).json(); assert.equal(created.runtimeEffect, 'shell_created');
  await page.evaluate(async function () { (await import('./modules/terminal.js')).openTerminal(); });
  await page.locator('.terminal-tab').first().waitFor().catch(async function (error) { await page.screenshot({path:'/tmp/clay-terminal-failure.png'}); console.log(await page.locator('#terminal-container').evaluate(function(e){return e.outerHTML.slice(0,1500);})); throw error; });
  var terminalHeader = await page.locator('#contextual-clay > header').boundingBox();
  await page.mouse.move(terminalHeader.x + 45, terminalHeader.y + 16); await page.mouse.down(); await page.mouse.move(160, 48, { steps: 8 }); await page.mouse.up();
  p = await objectProposal('terminal'); exactTip(await targetGeometry('terminal'));
  await page.screenshot({ path: '/tmp/clay-control-adapter-terminal.png' }); await answer(p, 'Keep unchanged'); await p.pending;
  await page.evaluate(async function () { (await import('./modules/terminal.js')).closeTerminal(); });
  await (await page.request.get(origin + '/fixture/activity-object?kind=git_seed')).json();
  await page.evaluate(async function () { await (await import('./modules/git-panel.js')).refreshGitSummary(); });
  await page.getByRole('button', { name: 'Close Clay conversation', exact: true }).click();
  await page.locator('#git-placard-more').click();
  await page.locator('.git-file-row').first().waitFor();
  await page.locator('.icon-strip-brand').click();
  await page.waitForTimeout(300);
  var box = await page.locator('#contextual-clay > header').boundingBox();
  await page.mouse.move(box.x + 100, box.y + 16); await page.mouse.down(); await page.mouse.move(1120, 100, { steps: 8 }); await page.mouse.up();
  assert.ok((await page.locator('#contextual-clay').boundingBox()).x > 500);
  await page.evaluate(async function () { await (await import('./modules/git-panel.js')).refreshGitStatus(); });
  p = await objectProposal('git'); exactTip(await targetGeometry('git'));
  await page.screenshot({ path: '/tmp/clay-control-adapter-git.png' }); await answer(p, 'Keep unchanged'); await p.pending;
});


test('render acknowledgement requires the reviewed visible entry and stops on a changed or hidden viewer', async function () {
  var pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha&entry=0');
  var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
  var delivery = await (await pending).json();
  await page.waitForFunction(function () { var e = document.getElementById('messages'); return e && e.dataset.controlHistoryRevision; });
  for (var attempt = 0; attempt < 50; attempt++) {
    var receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
    if (receipt.visible === true) break;
    await page.waitForTimeout(50);
  }
  assert.equal(receipt.visible, true); assert.equal(receipt.renderStatus, 'rendered');
  assert.ok(events.find(function (event) { return event.type === 'clay_navigation_apply' && event.target.entryIndex === 0 && event.target.historyRevision; }));
  for (var invalidation of ['hidden', 'disconnect', 'changed-route']) {
    await page.locator('#messages').evaluate(function (element) { element.style.visibility = 'hidden'; });
    pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha');
    review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
    delivery = await (await pending).json();
    await page.waitForFunction(async function () { return !!(await import('./modules/store.js')).store.get('clayNavigationRendering'); });
    await page.evaluate(async function (change) {
      if (change === 'hidden') { Object.defineProperty(document, 'hidden', { configurable: true, value: true }); document.dispatchEvent(new Event('visibilitychange')); }
      if (change === 'disconnect') (await import('./modules/ws-ref.js')).getWs().close();
      if (change === 'changed-route') (await import('./modules/store.js')).store.set({ currentSlug: 'changed-view' });
    }, invalidation);
    await page.waitForTimeout(5200);
    receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
    assert.equal(receipt.visible, 'unknown', invalidation); assert.notEqual(receipt.renderStatus, 'rendered', invalidation);
    await page.goto(origin + '/fixture/login?to=alpha');
    await page.locator('.icon-strip-brand:not([disabled])').waitFor(); await page.locator('.icon-strip-brand').click();
    frame = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
    await page.waitForFunction(function () { var f = document.querySelector('#contextual-clay iframe'); return f && f.conversation && f.conversation.identity().ready; });
  }
});