evOo-local/apps/web/e2e/new-bot-ux.spec.ts
cynarAI 2c6b5c3d7b
chore: remove legacy orchestrator, trading and one-off leftovers (#109)
* chore: remove legacy orchestrator, trading and one-off script leftovers

The repository still carried the pre-evOo prototype: a Python orchestrator,
the old HTML "evoo-platform" cockpit (whose test ran in the root unit
suite), an apex-trading package, the hot-reload deploy.sh for it, a
permissions manifest, an architecture note with local file links, agent
handoff notes, a Steve Jobs persona template and about ninety one-off
scripts that probe a developer database with hard-coded local URLs. None
of it is used by the product, CI or the real deploy in ops/evo-deploy.

Delete those files, drop the evoo-platform test glob from the root vitest
config, the Cockpit section from .env.example and the dead biome and
gitignore entries, and update the docs that listed the removed tools.
scripts/start-tmux.sh no longer starts the removed sentinel and resolves
the checkout from its own location instead of a machine-specific path;
scripts/seed-ollama.ts requires DATABASE_URL instead of falling back to a
local URL with a username.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* fix(web): drop trading and persona prompts from the conversation shell

The web shell still showed leftovers from the trading prototype: composer
chips with German trading and "Steve Jobs" prompts, trading suggestion
chips under the empty conversation line, a German trading hero for the
empty transcript, and a German "data protection" status panel in the
sidebar. They suggested actions the product does not have and added
persistent chrome.

Remove the composer chips and their helper, the empty-state chips and the
hero (the empty conversation keeps its existing one-line start hint), the
sidebar trading indicator and the data trust panel. The new-bot E2E now
asserts the plain empty state; the sidebar spec no longer expects the
trading status, and the placeholder Steve Jobs spec with no assertions is
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* refactor: remove the trading bot status integration

With the sidebar trading indicator gone, the only remaining consumer of
the trading bot status was a banner in General settings that reused the
same component. evOo is a personal assistant; a status proxy and poller
for one external trading service, configured through service-specific
TRADING_BOT_URL and VITE_TRADING_BOT_URL variables, does not belong in
the core product.

Remove the settings banner and its prop plumbing, the web poller, the
indicator component, the /api/trading-bot/status proxy, the shared core
contract, the env loading and both variables from .env.example, together
with their tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore: remove one-off database scripts and a stale review note

packages/db carried four hand-run scripts outside the package build that
query or seed a developer database with a hard-coded connection string,
one of them for a specific account. docs/VOICE-FIRST-REVIEW.md was a
one-time verification note for a workflow that no longer exists and
named private machines and local paths. Nothing references them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* chore: replace machine-specific paths and addresses with placeholders

ORIGIN.md pinned the intended checkout to an absolute path under a
personal home directory, docs/LOCAL-SETUP.md listed folders on one
developer's machine, and two tests used what looks like a real tailnet
address. Use a home-relative path, drop the machine-specific folder list,
and use 100.100.100.100 in the tests; it is still inside 100.64.0.0/10,
so both tests check the same thing as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

* test(web): drop the trading status from the shell contrast test

main's contrast test now waits for the sidebar trading status before
measuring; this branch removes that status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-10-04 01:13:52 +02:00

191 lines
9.1 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
captureScreenshot,
completeOnboarding,
createBotFromPicker,
openNewBot,
rpc,
signup,
} from "./helpers";
test("create opens form, then empty chat; picker lists bots; sidebar collapses", async ({
page,
}, testInfo) => {
const stamp = Date.now();
await signup(page, `new-bot-ux-${stamp}@rakazo.test`, "password12", "New Bot UX");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
await page.getByTestId("create-menu-trigger").click();
const picker = page.getByTestId("bot-create-picker");
await expect(picker).toBeVisible();
await expect(picker.getByPlaceholder("Search")).toBeVisible();
await expect(picker.getByTestId("create-new-bot")).toBeVisible();
await expect(picker.getByText("Chief", { exact: true })).toBeVisible();
await captureScreenshot(page, testInfo, "plus-picker-bots");
await picker.getByTestId("create-new-bot").click();
const form = page.getByTestId("create-bot-form");
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create");
await expect(form).toBeVisible();
await expect(form.locator("label:has-text('Name') input")).toBeVisible();
await expect(form.locator("label:has-text('Title') input")).toBeVisible();
await expect(form.locator("label:has-text('Description') textarea")).toBeVisible();
await expect(form.getByTestId("create-bot-computer")).toBeVisible();
await expect(form.getByTestId("create-bot-team")).toBeVisible();
await expect(form.getByTestId("create-bot-private")).toBeVisible();
await captureScreenshot(page, testInfo, "create-bot-form");
await form.locator("label:has-text('Name') input").fill("New Bot");
await form.getByRole("button", { name: "Create", exact: true }).click();
await page.waitForURL(/\/app\/[^/]+$/);
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "closed");
await expect(page.getByText("What do you want me on first?", { exact: true })).toHaveCount(0);
await expect(
page.getByTestId("transcript").getByText("Start the conversation — New Bot is on stage above."),
).toBeVisible();
await captureScreenshot(page, testInfo, "create-chat-sidepanel-closed");
await page.getByTestId("toggle-bots-sidebar").click();
await expect(page.getByTestId("bots-sidebar")).toHaveAttribute("data-collapsed", "true");
const edge = page.getByTestId("bots-sidebar-edge");
await expect(edge).toBeVisible();
const restore = page.getByTestId("toggle-bots-sidebar");
await expect(restore).toBeVisible();
await captureScreenshot(page, testInfo, "bots-sidebar-collapsed");
await restore.click();
await expect(page.getByTestId("bots-sidebar")).toHaveAttribute("data-collapsed", "false");
await expect(page.getByRole("button", { name: "Minimize bots", exact: true })).toBeVisible();
await page.getByTestId("toggle-bots-sidebar").click();
await expect(page.getByTestId("bots-sidebar")).toHaveAttribute("data-collapsed", "true");
const box = await edge.boundingBox();
expect(box).toBeTruthy();
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2);
await page.mouse.down();
await page.mouse.move(box!.x + 80, box!.y + box!.height / 2, { steps: 8 });
await page.mouse.up();
await expect(page.getByTestId("bots-sidebar")).toHaveAttribute("data-collapsed", "false");
await captureScreenshot(page, testInfo, "bots-sidebar-expanded");
});
test("picker rows explain groups and spaces", async ({ page }, testInfo) => {
const stamp = Date.now();
await signup(page, `picker-info-${stamp}@rakazo.test`, "password12", "Picker Info");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
await page.getByTestId("create-menu-trigger").click();
const picker = page.getByTestId("bot-create-picker");
await expect(picker).toBeVisible();
await picker.getByTestId("create-new-group").hover();
const groupInfo = picker.getByTestId("picker-info-group");
await expect.poll(() => groupInfo.evaluate((el) => getComputedStyle(el).opacity)).toBe("1");
await captureScreenshot(page, testInfo, "picker-group-info-hover");
await groupInfo.click();
const dialog = page.getByTestId("picker-info-dialog");
await expect(dialog).toBeVisible();
await expect(dialog.getByText("Groups", { exact: true })).toBeVisible();
await expect(dialog).toContainText("same thread");
await expect(page.getByTestId("side-panel")).not.toHaveAttribute("data-panel", "create-group");
await captureScreenshot(page, testInfo, "picker-group-info-dialog");
await dialog.getByRole("button", { name: "Close" }).click();
await expect(dialog).toBeHidden();
await page.getByTestId("create-menu-trigger").click();
const spaceInfo = picker.getByTestId("picker-info-space");
await expect.poll(() => spaceInfo.evaluate((el) => getComputedStyle(el).opacity)).toBe("0");
await picker.getByTestId("create-new-space").hover();
await expect.poll(() => spaceInfo.evaluate((el) => getComputedStyle(el).opacity)).toBe("1");
await spaceInfo.click();
await expect(dialog).toBeVisible();
await expect(dialog.getByText("Spaces", { exact: true })).toBeVisible();
await expect(dialog).toContainText("own bots and groups");
await captureScreenshot(page, testInfo, "picker-space-info-dialog");
await page.keyboard.press("Escape");
await expect(dialog).toBeHidden();
});
test("later bot waits before showing the focus card; sending cancels it", async ({ page }) => {
const stamp = Date.now();
await signup(page, `focus-delay-${stamp}@rakazo.test`, "password12", "Focus Delay");
await completeOnboarding(page, undefined, { mainConversation: true });
// The main bot shows the focus card immediately in the main conversation.
await expect(page.getByText("What do you want me on first?", { exact: true })).toBeVisible();
await page.clock.install();
await createBotFromPicker(page);
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
await expect(page.getByText("What do you want me on first?", { exact: true })).toHaveCount(0);
await page.clock.fastForward(9_000);
await expect(page.getByText("What do you want me on first?", { exact: true })).toHaveCount(0);
await page.clock.fastForward(1_500);
await expect(page.getByText("What do you want me on first?", { exact: true })).toBeVisible();
await createBotFromPicker(page, { name: "Later Bot" });
await expect(page.getByText("What do you want me on first?", { exact: true })).toHaveCount(0);
const composer = page.getByPlaceholder(/Message/);
await composer.fill("I'll set this up myself");
// Send must finish before the delay is advanced: cancel runs after a successful
// RPC, and promptFocus will still post if the clock fires while send is in flight.
const sent = page.waitForResponse(
(response) => response.url().includes("/rpc/threads/send") && response.ok(),
);
await page.keyboard.press("Enter");
await sent;
await expect(
page.getByTestId("transcript").getByText("I'll set this up myself").first(),
).toBeVisible();
await page.clock.fastForward(12_000);
await expect(page.getByText("What do you want me on first?", { exact: true })).toHaveCount(0);
});
test("plus picker can create a Private computer bot", async ({ page }, testInfo) => {
const stamp = Date.now();
await signup(page, `new-bot-private-${stamp}@rakazo.test`, "password12", "New Bot Private");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
await createBotFromPicker(page, { computerMode: "dedicated" });
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
await captureScreenshot(page, testInfo, "create-private-computer-bot");
const botId = page.url().split("/").pop()!;
const bots = await rpc<Array<{ id: string; computerMode: string }>>(page, "bots/list", {});
expect(bots.find((bot) => bot.id === botId)?.computerMode).toBe("dedicated");
});
test("second bot from plus opens create form before persist", async ({ page }, testInfo) => {
const stamp = Date.now();
await signup(page, `second-bot-form-${stamp}@rakazo.test`, "password12", "Second Bot Form");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
await openNewBot(page);
const form = page.getByTestId("create-bot-form");
await expect(form).toBeVisible();
await form.locator("label:has-text('Name') input").fill("Researcher");
await form.locator("label:has-text('Title') input").fill("Finds sources");
await form.locator("label:has-text('Description') textarea").fill("Briefs from the web.");
await captureScreenshot(page, testInfo, "second-bot-create-form");
const create = page.waitForResponse(
(response) => response.url().includes("/rpc/bots/create") && response.ok(),
);
await form.getByRole("button", { name: "Create", exact: true }).click();
await create;
await page.waitForURL(/\/app\/[^/]+$/);
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "closed");
await captureScreenshot(page, testInfo, "second-bot-created");
});