mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 05:56:19 +01:00
The agent, drawn as itself (#12274)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A new customer's first session ends in the tenant's agent arc: create an agent, connect a model, review > - Walking it turned up questions the arc had no business asking — a role picker using a vocabulary the customer has not been given, a model picker asking them to judge models they have not met — and chrome restating what they had just watched happen > - Each one costs a first-session customer attention at the exact moment they are deciding what this product is > - This pull request cuts the arc to what it must ask, and draws the agent as itself so the arc has a visible subject > - The benefit is three steps that each ask one thing, ending on an agent that is visibly ready ## Linked Issues or Issue Description No public issue exists. The changes come from walking the sign-up arc end to end. **What happened:** The agent step asks for a role from a fixed enum before asking for a name. The model step shows two "Recommended" badges (on both options), an "Adapter type" eyebrow, and a model picker. The review step lists a three-row checklist of work the customer just performed. The progress strip is a full-width segmented bar. **Expected behavior:** The agent step asks for a name. The model step offers the two harnesses and hides the rest behind advanced settings. The review step says the agent is ready. The strip counts three discrete steps. **Steps to reproduce:** 1. Sign up and enter the tenant wizard on the agent arc. 2. Observe the role select above the optional name field. 3. Continue to the model step: both options carry a "Recommended" badge, and a model picker sits below. 4. Continue to review: a checklist restates the organization name, agent, and model. **Additional context:** The brand pill assets (`pill-1-dormant.svg`, `pill-1-alive.svg`) are transcribed verbatim into a component rather than approximated. The role removal exposed a latent silent-failure path — see Risks. ## What Changed - `PillGuy` renders the brand pill in two states; the arc holds one instance, dormant through create and connect, alive on review. - The agent step asks for a name only. The name is required; the role picker is gone. - `DEFAULT_AGENT_ROLE` (`general`) backs every onboarding hire, and `agentRole` now defaults to it rather than empty. - The model step drops both "Recommended" badges, the "Adapter type" eyebrow, and the model picker; "More Agent Adapter Types" becomes "Advanced settings"; the sub-line becomes "Paperclip works with your existing subscription or API keys." - The review step drops its checklist; the heading becomes "Let's get started..." with "[name] is ready to work!". - The progress strip renders three left-aligned dots at the previous gap. - Five e2e specs and both wizard unit suites migrate off `#onboarding-agent-role`. ## Verification Run the tenant suite: ``` cd ui && npx vitest run ``` - 4398 tests pass across 474 files; `npx tsc --noEmit` clean. - Walked live in a local instance: agent step (dots, dormant pill, name placeholder), model step (no badges/eyebrow/picker, "Advanced settings"), review (pill alive, new copy, no checklist). - The retargeted role test asserts the hire payload carries `role: "general"` and the typed name — it is the test that catches the silent failure below. ## Risks - **A latent silent failure, now closed.** `handleGiveHeartbeat` returns early when `agentRole` is empty. With the picker removed and no default, Connect would have hired nobody and shown no error. The default closes it; the guard stays for any future path that clears the role. - **Behavioral change:** every onboarding hire is filed as `general` rather than a chosen role. The role remains editable in the app. - **Behavioral change:** the model is no longer chosen during onboarding. Every adapter offered here resolves its own default in `buildAdapterConfig`, and the model is changeable later. - **Assets:** the pill carries its own gradient fills and does not follow the theme. That is deliberate — the agent looks like itself on either ground. ## Model Used Claude Opus 5 (`claude-opus-5`) via Claude Code, with tool use and code 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 - [x] My branch name describes the change and contains no internal Paperclip ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] 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: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
24d639abad
commit
eb86fcd498
13 files changed
+481
-350
No files matched your search
@@ -73,16 +73,15 @@ async function runOnboardingWizard(page: Page, companyName: string) {
|
||||
if (await frontDoor.count()) await frontDoor.first().click();
|
||||
|
||||
// Step 1: company name.
|
||||
await page.getByPlaceholder("Acme Corp").fill(companyName);
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
await page.getByPlaceholder("e.g. Northwind Labs").fill(companyName);
|
||||
await page.getByRole("button", { name: /^Continue/ }).click();
|
||||
|
||||
// Step 1's "Next" creates the company; the mission step no longer runs.
|
||||
|
||||
// Step 3: the lead's role, then its name. The role gates "Next", and
|
||||
// choosing one fills the name — so the walk only types here to override it.
|
||||
await page.waitForSelector("#onboarding-agent-role", { timeout: 15_000 });
|
||||
await page.locator("#onboarding-agent-role").click();
|
||||
await page.getByRole("option", { name: "CEO", exact: true }).click();
|
||||
// Step 3: name the agent. The role picker is gone — the arc asks for a
|
||||
// name and hires under the neutral `general` role.
|
||||
await page.waitForSelector("#onboarding-agent-name", { timeout: 30_000 });
|
||||
await page.locator("#onboarding-agent-name").fill("Ada");
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
|
||||
// Step 4: adapter (claude_local default); heartbeat is intercepted.
|
||||
|
||||
@@ -66,15 +66,15 @@ test.describe("NUX Phase 4 visual QA", () => {
|
||||
await createCard.first().click();
|
||||
}
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Name your organization" }),
|
||||
page.getByRole("heading", { name: "What is the name of your organization?" }),
|
||||
).toBeVisible({ timeout: 15_000 });
|
||||
await page.getByPlaceholder("Acme Corp").fill("QA Robotics");
|
||||
await page.getByPlaceholder("e.g. Northwind Labs").fill("QA Robotics");
|
||||
await page.screenshot({ path: shot("02-create-name.png") });
|
||||
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
await page.getByRole("button", { name: /^Continue/ }).click();
|
||||
// Step 1's "Next" creates the company and goes straight to the team lead.
|
||||
// The mission screenshot that sat here is gone with the step it captured.
|
||||
await page.waitForSelector("#onboarding-agent-role", {
|
||||
await page.waitForSelector("#onboarding-agent-name", {
|
||||
timeout: 30_000,
|
||||
});
|
||||
await page.screenshot({ path: shot("04-hire-team-lead.png") });
|
||||
@@ -111,10 +111,10 @@ test.describe("NUX Phase 4 visual QA", () => {
|
||||
await page.getByRole("button", { name: /Add agents to your org/ }).click();
|
||||
// The grow path shares step 1 (company name) before its step-2 intake.
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Name your organization" }),
|
||||
page.getByRole("heading", { name: "What is the name of your organization?" }),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByPlaceholder("Acme Corp").fill("QA Robotics Grow");
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
await page.getByPlaceholder("e.g. Northwind Labs").fill("QA Robotics Grow");
|
||||
await page.getByRole("button", { name: /^Continue/ }).click();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: /Tell us about your team/ }),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
@@ -53,15 +53,15 @@ test.describe("Onboarding wizard", () => {
|
||||
|
||||
// Step 1 — Name your organization.
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Name your organization" }),
|
||||
page.getByRole("heading", { name: "What is the name of your organization?" }),
|
||||
).toBeVisible({ timeout: 15_000 });
|
||||
await page.getByPlaceholder("Acme Corp").fill(COMPANY_NAME);
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
await page.getByPlaceholder("e.g. Northwind Labs").fill(COMPANY_NAME);
|
||||
await page.getByRole("button", { name: /^Continue/ }).click();
|
||||
|
||||
// Step 1's "Next" now creates the company and goes straight to the agent.
|
||||
// The mission step used to sit between them and do the creating; onboarding
|
||||
// no longer asks for the mission, which is collected later in the app.
|
||||
await page.waitForSelector("#onboarding-agent-role", {
|
||||
await page.waitForSelector("#onboarding-agent-name", {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
|
||||
@@ -54,24 +54,25 @@ test("captures planning mode UI for desktop and mobile", async ({ page }) => {
|
||||
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 organization" })).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByRole("heading", { name: "What is the name of your organization?" })).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
await page.locator('input[placeholder="Acme Corp"]').fill(companyName);
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
await page.locator('input[placeholder="e.g. Northwind Labs"]').fill(companyName);
|
||||
await page.getByRole("button", { name: /^Continue/ }).click();
|
||||
|
||||
// Naming the company creates it and goes straight to the agent step.
|
||||
|
||||
// The lead is no longer pre-named. Choosing a role fills the name from the
|
||||
// role's label, which is also what gates "Next".
|
||||
await page.waitForSelector("#onboarding-agent-role", { timeout: 30_000 });
|
||||
await page.locator("#onboarding-agent-role").click();
|
||||
await page.getByRole("option", { name: "CEO", exact: true }).click();
|
||||
await expect(page.locator("#onboarding-agent-name")).toHaveValue(AGENT_NAME);
|
||||
// The agent step asks for a name and nothing else; the name is what gates
|
||||
// "Next", and the hire is filed under the neutral `general` role.
|
||||
await page.waitForSelector("#onboarding-agent-name", { timeout: 30_000 });
|
||||
await page.locator("#onboarding-agent-name").fill(AGENT_NAME);
|
||||
|
||||
await page.getByRole("button", { name: /^Next/ }).click();
|
||||
await page.getByRole("button", { name: /^Connect$/ }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Review" })).toBeVisible({ timeout: 30_000 });
|
||||
// The review step names the agent rather than the step.
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Let's get started..." }),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole("button", { name: /Get started/ }).click();
|
||||
// The wizard now drops the user straight onto the first task's detail page,
|
||||
// and must not bounce through the dashboard (PAP-404).
|
||||
|
||||
Reference in new issue
Block a user