Ctrl K
ComunidadGuías…

scripts/verify-data.ts

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

Archivo original ↗Preguntar a KiBot
// Full app + real DuckDB, with synthetic data and a disposable home only.
import assert from "node:assert/strict";
import { spawn, type ChildProcess } from "node:child_process";
import { randomUUID } from "node:crypto";
import { closeSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
import { createServer } from "node:http";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { closeBrowserSession } from "../server/browser-engine.ts";
import { waitForExit } from "../server/testing/cleanup.ts";
import { DATA_ROUTES, DATA_TOOL_NAMES, type DataSheet } from "../shared/data-surface.ts";
import { launchVerificationServer, runControlOmb, verificationServerEnvironment } from "./control-omb.ts";
import { agentBrowser, ensureUiBrowser, sessionEnv, type UiHandle } from "./testing/control-omb-ui.ts";
import { mountPreview, parkUntilSignal, type MountedPreview } from "./testing/preview-fixture.ts";

assert(process.argv.slice(2).every((arg) => arg === "--check"), "Usage: node --experimental-strip-types scripts/verify-data.ts [--check]");
const check = process.argv.includes("--check");
const root = fileURLToPath(new URL("..", import.meta.url));
const fixture = await launchVerificationServer();
const { url, dataDir, logPath } = fixture.info;
const stopped = parkUntilSignal();
const capabilityKey = randomUUID();
let server: ChildProcess | undefined;
let preview: MountedPreview | undefined;
let handle: UiHandle | undefined;

async function until(test: () => Promise<boolean>, message: string, timeout = 30_000) {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    if (await test()) return;
    await new Promise((resolve) => setTimeout(resolve, 100));
  }
  throw new Error(message);
}

async function api(method: string, path: string, body?: unknown, headers: Record<string, string> = {}) {
  const response = await fetch(url + path, {
    method, headers: { "content-type": "application/json", origin: url, ...headers },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(60_000),
  });
  const text = await response.text();
  assert(response.ok, `${method} ${path}: ${response.status} ${text.slice(0, 500)}`);
  return text ? JSON.parse(text) : null;
}

async function startServer() {
  const log = openSync(logPath, "a", 0o600);
  server = spawn(process.execPath, ["--experimental-strip-types", join(root, "server/index.ts")], {
    cwd: root,
    env: { ...verificationServerEnvironment({}, dataDir, Number(new URL(url).port)), OMB_TEST_INTERNAL_CAPABILITY_KEY: capabilityKey },
    stdio: ["ignore", log, log],
  });
  closeSync(log);
  await until(async () => {
    assert(server?.exitCode === null && server?.signalCode === null, `Fixture server exited; see ${logPath}`);
    try { return (await fetch(url + "/api/health", { signal: AbortSignal.timeout(1000) })).ok; } catch { return false; }
  }, `Fixture server did not start; see ${logPath}`);
}

try {
  // The capability minting route exists only in this restarted fixture server.
  await waitForExit(fixture.child, { signal: "SIGTERM" });
  await startServer();
  await api("PATCH", "/api/config", { language: "en" });
  const { bot } = await runControlOmb(["new-bot", "--name", "Data review", "--url", url]) as { bot: { id: string; activeTaskId: string } };
  const minted = await api("POST", "/api/testing/internal-capability", { botId: bot.id, threadId: bot.activeTaskId, kind: "data" }, { "x-openmausbot-test-capability": capabilityKey });
  const mcp = async (method: string, params?: unknown) =>
    (await api("POST", "/api/internal/data/mcp", { method, params }, { authorization: `Bearer ${minted.token}` })).result;
  const call = async (name: string, args: Record<string, unknown>) => {
    const result = await mcp("tools/call", { name, arguments: args });
    assert(!result.isError, `${name}: ${JSON.stringify(result.structuredContent ?? result.content)}`);
    return result.structuredContent;
  };
  assert.deepEqual((await mcp("tools/list")).tools.map((tool: { name: string }) => tool.name), [...DATA_TOOL_NAMES]);

  const workspace = join(dataDir, "workspaces", bot.id);
  mkdirSync(workspace, { recursive: true });
  mkdirSync(join(dataDir, "Downloads"), { recursive: true });
  const csv = join(workspace, "orders.csv");
  const regions = ["India", "Europe", "North America", "東京"];
  const rows = ["order_id,ordered_at,region,product,quantity,revenue,channel,note"];
  for (let i = 0; i < 100_000; i++) {
    rows.push(`${i + 1},2026-09-${String(i % 28 + 1).padStart(2, "0")},${regions[i % 4]},${["Notebook", "Keyboard", "Display"][i % 3]},${i % 5 + 1},${((i % 173 + 1) * 12.5).toFixed(2)},${i % 2 ? "Online" : "Retail"},${i === 99_999 ? "Final order" : i % 97 ? "Subscription renewal" : '"Delivery, then setup"'}`);
  }
  writeFileSync(csv, rows.join("\n") + "\n");
  const loaded = await call("data_load", { source: csv, name: "orders" });
  assert.equal(loaded.tables[0].rowCount, 100_000);
  const events = join(workspace, "events.json");
  writeFileSync(events, JSON.stringify([{ event: "trial", count: 32 }, { event: "paid", count: 18 }, { event: "取消", count: null }]));
  await call("data_load", { source: events, name: "events" });
  const described = await call("data_describe", { target: "orders" });
  assert.equal(described.columns.find((column: { name: string }) => column.name === "region").approxUnique, 4);
  await call("data_sql", { sql: "CREATE TABLE regional_totals AS SELECT region, sum(revenue) AS revenue FROM orders GROUP BY region" });

  for (let i = 1; i <= 17; i++) {
    await call("data_show", { kind: "table", title: `Snapshot ${String(i).padStart(2, "0")}`, sql: `SELECT region, count(*) AS orders, sum(revenue) AS revenue FROM orders WHERE order_id <= ${i * 5000} GROUP BY region ORDER BY region` });
  }
  const regional = await call("data_show", { kind: "chart", title: "Revenue by region", table: "orders", chart: { type: "bar", x: "region", y: "revenue", agg: "sum" } });
  const daily = await call("data_show", { kind: "chart", title: "Daily revenue", table: "orders", chart: { type: "line", x: "ordered_at", y: "revenue", agg: "sum", timeUnit: "day" } });
  const latest = await call("data_show", { kind: "table", title: "All 100,000 orders", sql: "SELECT * FROM orders ORDER BY order_id" });
  const sheet = (await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet;
  assert.equal(sheet.cards.length, 20);
  assert(sheet.cards.every((card) => card.status === "ready"));
  const lastPage = await api("POST", DATA_ROUTES.page(bot.id), { cardId: latest.id, offset: 99_990, limit: 10 });
  assert.equal(lastPage.rowCount, 100_000);
  assert.equal(String(lastPage.rows.at(-1)[0]), "100000");
  const filtered = await api("POST", DATA_ROUTES.page(bot.id), { cardId: latest.id, offset: 0, limit: 10, filter: "Final order" });
  assert.equal(filtered.rowCount, 1);
  const sorted = await api("POST", DATA_ROUTES.page(bot.id), { cardId: latest.id, offset: 0, limit: 10, sort: { column: "order_id", direction: "desc" } });
  assert.equal(String(sorted.rows[0][0]), "100000");
  const exported = await api("POST", DATA_ROUTES.export(bot.id), { cardId: regional.id, format: "csv" });
  assert.equal(dirname(exported.path), join(dataDir, "Downloads"));
  assert.match(readFileSync(exported.path, "utf8"), /India/);

  // Materialized card tables and source metadata must survive server restart.
  await waitForExit(server, { signal: "SIGTERM" });
  await startServer();
  const restored = await api("GET", DATA_ROUTES.sheet(bot.id));
  assert.equal(restored.sheet.cards.length, 20);
  assert.deepEqual(restored.sheet.sources.map((source: { name: string }) => source.name).sort(), ["events", "orders"]);
  assert(restored.tables.some((table: { name: string }) => table.name === "regional_totals"));
  assert.equal((await api("POST", DATA_ROUTES.page(bot.id), { cardId: latest.id, offset: 99_999, limit: 1 })).rows[0][7], "Final order");

  preview = await mountPreview(fixture, { entry: "/scripts/testing/threads-preview.tsx", route: "/__data.html", title: "Isolated OMB Data", logLevel: "warn" });
  const { binary, chrome } = await ensureUiBrowser(process.env, (line) => console.error(`Data fixture: ${line}`));
  // The private HOME isolates sessions; one letter fits macOS's socket-path limit.
  handle = { url, previewUrl: preview.previewUrl, binary, chrome, home: dataDir, session: "d", botId: bot.id, logPath };
  await agentBrowser(binary, sessionEnv(handle), ["open", preview.previewUrl], 120_000);
  const handlePath = join(dataDir, "ui.json");
  writeFileSync(handlePath, JSON.stringify(handle, null, 2) + "\n", { mode: 0o600 });
  const ui = (verb: string, ...args: string[]) => runControlOmb(["ui", verb, "--ui", handlePath, ...args]) as Promise<Record<string, any>>;
  const evaluate = async (js: string) => (await ui("eval", "--js", js)).result;
  const click = (name: string) => ui("click", "--name", name);
  // The selected panel shares its tab's name; click the tab itself.
  const clickTab = (tour: string) => evaluate(`(() => { const tab = document.querySelector('[data-tour=${tour}]'); if (!tab) throw new Error('Missing tab ${tour}'); tab.click(); return true; })()`);
  const browser = (...args: string[]) => agentBrowser(binary, sessionEnv(handle!), args);
  const drag = async (element: string, dx: number, dy: number) => {
    const { x, y } = await evaluate(`(() => { const { left, top, width, height } = (${element}).getBoundingClientRect(); return { x: Math.round(left + width / 2), y: Math.round(top + height / 2) }; })()`);
    await browser("mouse", "move", String(x), String(y));
    await browser("mouse", "down");
    try {
      for (let step = 1; step <= 4; step++) await browser("mouse", "move", String(Math.round(x + dx * step / 4)), String(Math.round(y + dy * step / 4)));
    } finally { await browser("mouse", "up"); }
  };
  const splitter = "document.querySelector('[data-testid=data-query-resize]')";
  const dragSql = (dy: number) => drag(splitter, 0, dy);
  const resizeValue = () => evaluate(`Number(${splitter}.getAttribute('aria-valuenow'))`) as Promise<number>;
  const keyResize = async (key: string) => {
    await click("Resize SQL editor");
    await ui("press", "--keys", key);
  };
  const preserveQuery = async (resize: () => Promise<void>) => {
    const query = await evaluate(`(() => {
      window.__resizeSql = document.querySelector('[role=textbox][aria-label=SQL]');
      window.__resizeRuns = 0;
      window.__resizeFetch = window.fetch;
      window.fetch = (...args) => { if (String(args[0]).endsWith('/data/run')) window.__resizeRuns++; return window.__resizeFetch(...args); };
      return window.__resizeSql.textContent;
    })()`);
    try {
      await resize();
      assert.equal(await evaluate("window.__resizeRuns"), 0, "Resizing must not execute SQL");
      assert.equal(await evaluate("window.__resizeSql === document.querySelector('[role=textbox][aria-label=SQL]')"), true, "Resizing must not remount the SQL draft");
      assert.equal(await evaluate("window.__resizeSql.textContent"), query, "Resizing must preserve the SQL draft");
    } finally { await evaluate("window.fetch = window.__resizeFetch; true"); }
  };
  const chooseHistory = async (name: string) => {
    await click("History");
    const bounds = await evaluate("(() => { const menu = document.querySelector('[data-testid=data-history] [role=menu]').getBoundingClientRect(); const panel = document.querySelector('[data-testid=data-result]').getBoundingClientRect(); return { top: menu.top, bottom: menu.bottom, left: menu.left, right: menu.right, panelTop: panel.top, panelBottom: panel.bottom, panelLeft: panel.left, panelRight: panel.right }; })()");
    assert(bounds.top >= bounds.panelTop - 1 && bounds.bottom <= bounds.panelBottom + 1 && bounds.left >= bounds.panelLeft - 1 && bounds.right <= bounds.panelRight + 1, `History menu should remain visible within the workspace: ${JSON.stringify(bounds)}`);
    await evaluate(`[...document.querySelectorAll('[data-testid=data-history] [role=menuitemradio]')].find(element => element.textContent.trim() === ${JSON.stringify(name)}).click(); true`);
  };
  const active = "document.querySelectorAll('[data-testid^=data-card-], [data-testid=data-source-view]')";
  const gridCount = "document.querySelector('.data-grid')?.getAttribute('data-row-count')";
  const gridRows = "document.querySelectorAll('.data-grid .ag-row[row-index]')";
  const visibleChart = "[...document.querySelectorAll('[data-testid=data-chart] .vega-embed canvas, [data-testid=data-chart] .vega-embed svg')].find(element => element.checkVisibility({ visibilityProperty: true }))";
  const layout = () => evaluate(`(() => {
    const rect = (element) => { if (!element) return null; const { top, bottom, left, width, height } = element.getBoundingClientRect(); return { top, bottom, left, width, height }; };
    const panel = document.querySelector('[data-testid=data-panel]');
    const splitter = ${splitter};
    const sql = document.querySelector('[data-testid=sql-editor]');
    const sqlStyle = getComputedStyle(document.querySelector('[role=textbox][aria-label=SQL]'));
    const sqlContentHeight = sql.clientHeight - parseFloat(sqlStyle.paddingTop) - parseFloat(sqlStyle.paddingBottom);
    return { panel: rect(panel), sql: rect(sql), sqlContentHeight, sqlLineHeight: parseFloat(sqlStyle.lineHeight), dock: rect(document.querySelector('[data-testid=data-query]')), splitter: rect(splitter), limits: { min: Number(splitter.getAttribute('aria-valuemin')), max: Number(splitter.getAttribute('aria-valuemax')), value: Number(splitter.getAttribute('aria-valuenow')) }, result: rect(document.querySelector('[data-testid=data-result]')), grid: rect(document.querySelector('.data-grid')), viewport: { width: innerWidth, height: innerHeight }, fullscreen: document.fullscreenElement === panel };
  })()`);
  const measureChart = () => evaluate(`(() => { const box = document.querySelector('[data-testid=data-chart]').getBoundingClientRect(); const result = document.querySelector('[data-testid=data-result]').getBoundingClientRect(); const chart = (${visibleChart}).getBoundingClientRect(); return { width: chart.width, height: chart.height, availableWidth: box.width, availableHeight: box.height, top: chart.top, bottom: chart.bottom, visibleTop: result.top, visibleBottom: document.querySelector('[data-testid=data-result-footer]').getBoundingClientRect().top }; })()`);
  const assertDockedLayout = (measurement: Awaited<ReturnType<typeof layout>>) => {
    assert(measurement.result.bottom <= measurement.dock.top + 1, "Result should sit above the SQL dock");
    assert(Math.abs(measurement.result.bottom - measurement.splitter.top) <= 1 && Math.abs(measurement.splitter.bottom - measurement.dock.top) <= 1, "Splitter should separate the result from SQL");
    assert(Math.abs(measurement.panel.bottom - measurement.dock.bottom) <= 2, "SQL dock should stay at the bottom of the workspace");
    assert(measurement.sql.top >= measurement.dock.top && measurement.sql.bottom <= measurement.dock.bottom, "SQL should remain visible inside its dock");
    assert(measurement.sqlContentHeight >= measurement.sqlLineHeight, "SQL should retain one unclipped line even with wrapped table references");
    assert(measurement.limits.value >= measurement.limits.min && measurement.limits.value <= measurement.limits.max && Math.abs(measurement.limits.value - measurement.dock.height) <= 1, "Accessible resize bounds should match the visible SQL dock");
    if (measurement.grid) {
      assert(measurement.result.bottom - measurement.grid.bottom <= 40, "Table leaves unused space above the SQL dock");
      assert(measurement.grid.height >= measurement.result.height * 0.65, "Table should fill the remaining result area");
    }
  };
  const assertFooter = async (count: RegExp, chart = false) => {
    const footer = await evaluate(`(() => {
      const footer = document.querySelector('[data-testid=data-result-footer]');
      const { top, bottom } = footer.getBoundingClientRect();
      const controls = [...footer.querySelectorAll('button, summary[role=button]')].filter(element => element.checkVisibility());
      const panel = document.querySelector('[data-testid=data-panel]').getBoundingClientRect();
      return { text: footer.innerText, top, bottom, dockTop: document.querySelector('[data-testid=data-query]').getBoundingClientRect().top, splitterHeight: document.querySelector('[data-testid=data-query-resize]').getBoundingClientRect().height, labels: controls.map(element => element.getAttribute('aria-label') || element.textContent.trim()), controlsFit: controls.every(element => { const r = element.getBoundingClientRect(); return r.left >= panel.left && r.right <= panel.right && r.top >= top && r.bottom <= bottom; }), scrollWidth: footer.scrollWidth, width: footer.clientWidth };
    })()`);
    assert.match(footer.text, count, "Result footer should show its row and column counts");
    for (const label of ["Export", "History", "Enter fullscreen", ...(chart ? ["Chart", "Table"] : [])]) assert(footer.labels.includes(label), `${label} should share the counts footer`);
    assert(Math.abs(footer.bottom + footer.splitterHeight - footer.dockTop) <= 2, "Result footer should sit immediately above the SQL splitter");
    assert(footer.controlsFit && footer.scrollWidth <= footer.width + 1, "Counts and footer controls should fit inside the panel");
    return footer;
  };
  const receipt = `[data-data-result="${daily.id}"]`;
  const recordedSql = sheet.cards.find((card) => card.id === daily.id)?.sql;
  const openReceipt = () => evaluate(`[...document.querySelectorAll('${receipt} button')].find(element => element.textContent.includes('Open in Data')).click(); true`);
  const toggleReceiptSql = () => evaluate(`document.querySelector('${receipt} button[aria-expanded]').click(); true`);
  const receiptSelected = `document.querySelector('[data-tour=computer-data]')?.getAttribute('aria-selected') === 'true' && ${active}.length === 1 && ${active}[0].getAttribute('data-card-id') === ${JSON.stringify(daily.id)}`;
  await until(async () => await evaluate("!!document.querySelector('textarea[aria-label=\"Message Data review\"]')"), "Seeded conversation did not mount");
  console.log(JSON.stringify({ ok: true, ui: handlePath, ...fixture.info, pid: server?.pid, previewUrl: preview.previewUrl, botId: bot.id, rows: 100_000, cards: 20, serverChecks: "load, describe, SQL, charts, paging, filter, sort, export, restart" }, null, 2));
  if (check) {
    if (await evaluate("!!document.querySelector('[data-tour=computer-data]')")) await click("Close computer panel");
    await until(async () => await evaluate("!document.querySelector('[data-tour=computer-data]')"), "Computer panel did not close");
    await until(async () => await evaluate(`!!document.querySelector('${receipt} button')`), "Older chart's inline receipt did not render");
    await toggleReceiptSql();
    assert.equal(await evaluate(`document.querySelector('${receipt} pre code')?.textContent`), recordedSql, "Expanded receipt should show the bot's recorded SQL");
    await ui("screenshot", "--out", logPath + ".data-recorded-sql.png");
    await toggleReceiptSql();
    assert.equal(await evaluate(`document.querySelector('${receipt} pre') === null`), true, "Recorded SQL should collapse");
    await openReceipt();
    await until(async () => await evaluate(receiptSelected), "First receipt did not mount its Data result");
    await ui("wait-settle", "--timeout", "30");
    assert.equal(await evaluate(receiptSelected), true, "First receipt lost its Data selection after configuration settled");
    await clickTab("computer-browser");
    await click("Close computer panel");
    await until(async () => await evaluate("!document.querySelector('[data-tour=computer-data]')"), "Computer panel did not close after Browser selection");
    await click("Bot's computer");
    await ui("wait-settle", "--timeout", "30");
    assert.equal(await evaluate("document.querySelector('[data-tour=computer-browser]')?.getAttribute('aria-selected') === 'true' && !document.querySelector('[data-testid=data-panel]')"), true, "Consumed receipt replaced Browser when reopening the panel");
    await openReceipt();
    await until(async () => await evaluate(receiptSelected), "Clicking the receipt again did not mount its Data result");
    await ui("wait-settle", "--timeout", "30");
    assert.equal(await evaluate(receiptSelected), true, "Clicking the receipt again did not reopen its Data result");
    await chooseHistory("Latest result");
  } else {
    if (!await evaluate("!!document.querySelector('[data-tour=computer-data]')")) await click("Bot's computer");
    await clickTab("computer-data");
  }

  if (!check) {
    await stopped;
  } else {
    await until(async () => await evaluate(`${active}.length === 1 && ${gridCount} === '100000' && ${gridRows}.length > 0`), "Latest 100k-row result did not become the only active result");
    await until(async () => await evaluate("document.querySelector('.data-grid .ag-row[row-index=\"0\"] .ag-cell[col-id=order_id]')?.textContent === '1'"), "First page did not load");
    assert.equal(await evaluate("document.querySelector('[role=textbox][aria-label=SQL]')?.checkVisibility()"), true, "SQL should always be visible");
    assert.equal(await evaluate("document.querySelectorAll('[role=textbox][aria-label=SQL]').length"), 1);
    assert.equal(await evaluate("document.querySelector('[data-testid=data-details-toggle]') === null"), true);
    assert.equal(await evaluate(`${active}[0].querySelector('header, h1, h2, h3') === null`), true, "Result should not display a title header");
    const normalLayout = await layout();
    assertDockedLayout(normalLayout);
    const tableFooter = await assertFooter(/100,000 rows\s*·\s*8 columns/);
    const headerNames = await evaluate("[...document.querySelectorAll('.data-grid .ag-header-cell-text')].map(cell => cell.textContent.trim())") as string[];
    assert(headerNames.length > 0 && headerNames.every((name) => sheet.cards.find((card) => card.id === latest.id)?.columns?.some((column) => column.name === name)), "Column types should not clutter the visible column names");
    const initialRows = await evaluate(`${gridRows}.length`);
    assert(initialRows > 0 && initialRows < 100, `Expected bounded rows, received ${initialRows}`);
    await ui("screenshot", "--out", logPath + ".data-grid.png");
    await click("Enter fullscreen");
    await until(async () => await evaluate("document.fullscreenElement === document.querySelector('[data-testid=data-panel]')"), "Data workspace did not enter native fullscreen");
    const fullscreenLayout = await layout();
    assert(fullscreenLayout.panel.width >= fullscreenLayout.viewport.width - 4 && fullscreenLayout.panel.height >= fullscreenLayout.viewport.height - 4, "Fullscreen workspace should fill the viewport");
    assert(fullscreenLayout.panel.width > normalLayout.panel.width + 100, "Fullscreen should expand the result workspace");
    assertDockedLayout(fullscreenLayout);
    assert.equal(await evaluate(gridCount), "100000");
    await ui("screenshot", "--out", logPath + ".data-fullscreen.png");
    await click("Exit fullscreen");
    await until(async () => await evaluate("document.fullscreenElement === null"), "Data workspace did not exit native fullscreen");
    await until(async () => Math.abs((await layout()).panel.width - normalLayout.panel.width) < 2, "Exiting fullscreen did not restore the panel width");
    assert.equal(await evaluate("document.querySelector('[role=textbox][aria-label=SQL]').textContent"), latest.sql ?? sheet.cards.find((card) => card.id === latest.id)?.sql);
    await evaluate("(() => { const grid = document.querySelector('.data-grid .ag-grid-viewport'); grid.scrollTop = grid.scrollHeight; return true; })()");
    await until(async () => await evaluate("document.querySelector('.data-grid .ag-row[row-index=\"99999\"] .ag-cell[col-id=order_id]')?.textContent === '100000'"), "Scrolling did not fetch the final row");
    const finalRows = await evaluate(`${gridRows}.length`);
    assert(finalRows > 0 && finalRows < 100, `Expected bounded rows at the end, received ${finalRows}`);
    await chooseHistory("Daily revenue");
    await until(async () => await evaluate(`!!${visibleChart} && ${active}.length === 1`), "History chart did not render alone");
    await click("Table");
    await until(async () => await evaluate("!!document.querySelector('[data-testid=data-card-chart] .data-grid .ag-root')"), "Chart table toggle did not mount the grid");
    await click("Chart");
    await until(async () => await evaluate("document.querySelectorAll('[data-testid=data-card-chart] .data-grid').length === 0"), "Chart toggle kept its table mounted");
    await until(async () => await evaluate(`!!${visibleChart}`), "Chart did not remount");
    const chartLayout = await measureChart();
    assert(chartLayout.width > 100 && chartLayout.height > 100 && chartLayout.width <= chartLayout.availableWidth + 2 && chartLayout.height <= chartLayout.availableHeight + 2, "Chart should fit its result area");
    assert(chartLayout.top >= chartLayout.visibleTop && chartLayout.bottom <= chartLayout.visibleBottom + 1, "Chart axes should be visible without scrolling behind the SQL dock");
    assert(chartLayout.height / chartLayout.width >= 0.2 && chartLayout.height / chartLayout.width <= 1.2, "Chart has an unusable aspect ratio");
    const chartFooter = await assertFooter(/28 rows\s*·\s*2 columns/, true);
    assert.equal(await evaluate(`${active}[0].querySelector('header, h1, h2, h3') === null`), true, "Chart should not display a title header");
    await ui("screenshot", "--out", logPath + ".data-chart.png");
    await click("Enter fullscreen");
    await until(async () => await evaluate("document.fullscreenElement === document.querySelector('[data-testid=data-panel]')") && (await measureChart()).width > chartLayout.width + 100, "Chart did not resize for native fullscreen");
    const chartFullscreen = await measureChart();
    assert(chartFullscreen.top >= chartFullscreen.visibleTop && chartFullscreen.bottom <= chartFullscreen.visibleBottom + 1 && chartFullscreen.height >= chartLayout.height, "Fullscreen chart should fit the larger result area");
    await ui("screenshot", "--out", logPath + ".data-chart-fullscreen.png");
    await click("Exit fullscreen");
    await until(async () => await evaluate("document.fullscreenElement === null") && Math.abs((await measureChart()).width - chartLayout.width) < 2, "Chart did not resize back after fullscreen");
    assert.equal(await evaluate("document.querySelector('[role=textbox][aria-label=SQL]')?.checkVisibility()"), true, "Chart SQL should remain visible");
    assert.equal(await evaluate("document.querySelector('[role=textbox][aria-label=SQL]').textContent"), sheet.cards.find((card) => card.id === daily.id)?.sql);
    assert.equal(await evaluate("document.querySelectorAll('[data-testid=sources-strip] button, [data-testid=sources-strip] svg').length"), 0, "Table references should be plain text");
    assert.deepEqual(await evaluate("[...document.querySelectorAll('[data-testid=sources-strip] li')].map(row => row.textContent)"), ["events3 rows", "orders100,000 rows", "regional_totals4 rows"]);
    await chooseHistory("Latest result");
    await until(async () => await evaluate(`${gridCount} === '100000'`), "Latest result did not restore the 100k table");
    // A new result arrives over the real event stream and replaces the viewer.
    await api("POST", DATA_ROUTES.run(bot.id), { sql: "SELECT count(*) AS order_count FROM orders", title: "Live order count" });
    const beforeEdits = (await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet;
    const countCard = beforeEdits.cards.find((card) => card.title === "Live order count")!;
    await until(async () => await evaluate(`${active}.length === 1 && ${active}[0].getAttribute('data-card-id') === ${JSON.stringify(countCard.id)} && ${active}[0].querySelector('.ag-cell')?.textContent === '100000'`), "New result did not replace the active viewer");
    assert.deepEqual(((await ui("console")).messages ?? []).filter((message: { type: string }) => message.type === "error"), []);
    await until(async () => await evaluate("document.querySelector('[role=textbox][aria-label=SQL]')?.textContent === 'SELECT count(*) AS order_count FROM orders'"), "SQL box did not start with the selected result's query");
    const typeSql = async (sql: string) => {
      await click("SQL");
      await ui("press", "--keys", process.platform === "darwin" ? "Meta+a" : "Control+a");
      await ui("press", "--keys", "Backspace");
      await ui("type", "--name", "SQL", "--text", sql);
    };
    const delayedSql = "SELECT stale_column FROM orders";
    // Hold one real error response while a newer query completes. DuckDB and
    // the HTTP routes remain real; only delivery order to the renderer changes.
    await evaluate(`(() => {
      const originalFetch = window.fetch.bind(window);
      window.__sqlErrors = 0;
      window.__dataOriginalFetch = async (input, init) => {
        const response = await originalFetch(input, init);
        if (String(input).endsWith('/data/run') && [400, 404, 409, 422].includes(response.status)) window.__sqlErrors++;
        return response;
      };
      window.__dataCheck = { held: false, release: null, holdChartPage: false, chartPageHeld: false, chartWait: null, releaseChart: null };
      window.fetch = async (input, init) => {
        const response = await window.__dataOriginalFetch(input, init);
        if (String(input).endsWith('/data/run')) {
          if (JSON.parse(init.body).sql === ${JSON.stringify(delayedSql)}) {
            window.__dataCheck.held = true;
            await new Promise(resolve => { window.__dataCheck.release = resolve; });
          }
        }
        if (String(input).endsWith('/data/page') && JSON.parse(init.body).cardId === ${JSON.stringify(daily.id)} && window.__dataCheck.holdChartPage) {
          window.__dataCheck.chartPageHeld = true;
          await window.__dataCheck.chartWait;
        }
        return response;
      };
      return true;
    })()`);
    let expectedSqlErrors = 0;
    let chartEditLayout: { before: number; pending: number; after: number } | undefined;
    type SqlBounds = { top: number; bottom: number; height: number };
    const sqlBounds = () => evaluate("(() => { const { top, bottom, height } = document.querySelector('[data-testid=sql-editor]').getBoundingClientRect(); return { top, bottom, height }; })()") as Promise<SqlBounds>;
    let validationEditLayout: { before: SqlBounds; invalid: SqlBounds; after: SqlBounds } | undefined;
    const resizing: Record<string, unknown> = {};
    try {
      const sqlBeforeInvalid = await sqlBounds();
      await typeSql("SELECT * FROM");
      await until(async () => await evaluate("!!document.querySelector('[data-testid=data-panel] [role=alert]')"), "Invalid SQL did not report its error");
      assert.match(await evaluate("document.querySelector('[data-testid=data-panel] [role=alert]').textContent"), /syntax|parser/i, "Invalid SQL should show DuckDB's error message");
      assert.equal(await evaluate(`${active}[0].querySelector('.ag-cell')?.textContent`), "100000", "Invalid SQL replaced the last good result");
      assert.equal(await evaluate(`${active}[0].getAttribute('data-card-id')`), countCard.id);
      const sqlWhileInvalid = await sqlBounds();
      for (const dimension of ["top", "bottom", "height"] as const) assert(Math.abs(sqlWhileInvalid[dimension] - sqlBeforeInvalid[dimension]) < 1, `SQL ${dimension} changed when validation failed`);
      await preserveQuery(async () => {
        const before = await resizeValue();
        await dragSql(-24);
        await until(async () => (await resizeValue()) > before + 20, "Dragging did not grow the invalid SQL draft");
        await dragSql(24);
        await until(async () => Math.abs((await resizeValue()) - before) <= 1, "Dragging did not restore the invalid SQL draft height");
        assert.match(await evaluate("document.querySelector('[data-testid=data-query-error]').textContent"), /syntax|parser/i);
      });
      resizing.invalidDraftPreserved = true;
      const validSql = "SELECT order_id, region FROM orders ORDER BY order_id LIMIT 12";
      await typeSql(validSql);
      await until(async () => await evaluate(`${gridCount} === '12' && !document.querySelector('[data-testid=data-panel] [role=alert]')`), "Typed SQL did not update the result without Run");
      const sqlAfterValid = await sqlBounds();
      for (const dimension of ["top", "bottom", "height"] as const) assert(Math.abs(sqlAfterValid[dimension] - sqlBeforeInvalid[dimension]) < 1, `SQL ${dimension} changed after validation recovered`);
      validationEditLayout = { before: sqlBeforeInvalid, invalid: sqlWhileInvalid, after: sqlAfterValid };
      // One more keystroke updates the same result immediately: 12 → 120 rows.
      await ui("type", "--name", "SQL", "--text", "0");
      await until(async () => await evaluate(`${gridCount} === '120'`), "Single-character edit did not update the row count");
      await typeSql(delayedSql);
      await until(async () => await evaluate("window.__dataCheck.held"), "Older SQL response was not held");
      const newestSql = "SELECT order_id, region FROM orders ORDER BY order_id LIMIT 9";
      await typeSql(newestSql);
      await until(async () => await evaluate(`${gridCount} === '9'`), "Newest edit did not win while an older response was pending");
      await evaluate("window.__dataCheck.release(); true");
      await ui("wait-settle", "--timeout", "30");
      assert.equal(await evaluate(gridCount), "9");
      assert.equal(await evaluate("document.querySelector('[data-testid=data-panel] [role=alert]') === null"), true, "Stale SQL error replaced the newest result");
      assert.equal(await evaluate("document.querySelector('[role=textbox][aria-label=SQL]').textContent"), newestSql);
      const edited = (await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet;
      assert.equal(edited.cards.length, beforeEdits.cards.length, "Live typing created extra history cards");
      assert.deepEqual(edited.cards.filter((card) => card.id === countCard.id).map(({ kind, title, rowCount, sql, status }) => ({ kind, title, rowCount, sql, status })), [{ kind: "table", title: "Live order count", rowCount: 9, sql: newestSql, status: "ready" }]);
      await chooseHistory("Daily revenue");
      await until(async () => await evaluate(`!!${visibleChart}`), "History chart did not load before editing");
      const sqlTopBefore = await evaluate(`(() => {
        window.__previousDataChart = ${visibleChart};
        window.__previousChartBytes = window.__previousDataChart.tagName === 'CANVAS' ? window.__previousDataChart.toDataURL() : window.__previousDataChart.outerHTML;
        // Per-keystroke SQL can commit multiple valid revisions. Hold all of
        // their page responses, not just the first intermediate revision.
        window.__dataCheck.holdChartPage = true;
        window.__dataCheck.chartWait = new Promise(resolve => { window.__dataCheck.releaseChart = () => { window.__dataCheck.holdChartPage = false; resolve(); }; });
        return document.querySelector('[data-testid=sql-editor]').getBoundingClientRect().top;
      })()`);
      const chartSql = "SELECT * FROM orders WHERE ordered_at < DATE '2026-09-03'";
      await typeSql(chartSql);
      await until(async () => await evaluate("window.__dataCheck.chartPageHeld"), "Edited chart page response was not held");
      const pendingChart = await evaluate(`(() => { const chart = ${visibleChart}; return { originalConnected: window.__previousDataChart.isConnected, originalVisible: window.__previousDataChart.checkVisibility({ visibilityProperty: true }), currentVisible: !!chart }; })()`);
      assert(pendingChart.originalConnected && pendingChart.originalVisible, `Chart disappeared while its replacement rows were pending: ${JSON.stringify(pendingChart)}`);
      const sqlTopPending = await evaluate("document.querySelector('[data-testid=sql-editor]').getBoundingClientRect().top");
      assert(Math.abs(sqlTopPending - sqlTopBefore) < 1, "SQL box moved while chart rows were pending");
      await evaluate("window.__dataCheck.releaseChart(); true");
      await until(async () => {
        const updated = ((await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet).cards.find((card) => card.id === daily.id);
        return updated?.sql === chartSql && updated.kind === "chart" && updated.rowCount === 2;
      }, "Live SQL did not preserve the chart and reduce it to two days");
      await until(async () => await evaluate(`!!${visibleChart} && !document.querySelector('[data-testid=data-panel] [role=alert]')`), "Edited chart did not render");
      await until(async () => await evaluate(`(() => { const chart = ${visibleChart}; return !!chart && (chart.tagName === 'CANVAS' ? chart.toDataURL() : chart.outerHTML) !== window.__previousChartBytes; })()`), "Edited chart never displayed its replacement data");
      const sqlTopAfter = await evaluate("document.querySelector('[data-testid=sql-editor]').getBoundingClientRect().top");
      assert(Math.abs(sqlTopAfter - sqlTopBefore) < 1, "SQL box moved after the replacement chart rendered");
      chartEditLayout = { before: sqlTopBefore, pending: sqlTopPending, after: sqlTopAfter };
      assert.equal(await evaluate(`${active}[0].getAttribute('data-card-id')`), daily.id);
      await toggleReceiptSql();
      assert.equal(await evaluate(`document.querySelector('${receipt} pre code')?.textContent`), recordedSql, "Editing the viewer should not rewrite the bot's recorded SQL receipt");
      assert.notEqual(recordedSql, chartSql);
      await toggleReceiptSql();
      await click("Table");
      await until(async () => await evaluate(`${gridCount} === '2'`), "Edited chart's table did not contain two rows");
      await click("Chart");
      expectedSqlErrors = await evaluate("window.__sqlErrors");
      assert(expectedSqlErrors >= 2, "The real server did not reject both invalid queries");
      await ui("screenshot", "--out", logPath + ".data-live-sql.png");
    } finally {
      await evaluate("window.__dataCheck.release?.(); window.__dataCheck.releaseChart?.(); window.fetch = window.__dataOriginalFetch; true");
    }
    const beforeResizeCards = ((await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet).cards;
    const resizeScreenshots: string[] = [];
    const recordResize = async (phase: string, chart = false) => {
      const measured = await layout();
      assertDockedLayout(measured);
      let plot;
      let rows;
      if (chart) {
        await until(async () => {
          const current = await measureChart();
          return current.bottom <= current.visibleBottom + 1 && current.width <= current.availableWidth + 1 && current.height <= current.availableHeight + 1;
        }, "Chart did not fit after resizing SQL");
        plot = await measureChart();
        assert(plot.height > 50 && plot.width > 100, "Chart should retain a usable plot after resizing SQL");
      } else {
        rows = await evaluate(`(() => { const viewport = document.querySelector('.data-grid .ag-grid-viewport').getBoundingClientRect(); const header = document.querySelector('.data-grid .ag-header-cell').getBoundingClientRect(); const rows = [...${gridRows}]; return { mounted: rows.length, visible: rows.filter(row => { const r = row.getBoundingClientRect(); return r.bottom > Math.max(viewport.top, header.bottom) && r.top < viewport.bottom; }).length }; })()`);
        assert(rows.mounted > 0 && rows.mounted < 100 && rows.visible > 0, "Resized table should retain visible rows with a bounded DOM");
      }
      resizing[phase] = { ...measured, plot, rows };
      const screenshot = `${logPath}.data-resize-${phase}.png`;
      resizeScreenshots.push(screenshot);
      await ui("screenshot", "--out", screenshot);
    };
    const clampDock = async (maximum: boolean) => {
      const current = await layout();
      const target = maximum ? current.panel.top + 1 : current.panel.bottom - 1;
      await dragSql(target - current.splitter.top - current.splitter.height / 2);
      await until(async () => {
        const next = await layout();
        return Math.abs(next.limits.value - (maximum ? next.limits.max : next.limits.min)) <= 1;
      }, `Pointer drag did not clamp SQL to its ${maximum ? "maximum" : "minimum"}`);
    };
    await chooseHistory("All 100,000 orders");
    await until(async () => await evaluate(`${gridCount} === '100000' && ${gridRows}.length > 0`), "Large table did not load for splitter checks");
    await preserveQuery(async () => {
      const originalHeight = await resizeValue();
      await dragSql(-48);
      await until(async () => (await resizeValue()) > originalHeight + 40, "Pointer drag up did not enlarge SQL");
      await recordResize("table-grown");
      await dragSql(48);
      await until(async () => Math.abs((await resizeValue()) - originalHeight) <= 1, "Pointer drag down did not shrink SQL");
      await keyResize("ArrowUp");
      assert(Math.abs((await resizeValue()) - originalHeight - 24) <= 1, "ArrowUp should grow SQL by 24px");
      await keyResize("ArrowDown");
      assert(Math.abs((await resizeValue()) - originalHeight) <= 1, "ArrowDown should restore SQL height");
      await clampDock(true);
      const maximum = await resizeValue();
      await keyResize("ArrowUp");
      assert.equal(await resizeValue(), maximum, "Keyboard resizing must respect the maximum");
      await recordResize("table-max");
      await chooseHistory("All 100,000 orders");
      await clampDock(false);
      const minimum = await resizeValue();
      await keyResize("ArrowDown");
      assert.equal(await resizeValue(), minimum, "Keyboard resizing must respect the minimum");
      await recordResize("table-min");
      await drag("document.querySelector('[data-testid=data-panel]').closest('aside').querySelector(':scope > [role=separator]')", 40, 0);
      await browser("set", "viewport", "1280", "460");
      await until(async () => {
        const current = await layout();
        return current.panel.width >= 350 && current.panel.width <= 361 && current.panel.height < 400;
      }, "Narrow/short Data panel did not settle");
      await clampDock(true);
      await recordResize("table-narrow-short");
      await assertFooter(/100,000 rows\s*·\s*8 columns/);
      await chooseHistory("All 100,000 orders");
    });
    await chooseHistory("Daily revenue");
    await until(async () => await evaluate(`!!${visibleChart}`), "Chart did not load for splitter checks");
    await preserveQuery(async () => {
      await clampDock(false);
      await recordResize("chart-narrow-min", true);
      await clampDock(true);
      await recordResize("chart-narrow-max", true);
      await assertFooter(/2 rows\s*·\s*2 columns/, true);
      await chooseHistory("Daily revenue");
      await click("Enter fullscreen");
      await until(async () => await evaluate("document.fullscreenElement === document.querySelector('[data-testid=data-panel]')"), "Resizable SQL workspace did not enter fullscreen");
      await clampDock(true);
      await recordResize("chart-fullscreen-max", true);
      await clampDock(false);
      await recordResize("chart-fullscreen-min", true);
      await click("Exit fullscreen");
      await until(async () => await evaluate("document.fullscreenElement === null"), "Resizable SQL workspace did not exit fullscreen");
      await recordResize("chart-restored", true);
    });
    assert.deepEqual(((await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet).cards, beforeResizeCards, "Resizing should not change stored queries or results");
    resizing.queryRequests = 0;
    await browser("set", "viewport", String(normalLayout.viewport.width), String(normalLayout.viewport.height));
    const wideSql = `SELECT i+1 AS "order.id", 9007199254740993::BIGINT+i AS "big id", i%2=0 AS "is active?", CASE WHEN i%2=0 THEN NULL ELSE 'present' END AS "nullable value", CASE WHEN i=99999 THEN 'Final typed row' ELSE '東京' END AS "label []", ${Array.from({ length: 115 }, (_, i) => `i+${i} AS "metric.${String(i).padStart(2, "0")}"`).join(", ")} FROM range(100000) AS generated(i)`;
    await api("POST", DATA_ROUTES.run(bot.id), { sql: wideSql, title: "Wide typed values" });
    const wideCard = ((await api("GET", DATA_ROUTES.sheet(bot.id))).sheet as DataSheet).cards.find((card) => card.title === "Wide typed values")!;
    const widePage = await api("POST", DATA_ROUTES.page(bot.id), { cardId: wideCard.id, offset: 0, limit: 2 });
    assert.equal(widePage.columns.length, 120);
    assert.deepEqual(widePage.rows[0].slice(1, 5), ["9007199254740993", true, null, "東京"]);
    await until(async () => await evaluate("[...document.querySelectorAll('[data-testid=data-history] [role=menuitemradio]')].some(element => element.textContent === 'Wide typed values')"), "Wide result did not enter History");
    await chooseHistory("Wide typed values");
    const cell = (row: number, column: string) => `document.querySelector(${JSON.stringify(`.data-grid .ag-row[row-index="${row}"] .ag-cell[col-id="${column}"]`)})`;
    await until(async () => await evaluate(`${cell(0, "order.id")}?.textContent === '1'`), "Wide grid did not load its first page");
    assert.equal(await evaluate("document.querySelector('.data-grid button[aria-label=\"Copy rows\"]') === null"), true, "Grid should not add a top copy button");
    await evaluate(`(() => {
      window.__gridFetch = window.fetch;
      window.__gridRequests = [];
      window.__gridFailNext = false;
      window.__gridClipboard = navigator.clipboard.writeText;
      window.__gridCopied = null;
      Object.defineProperty(navigator.clipboard, 'writeText', { configurable: true, value: async text => { window.__gridCopied = text; } });
      window.fetch = (...args) => {
        if (String(args[0]).endsWith('/data/page')) {
          window.__gridRequests.push(JSON.parse(args[1].body));
          if (window.__gridFailNext) { window.__gridFailNext = false; return Promise.resolve(new Response(JSON.stringify({ error: { message: 'Fixture page unavailable' } }), { status: 503, headers: { 'content-type': 'application/json' } })); }
        }
        return window.__gridFetch(...args);
      };
      return true;
    })()`);
    const agGrid: Record<string, unknown> = { rows: 100_000, columns: 120 };
    try {
      const header = '.data-grid .ag-header-cell[col-id="order.id"]';
      for (const [direction, first] of [["asc", "1"], ["desc", "100000"]]) {
        await browser("click", header + " .ag-header-cell-label");
        await until(async () => await evaluate(`${cell(0, "order.id")}?.textContent === ${JSON.stringify(first)} && window.__gridRequests.some(request => request.sort?.column === 'order.id' && request.sort?.direction === ${JSON.stringify(direction)})`), `Server-side ${direction} sort did not replace the page`);
      }
      await browser("click", header + " .ag-header-cell-label");
      await until(async () => await evaluate(`${cell(0, "order.id")}?.textContent === '1'`), "Clearing sort did not restore source order");
      const headerWidth = await evaluate(`document.querySelector(${JSON.stringify(header)}).getBoundingClientRect().width`);
      await drag(`document.querySelector(${JSON.stringify(header + " .ag-header-cell-resize")})`, 70, 0);
      assert((await evaluate(`document.querySelector(${JSON.stringify(header)}).getBoundingClientRect().width`)) > headerWidth + 60, "Native column drag did not resize the column");
      const setFilter = async (value: string, column = "All columns") => {
        await click("Filter");
        if (await evaluate("!!document.querySelector('input[aria-label=\"Filter value\"]')")) await click("Choose column");
        if (column === "All columns") await click(column);
        else await browser("click", `[data-filter-option][aria-label=${JSON.stringify(column)}]`);
        await click("Filter value");
        await evaluate("document.querySelector('input[aria-label=\"Filter value\"]').select(); true");
        await ui("press", "--keys", "Backspace");
        if (value) await ui("type", "--name", "Filter value", "--text", value);
        await ui("press", "--keys", "Escape");
      };
      assert.equal(await evaluate("!!document.querySelector('input[aria-label=\"Filter rows\"]')"), false, "Grid should show a compact Filter button instead of an always-visible search");
      await click("Filter");
      await ui("type", "--name", "Search columns", "--text", "label");
      assert.deepEqual(await evaluate("[...document.querySelectorAll('[data-filter-option]')].map(button => button.textContent.trim())"), ["All columns", "label []"]);
      await ui("screenshot", "--out", logPath + ".data-filter-menu.png");
      await ui("press", "--keys", "Escape");
      assert.equal(await evaluate("document.activeElement?.getAttribute('aria-label')"), "Filter", "Escape should return focus to Filter");
      await setFilter("Final typed row", "order.id");
      await until(async () => await evaluate(`${gridCount} === '0'`), "Column filter unexpectedly searched other columns");
      await setFilter("100000", "order.id");
      await until(async () => await evaluate(`${gridCount} === '1' && ${cell(0, "order.id")}?.textContent === '100000'`), "Column filter did not reach the final numeric row");
      assert.equal(await evaluate("window.__gridRequests.some(request => request.filter === '100000' && request.filterColumn === 'order.id')"), true);
      await setFilter("Final typed row");
      await until(async () => await evaluate(`${gridCount} === '1' && ${cell(0, "order.id")}?.textContent === '100000'`), "Global search did not find the hidden-column value on the last server row");
      assert.equal(await evaluate("window.__gridRequests.some(request => request.filter === 'Final typed row')"), true);
      await setFilter("__no_synthetic_match__");
      await until(async () => await evaluate(`${gridCount} === '0' && ${gridRows}.length === 0`), "Empty filter result did not clear the grid");
      await setFilter("");
      await until(async () => await evaluate(`${gridCount} === '100000' && ${cell(0, "order.id")}?.textContent === '1'`), "Clearing the filter did not restore rows");
      await evaluate("window.__gridFailNext = true; true");
      await setFilter("Final typed row");
      await until(async () => await evaluate("document.querySelector('.data-grid [role=alert]')?.textContent.includes('Fixture page unavailable') === true"), "Page failure did not show a recoverable error");
      await setFilter("");
      await until(async () => await evaluate(`${gridCount} === '100000' && ${cell(0, "order.id")}?.textContent === '1' && !document.querySelector('.data-grid [role=alert]')`), "Changing filter did not recover from the failed page");
      await click("Enter fullscreen");
      await until(async () => await evaluate(`document.fullscreenElement !== null && !!${cell(0, "label []")}`), "Fullscreen did not reveal typed columns");
      assert.equal(await evaluate(`${cell(0, "big id")}.textContent`), "9007199254740993", "BIGINT display lost precision");
      assert.equal(await evaluate(`${cell(0, "is active?")}.textContent`), "true");
      assert.equal(await evaluate(`${cell(1, "is active?")}.textContent`), "false");
      assert.equal(await evaluate(`${cell(0, "nullable value")}.textContent`), "null");
      assert.equal(await evaluate(`${cell(0, "label []")}.textContent`), "東京");
      await browser("click", '.data-grid .ag-row[row-index="0"] .ag-cell[col-id="order.id"]');
      await ui("press", "--keys", "Control+c");
      await until(async () => await evaluate("typeof window.__gridCopied === 'string'"), "Keyboard copy did not write selected rows");
      const copied = await evaluate("window.__gridCopied") as string;
      assert.equal(copied.split("\n").length, 2, "Keyboard copy should copy only the selected row and header");
      assert.deepEqual(copied.split("\n")[1].split("\t").slice(0, 5), ["1", "9007199254740993", "true", "", "東京"]);
      await click("Export");
      await click("Copy page as Markdown");
      await until(async () => await evaluate("window.__gridCopied.startsWith('| order.id | big id |')"), "Export did not copy visible rows as Markdown");
      const markdown = await evaluate("window.__gridCopied") as string;
      assert(markdown.includes("9007199254740993") && markdown.split("\n").length > 3 && markdown.split("\n").length < 103, "Markdown copy should preserve values and contain only a bounded visible page");
      await click("Exit fullscreen");
      await until(async () => await evaluate("document.fullscreenElement === null"), "Typed-value check did not exit fullscreen");
      const columnsBefore = await evaluate("document.querySelectorAll('.data-grid .ag-header-cell').length");
      assert(columnsBefore > 0 && columnsBefore < 20, "Wide grid should virtualize columns");
      await evaluate("(() => { const viewport = document.querySelector('.data-grid .ag-body-horizontal-scroll-viewport'); viewport.scrollLeft = viewport.scrollWidth; return true; })()");
      await until(async () => await evaluate(`${cell(0, "metric.114")}?.textContent === '114'`), "Horizontal scroll did not render the final column");
      const columnsAfter = await evaluate("document.querySelectorAll('.data-grid .ag-header-cell').length");
      assert(columnsAfter > 0 && columnsAfter < 20, "Column DOM should remain bounded at the right edge");
      await evaluate("(() => { const viewport = document.querySelector('.data-grid .ag-grid-viewport'); viewport.scrollTop = viewport.scrollHeight; return true; })()");
      await until(async () => await evaluate(`${cell(99999, "metric.114")}?.textContent === '100113'`), "Wide grid did not fetch the final row and column");
      const wideRows = await evaluate(`${gridRows}.length`);
      assert(wideRows > 0 && wideRows < 100, "Wide grid should retain bounded row DOM");
      Object.assign(agGrid, { sort: true, search: true, resize: true, emptyAndErrorRecovery: true, rawValues: true, keyboardCopy: true, markdownCopy: true, columnsBefore, columnsAfter, mountedRows: wideRows });
      await ui("screenshot", "--out", logPath + ".data-ag-grid.png");
    } finally {
      await evaluate("window.fetch = window.__gridFetch; Object.defineProperty(navigator.clipboard, 'writeText', { configurable: true, value: window.__gridClipboard }); true");
    }
    // Exercise the real composer -> provider -> MCP -> DuckDB -> viewer path.
    // The loopback provider is scripted; this proves wiring, not model reasoning.
    const manualSql = "SELECT order_id, region, revenue FROM orders ORDER BY order_id LIMIT 8";
    const brokenDraft = "SELECT region, revenue FROM orders WHERE";
    const chatRuns: Array<{ selectedId: string; draft?: string; sql: string }> = [];
    let chatFailure: unknown;
    let chatRequests = 0;
    const upstream = createServer(async (req, res) => {
      if (req.url === "/v1/models") {
        res.setHeader("content-type", "application/json");
        res.end(JSON.stringify({ data: [{ id: "data-chat-fixture" }] }));
        return;
      }
      try {
        let raw = "";
        for await (const chunk of req) raw += chunk;
        const request = JSON.parse(raw) as {
          messages: Array<{ role: string; content?: unknown }>;
          tools?: Array<{ function: { name: string } }>;
        };
        chatRequests++;
        const userIndex = request.messages.findLastIndex((message) => message.role === "user");
        const content = request.messages[userIndex]?.content;
        const prompt = typeof content === "string" ? content : JSON.stringify(content);
        const envelope = /<data-context>(.*?)<\/data-context>/.exec(prompt);
        assert(envelope, "Composer did not deliver the selected Data result to the provider");
        const context = JSON.parse(envelope[1]) as { cardId: string; draftSql?: string };
        assert.equal(context.cardId, latest.id, "Chat must target the viewed History result, not the newest result");
        const repair = prompt.includes("Fix the unfinished SQL");
        assert.equal(context.draftSql, repair ? brokenDraft : undefined);
        const nextSql = repair
          ? "SELECT region, revenue FROM orders WHERE region = 'Europe' LIMIT 3"
          : "SELECT order_id, region, revenue FROM orders WHERE region = 'India' ORDER BY order_id LIMIT 5";
        const results = request.messages.slice(userIndex + 1).filter((message) => message.role === "tool").map((message, index) => {
          const tool = index === 0 ? "data_describe" : "data_show";
          assert(typeof message.content === "string", `${tool} returned non-text content`);
          const reply = JSON.parse(message.content) as { ok?: boolean; result?: unknown };
          assert.equal(reply?.ok, true, `${tool} failed: ${message.content.slice(0, 1000)}`);
          assert(typeof reply.result === "string", `${tool} returned no text result`);
          return reply.result;
        });
        const frame = (delta: unknown, finish_reason: string) => `data: ${JSON.stringify({ choices: [{ index: 0, delta, finish_reason }] })}\n\n`;
        let response: string;
        if (results.length < 2) {
          const tool = results.length === 0 ? "data_describe" : "data_show";
          const name = request.tools?.find((entry) => entry.function.name.endsWith(tool))?.function.name;
          assert(name, `Provider did not receive ${tool}`);
          if (results.length === 1) {
            assert(results[0].includes(repair ? "region = 'India'" : manualSql), "Data tool did not return the latest saved SQL");
          }
          const args = results.length === 0 ? { id: context.cardId } : { id: context.cardId, sql: nextSql };
          response = frame({ tool_calls: [{ index: 0, id: `data-chat-${chatRuns.length}-${results.length}`, type: "function", function: { name, arguments: JSON.stringify(args) } }] }, "tool_calls");
        } else {
          assert(results[1].includes(context.cardId), "Data update did not return the current result");
          chatRuns.push({ selectedId: context.cardId, draft: context.draftSql, sql: nextSql });
          response = frame({ content: "Updated the current Data result and its SQL query." }, "stop");
        }
        res.writeHead(200, { "content-type": "text/event-stream" });
        res.end(response + "data: [DONE]\n\n");
      } catch (error) {
        chatFailure = error;
        res.writeHead(500, { "content-type": "application/json" });
        res.end(JSON.stringify({ error: { message: String(error) } }));
      }
    });
    await new Promise<void>((resolve) => upstream.listen(0, "127.0.0.1", resolve));
    try {
      const address = upstream.address();
      assert(address && typeof address !== "string");
      await api("PATCH", "/api/config", { openaiCompat: { key: "synthetic-data-fixture", url: `http://127.0.0.1:${address.port}/v1`, model: "data-chat-fixture" } });
      await runControlOmb(["set-model", "--bot", bot.id, "--instance", "openaiCompat", "--model", "data-chat-fixture", "--url", url]);
      await api("PATCH", `/api/bots/${bot.id}`, { toolScope: { allow: ["mcp:data:data_describe", "mcp:data:data_show"] } });
      await browser("set", "viewport", "1600", "1000");
      await clickTab("computer-data");
      await until(async () => await evaluate("!!document.querySelector('[data-testid=data-panel] [data-testid=data-history]')"), "Data view did not reopen after configuring the fixture provider");
      await chooseHistory("All 100,000 orders");
      await typeSql(manualSql);
      await until(async () => await evaluate(`${gridCount} === '8'`), "Manual query did not settle before chat");
      const cardsBefore = (await api("GET", DATA_ROUTES.sheet(bot.id))).sheet.cards.length;
      const sendChat = async (text: string, rows: number) => {
        const before = chatRequests;
        await ui("type", "--name", "Message Data review", "--text", text);
        await ui("press", "--keys", "Enter");
        await until(async () => chatRequests > before, "Composer did not send the chat request");
        let settled: any;
        for (let attempt = 0; attempt < 4; attempt++) {
          settled = await runControlOmb(["wait", "--bot", bot.id, "--task", bot.activeTaskId, "--timeout", "30", "--url", url]);
          if (chatFailure) throw chatFailure;
          if (settled.status !== "needs-user") break;
          const current = (await api("GET", "/api/bots")).bots.find((entry: { id: string }) => entry.id === bot.id);
          const pending = current.messages.find((message: any) => message.card?.requestId && !message.card.answered)?.card;
          assert(pending, "Fixture approval missing");
          await api("POST", `/api/bots/${bot.id}/respond`, { threadId: bot.activeTaskId, requestId: pending.requestId, behavior: "allow" });
        }
        assert.equal(settled.status, "settled", JSON.stringify(settled));
        await until(async () => await evaluate(`${gridCount} === '${rows}' && document.querySelector('[role=textbox][aria-label=SQL]')?.textContent === ${JSON.stringify(chatRuns.at(-1)?.sql)} && !document.querySelector('[data-testid=data-panel] [role=alert]')`), "Chat did not update the result and visible SQL or clear the old error");
        assert.equal((await api("GET", DATA_ROUTES.sheet(bot.id))).sheet.cards.length, cardsBefore, "Follow-up should update in place, not add another result");
        assert.equal(await evaluate("document.body.innerText.includes('<data-context>')"), false, "Internal context leaked into the transcript");
      };
      await sendChat("Only show India in this table, keep the same result, and show five rows.", 5);
      assert((await evaluate("new Set([...document.querySelectorAll('[data-testid=sql-editor] .cm-line span')].map(span => getComputedStyle(span).color)).size")) >= 3, "SQL keywords, strings and numbers should be coloured");
      await typeSql(brokenDraft);
      await until(async () => await evaluate("!!document.querySelector('[data-testid=data-panel] [role=alert]')"), "Invalid draft did not produce an error");
      await sendChat("Fix the unfinished SQL I am editing, and show three rows from Europe.", 3);
      assert.equal(chatRuns.length, 2);
      await ui("screenshot", "--out", logPath + ".data-chat.png");
      writeFileSync(logPath + ".data-chat.json", JSON.stringify({ ok: true, provider: "scripted loopback OpenAI-compatible", chatRuns, sameResult: true, visibleSqlUpdated: true }, null, 2) + "\n");
    } finally {
      upstream.closeAllConnections();
      await new Promise<void>((resolve) => upstream.close(() => resolve()));
    }
    const consoleOutput = await ui("console");
    expectedSqlErrors = await evaluate("window.__sqlErrors");
    const consoleErrors = (consoleOutput.messages ?? []).filter((message: { type: string }) => message.type === "error") as Array<{ text: string }>;
    assert(consoleErrors.length <= expectedSqlErrors, "Unexpected browser errors beyond the deliberate SQL failures");
    assert.deepEqual(consoleErrors.filter((message) => !/Failed to load resource: the server responded with a status of (400|404|409|422)/.test(message.text)), []);
    const evidence = logPath + ".data.json";
    const screenshot = logPath + ".data.png";
    await ui("screenshot", "--out", screenshot);
    writeFileSync(evidence, JSON.stringify({ ok: true, rows: 100_000, historyCards: 20, initialDomRows: initialRows, finalDomRows: finalRows, activeResults: 1, restart: true, inlineReceipt: true, recordedSqlReceipt: true, closedPanelReceipt: true, consumedReceipt: true, history: true, chartTableToggle: true, layout: { normal: normalLayout, fullscreen: fullscreenLayout, chart: chartLayout, chartFullscreen, tableFooter, chartFooter, restored: true, chartEditSqlTop: chartEditLayout, validationEditSqlBounds: validationEditLayout }, resizing, agGrid, liveResult: true, liveSql: { invalidPreservesResult: true, keystrokeUpdates: true, newestWins: true, chartPreserved: true, chartRemainsVisible: true, expectedHttp400: expectedSqlErrors }, console: consoleOutput, screenshots: [logPath + ".data-recorded-sql.png", logPath + ".data-grid.png", logPath + ".data-fullscreen.png", logPath + ".data-chart.png", logPath + ".data-chart-fullscreen.png", logPath + ".data-live-sql.png", ...resizeScreenshots, logPath + ".data-ag-grid.png", screenshot], logPath }, null, 2) + "\n");
    console.log(JSON.stringify({ ok: true, evidence, screenshot }));
  }
} catch (error) {
  if (handle) {
    const diagnostics = await Promise.all([
      agentBrowser(handle.binary, sessionEnv(handle), ["eval", "({ clipboardPrefix: window.__gridCopied?.slice(0,300), alerts: [...document.querySelectorAll('[role=alert]')].map(element=>element.textContent), selectedRows: document.querySelectorAll('.ag-row[aria-selected=true]').length, renderedRows: document.querySelectorAll('.ag-row[row-index]').length })"]),
      agentBrowser(handle.binary, sessionEnv(handle), ["console"]),
    ]).catch(() => null);
    if (diagnostics) writeFileSync(logPath + ".data-failed.json", JSON.stringify(diagnostics, null, 2) + "\n");
    await agentBrowser(handle.binary, sessionEnv(handle), ["screenshot", logPath + ".data-failed.png"])
      .then(() => console.error(`Data fixture failure evidence: ${logPath}.data-failed.png`), () => {});
  }
  throw error;
} finally {
  if (handle) await closeBrowserSession(handle.binary, sessionEnv(handle));
  await preview?.close();
  await waitForExit(server, { signal: "SIGTERM" });
  await fixture.close();
}