Each project is now a single sidebar row with its own icon and color that opens the project's coordinator conversation. The whole row drags to reorder. The row menu gains Customize for name, icon and color. Inside the coordinator conversation, an overview lists the project's agent threads that need the user, are running, or recently finished. Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ Co-authored-by: Claude <noreply@anthropic.com>
78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import {
|
|
captureScreenshot,
|
|
completeOnboarding,
|
|
projectRow,
|
|
rpc,
|
|
signup,
|
|
waitForChatComposer,
|
|
} from "./helpers";
|
|
|
|
test("a project is one sidebar row with its own look, and its coordinator lists the delegated threads", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `project-coordinator-${stamp}@rakazo.test`, "password12", "Coordinator");
|
|
await completeOnboarding(page);
|
|
|
|
const projekte = page.locator(".sidebar-projekte");
|
|
await projekte.getByTestId("create-project-trigger").click();
|
|
const dialog = page.getByRole("dialog", { name: "New project" });
|
|
await dialog.getByLabel("Name").fill("Garden");
|
|
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
|
|
await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 });
|
|
await waitForChatComposer(page);
|
|
|
|
const row = projectRow(projekte, "Garden");
|
|
await expect(row).toBeVisible({ timeout: 30_000 });
|
|
await expect(row).toHaveAttribute("aria-current", "page");
|
|
await expect(row.locator("[data-project-icon]")).toHaveAttribute("data-project-icon", "folder");
|
|
|
|
// Name, icon and color change from the row's menu.
|
|
await row.click({ button: "right" });
|
|
await page.getByRole("menuitem", { name: "Customize" }).click();
|
|
const look = page.getByTestId("project-look-dialog");
|
|
await expect(look).toBeVisible();
|
|
await look.getByLabel("Name").fill("Garten");
|
|
await look.getByRole("radio", { name: "green" }).click();
|
|
await look.getByTestId("project-icon-leaf").click();
|
|
await captureScreenshot(page, testInfo, "project-look");
|
|
await look.getByRole("button", { name: "Save", exact: true }).click();
|
|
await expect(look).toBeHidden();
|
|
|
|
const renamed = projectRow(projekte, "Garten");
|
|
await expect(renamed.locator("[data-project-icon]")).toHaveAttribute("data-project-icon", "leaf");
|
|
await expect(renamed.locator("[data-project-icon]")).toHaveAttribute(
|
|
"data-project-color",
|
|
"green",
|
|
);
|
|
await page.reload({ waitUntil: "domcontentloaded" });
|
|
await expect(renamed.locator("[data-project-icon]")).toHaveAttribute(
|
|
"data-project-icon",
|
|
"leaf",
|
|
{ timeout: 30_000 },
|
|
);
|
|
|
|
// The coordinator hands work to a specialist; the finished thread shows in its overview.
|
|
await waitForChatComposer(page);
|
|
await rpc(page, "bots/create", {
|
|
name: "Gardener",
|
|
title: "",
|
|
description: "",
|
|
instructions: "",
|
|
notifyOnFinish: true,
|
|
});
|
|
const composer = page.getByRole("combobox", { name: "Message" });
|
|
await composer.fill("delegate to the bot named Gardener: plan the spring beds");
|
|
await composer.press("Enter");
|
|
const card = page.getByTestId("transcript").getByTestId("delegated-task-card");
|
|
await expect(card).toHaveAttribute("data-status", "done", { timeout: 60_000 });
|
|
|
|
const overview = page.getByTestId("project-overview");
|
|
const done = overview.getByRole("button", { name: /Done/ });
|
|
await expect(done).toBeVisible({ timeout: 30_000 });
|
|
await done.click();
|
|
await expect(overview.getByText("Gardener").first()).toBeVisible();
|
|
await captureScreenshot(page, testInfo, "project-overview");
|
|
});
|