mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-18 05:23:39 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The onboarding and company switcher UI are the first places users create or select an organization > - Some of that surface still used older team/workspace wording even though the product model is company-centric > - Mixed wording makes the setup path feel inconsistent and can make users wonder whether they are creating a team, workspace, or company > - This pull request updates the affected UI copy to consistently say company > - The benefit is a clearer first-run and navigation experience without changing behavior ## Linked Issues or Issue Description No public issue exists for this small UI polish change. ### Problem or motivation The company creation and switcher surfaces used mixed team/workspace/company wording for the same concept, which makes the setup path feel inconsistent. ### Proposed solution Update the visible copy, accessibility label, inline comment, e2e expectations, and matching test expectations to use company-centric language consistently. ### Alternatives considered Leave the existing wording alone, but that preserves inconsistent terminology in a high-traffic setup path. ### Roadmap alignment This is focused UI polish and does not overlap with a roadmap-level core feature. ### Additional context The create action keeps its trailing ellipsis because it opens the onboarding wizard rather than completing immediately. ## What Changed - Updated front door and onboarding wizard labels from team-oriented copy to company-oriented copy. - Updated the sidebar company menu from workspace/team wording to company wording, including the trigger accessibility label and empty fallback text. - Kept the sidebar create action ellipsis for the dialog/wizard affordance. - Updated component and Playwright test expectations for the new copy. ## Verification - `pnpm exec vitest run ui/src/components/SidebarCompanyMenu.test.tsx` - Attempted `npx playwright test --config tests/e2e/playwright.config.ts tests/e2e/onboarding.spec.ts tests/e2e/nux-phase4-screenshots.spec.ts tests/e2e/planning-mode-visual-verification.spec.ts tests/e2e/conference-room-typing-intro.spec.ts`; local browser launch is blocked by missing host Chromium dependencies (`libatk1.0-0t64`, `libatspi2.0-0t64`, `libxcomposite1`, `libxdamage1`, `libxfixes3`, `libxrandr2`, `libgbm1`, `libasound2t64`). - Screenshots intentionally omitted because this is a copy-only change and no design screenshots are needed for review. ## Risks Low risk. This is copy-only UI polish plus matching test updates; no data model, API, migration, workflow, lockfile, or behavior changes are included. ## Model Used OpenAI GPT-5 Codex (`gpt-5`) via the Paperclip Codex agent, with tool-assisted repository inspection, GitHub CLI usage, and local command execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
162 lines
6.8 KiB
TypeScript
162 lines
6.8 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
const AGENT_NAME = "Chief of staff";
|
|
const TASK_TITLE = "Hire your first engineer and create a hiring plan";
|
|
|
|
test("captures planning mode UI for desktop and mobile", async ({ page }) => {
|
|
const timestamp = Date.now();
|
|
const companyName = `PAP-3413-${timestamp}`;
|
|
const screenshotDir = "test-results/planning-mode";
|
|
|
|
await page.route("**/test-environment", (route) =>
|
|
route.fulfill({
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ status: "pass", checks: [] }),
|
|
}),
|
|
);
|
|
|
|
await page.route("**/agent-hires", async (route) => {
|
|
const req = route.request();
|
|
const body = JSON.parse(req.postData() || "{}");
|
|
const auth = req.headers().authorization;
|
|
const real = await fetch(new URL(req.url()).toString(), {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(auth ? { Authorization: auth } : {}),
|
|
},
|
|
body: JSON.stringify({
|
|
name: body.name,
|
|
role: body.role,
|
|
adapterType: "http",
|
|
adapterConfig: { url: "http://127.0.0.1:1/dead" },
|
|
runtimeConfig: { heartbeat: { enabled: false } },
|
|
}),
|
|
});
|
|
await route.fulfill({
|
|
status: real.status,
|
|
contentType: "application/json",
|
|
body: await real.text(),
|
|
});
|
|
});
|
|
|
|
await page.goto("/onboarding");
|
|
const startBtn = page.getByRole("button", { name: /Start Onboarding|New Company|Add Agent/ });
|
|
if (await startBtn.count()) await startBtn.first().click();
|
|
|
|
const createCard = page.getByRole("button", { name: /Build a new company/ });
|
|
if (await createCard.count()) await createCard.first().click();
|
|
|
|
await expect(page.getByRole("heading", { name: "Name your company" })).toBeVisible({ timeout: 15_000 });
|
|
|
|
await page.locator('input[placeholder="Acme Corp"]').fill(companyName);
|
|
await page.getByRole("button", { name: /^Next/ }).click();
|
|
|
|
await expect(page.getByRole("heading", { name: "Define your mission" })).toBeVisible({ timeout: 30_000 });
|
|
await page
|
|
.getByPlaceholder("What is your team trying to achieve?")
|
|
.fill("Capture planning mode visual evidence for the graduated task UI.");
|
|
await page.getByRole("button", { name: /Confirm mission/ }).click();
|
|
|
|
await page.waitForSelector('input[placeholder="Chief of staff"]', { timeout: 30_000 });
|
|
await expect(page.locator('input[placeholder="Chief of staff"]')).toHaveValue(AGENT_NAME);
|
|
|
|
await page.getByRole("button", { name: /^Next/ }).click();
|
|
await page.getByRole("button", { name: /Give it a heartbeat/ }).click();
|
|
|
|
await expect(page.getByRole("heading", { name: "Review" })).toBeVisible({ timeout: 30_000 });
|
|
await page.getByRole("button", { name: /Get started/ }).click();
|
|
await expect(page).toHaveURL(/\/dashboard$/, { timeout: 30_000 });
|
|
|
|
const baseOrigin = new URL(page.url()).origin;
|
|
const companyRes = await page.request.get(`${baseOrigin}/api/companies`);
|
|
expect(companyRes.ok()).toBe(true);
|
|
const companies = await companyRes.json();
|
|
const company = companies.find((c: { name: string }) => c.name === companyName);
|
|
expect(company).toBeTruthy();
|
|
const issueRes = await page.request.get(`${baseOrigin}/api/companies/${company.id}/issues`);
|
|
expect(issueRes.ok()).toBe(true);
|
|
const issues = await issueRes.json();
|
|
const planningSeedIssue = issues.find(
|
|
(candidate: { id: string; identifier?: string; title: string }) =>
|
|
candidate.title === TASK_TITLE,
|
|
);
|
|
expect(planningSeedIssue).toBeTruthy();
|
|
|
|
const issue = planningSeedIssue;
|
|
const issueIdentifier = issue.identifier ?? issue.id;
|
|
const issuePath = `/${company.issuePrefix ?? company.id}/issues/${issueIdentifier}`;
|
|
const companyPrefix = company.issuePrefix ?? company.id;
|
|
const issueLinkSelector = `a[href$="/issues/${issueIdentifier}"]`;
|
|
|
|
const setMode = async (mode: "standard" | "planning") => {
|
|
const patchRes = await page.request.patch(`${baseOrigin}/api/issues/${issue.id}`, {
|
|
data: { workMode: mode },
|
|
});
|
|
expect(patchRes.ok()).toBe(true);
|
|
await expect
|
|
.poll(async () => {
|
|
const currentRes = await page.request.get(`${baseOrigin}/api/issues/${issue.id}`);
|
|
expect(currentRes.ok()).toBe(true);
|
|
const current = await currentRes.json();
|
|
return current.workMode;
|
|
}, { timeout: 10_000 })
|
|
.toBe(mode);
|
|
};
|
|
|
|
await setMode("planning");
|
|
|
|
await page.goto(issuePath);
|
|
await expect(page.getByText("Plan mode").first()).toBeVisible();
|
|
await expect(page.getByTestId("issue-chat-composer")).toHaveAttribute("data-pending-work-mode", "planning");
|
|
const desktopPlanningToggle = page.getByTestId("issue-chat-composer-work-mode-toggle");
|
|
await expect(desktopPlanningToggle).toBeVisible();
|
|
await expect(desktopPlanningToggle).toHaveAttribute("data-pending-work-mode", "planning");
|
|
await expect(desktopPlanningToggle).toHaveAttribute("aria-pressed", "true");
|
|
|
|
await page.screenshot({
|
|
path: `${screenshotDir}/desktop-planning-detail-${timestamp}.png`,
|
|
fullPage: true,
|
|
});
|
|
|
|
await page.goto(`/${companyPrefix}/issues`);
|
|
await expect(page.locator(issueLinkSelector)).toBeVisible();
|
|
await expect(page.locator(issueLinkSelector)).not.toContainText("Plan mode");
|
|
await page.screenshot({
|
|
path: `${screenshotDir}/desktop-planning-row-${timestamp}.png`,
|
|
fullPage: true,
|
|
});
|
|
|
|
await page.goto(issuePath);
|
|
await page.getByTestId("issue-chat-composer-work-mode-toggle").click();
|
|
await page.getByTestId("issue-chat-composer-work-mode-menu-standard").click();
|
|
await expect(page.getByTestId("issue-chat-composer")).toHaveAttribute("data-pending-work-mode", "standard");
|
|
await expect(page.getByTestId("issue-chat-composer-work-mode-toggle")).toHaveAttribute("data-pending-work-mode", "standard");
|
|
await expect(page.getByTestId("issue-chat-composer-work-mode-toggle")).toHaveAttribute("aria-pressed", "false");
|
|
await page.screenshot({
|
|
path: `${screenshotDir}/desktop-standard-toggle-${timestamp}.png`,
|
|
fullPage: true,
|
|
});
|
|
|
|
await setMode("planning");
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
await page.goto(issuePath);
|
|
await expect(page.getByText("Plan mode").first()).toBeVisible();
|
|
const mobilePlanningToggle = page.getByTestId("issue-chat-composer-work-mode-toggle");
|
|
await expect(mobilePlanningToggle).toBeVisible();
|
|
await expect(mobilePlanningToggle).toHaveAttribute("data-pending-work-mode", "planning");
|
|
await expect(mobilePlanningToggle).toHaveAttribute("aria-pressed", "true");
|
|
await page.screenshot({
|
|
path: `${screenshotDir}/mobile-planning-detail-${timestamp}.png`,
|
|
fullPage: true,
|
|
});
|
|
|
|
await page.goto(`/${companyPrefix}/issues`);
|
|
await expect(page.locator(issueLinkSelector)).toBeVisible();
|
|
await expect(page.locator(issueLinkSelector)).not.toContainText("Plan mode");
|
|
await page.screenshot({
|
|
path: `${screenshotDir}/mobile-planning-row-${timestamp}.png`,
|
|
fullPage: true,
|
|
});
|
|
});
|