mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 09:37:19 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The onboarding wizard in `ui/` hires that first agent. It runs three steps: create the agent, connect a model, and review > - A standalone prototype holds the agreed design for these steps. #10786 ported that prototype, but #11067 reverted it in full because the port deleted `OnboardingWizard.tsx` while four pull requests were editing that file > - Those four pull requests have since merged. The revert said the port can "re-land incrementally", and this is that re-land > - This pull request takes the presentational layer from the prototype only. It keeps master's wizard as the source of behaviour, so the eight onboarding fixes merged since the revert stay in place > - The benefit is that the three agent steps match the agreed design, and no merged fix is lost to get there ## Linked Issues or Issue Description Refs #10786 — the first attempt to land this design. Refs #11067 — the revert that asked for it to re-land in smaller steps. No public issue exists for the re-land. The problem is described below. **Subsystem affected** The `ui` package. The change touches the onboarding wizard, the agent capsule, and one Storybook story. It adds four small presentational components under `ui/src/components/onboarding/`. **Current behavior** The wizard's agent steps do not match the prototype. Each step shows a small heading beside an icon, above a form. The agent capsule sits below that heading and does not animate. The agent gets a name but no role, so every first agent is created as `ceo`. The wizard also shows a five-segment progress bar on these steps. A walker who enters on the agent step cannot reach the first two segments, so two of the five can never be filled. **Proposed behavior** The three steps use the prototype's card, its centred display heading, and its footer. One capsule sits above the heading and stays mounted across all three steps, so it reads as one object being built rather than three screens that each show their own. A three-segment strip counts these steps for a walker who enters on them. The full-length bar stays for a walker who starts at step one, so that count never restarts partway. The agent step gains a role. The options come from the agent role enum, not from the prototype's mock list. **Reason and benefit** The design is agreed and already built once. Re-landing it presentation-first keeps the behaviour that master gained after the revert. Sourcing roles from the enum matters. The prototype offers "Coder", which is not a valid role — the enum uses `engineer` — so a walker who picked it would fail validation at hire time. **Breaking changes** None. The wizard keeps its routes, its draft format, and its hire call. The draft gains one optional field, `agentRole`. A draft saved before this change loads without it and falls back to the default. ## What Changed - Add `ui/src/components/onboarding/`: `Stepper`, `OnboardingCard`, `OnboardingHeading`, `FooterNav`, `AgentPreview`, and shared motion constants - Rebuild wizard steps 3–5 on those parts: one card, the capsule above a centred heading, and one footer - Hold one `AgentCapsule` across the three steps. It springs in once, then morphs from dashed slot to traced outline to filled - Add `strokeDraw` to `AgentCapsule`. It traces the outline instead of cross-fading it. The dashed layer holds until the trace ends - Add a role select to the agent step. Choosing a role fills the name, unless the walker typed one - Show one progress indicator per run, not two - Label strip segments by destination, not by number - Add `motion` to the `ui` package - Add a Storybook story for the strip and the capsule states ## Verification Run the tests: ``` pnpm --filter @paperclipai/ui exec vitest run pnpm --filter @paperclipai/ui exec tsc -p tsconfig.json --noEmit ``` 4235 tests pass. The typecheck is clean. To see the steps, start the app and open `/<PREFIX>/onboarding` for a company that has a company-level goal. The wizard opens on the agent step. Step three requires a hire. Three absence assertions were checked by fault injection. Each one fails when the old behaviour returns: - put the step counter back, and the "shows no step counter" test fails - default `strokeDraw` to true, and the cross-fade test fails - restore the timer gate on the strip, and the indicator test fails ## Risks Low to medium. `motion` is one new dependency in `ui`. #11067 gave dependency weight as one of three reasons to revert #10786, so this branch carries the smallest set that works. `motion` drives the step transitions and the capsule choreography, and three files import it. An earlier revision of this branch also added `three` and `@types/three`. Both are removed. They existed for the 3D backdrop, which belongs to the auth and welcome screens rather than to these three steps, so nothing on this branch imported them. The role select changes what the wizard sends. Before this change every first agent was hired as `ceo`. Now the walker chooses. The values come from the enum, so the server accepts all of them. Steps 1 and 2 keep the older design. They do not run on the Cloud-first path, where the company already exists. ## Model Used Claude Opus 5 (`claude-opus-5`), with extended thinking, tool use, and code execution. Used for the code, the tests, and this description. ## 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: Claude Opus 5 <noreply@anthropic.com>
102 lines
4.1 KiB
TypeScript
102 lines
4.1 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
|
|
/**
|
|
* E2E: Onboarding wizard flow (NUX Phase 2 expanded wizard).
|
|
*
|
|
* The wizard now opens on a front door (path picker) and the "Create a new
|
|
* company" path runs:
|
|
* Step 0 — Front door (Create a new company / Level up existing)
|
|
* Step 1a — Name your organization
|
|
* Step 1b — Define your mission (direct or guided)
|
|
* Step 2 — Hire your team lead (adapter picker)
|
|
* Step 3+ — Launch celebration → CEO chat → hiring plan → orientation
|
|
*
|
|
* This test covers the deterministic, LLM-free core: it drives the front door
|
|
* through company naming + mission definition (which creates the company and a
|
|
* company-level goal) and verifies the wizard advances to the team-lead step.
|
|
*
|
|
* The tail (CEO chat at step 4, hiring-plan generation at step 5, final
|
|
* landing) depends on a live LLM and is verified separately during manual /
|
|
* LLM-backed QA — see PAP-50. Surface-level rendering of every step is
|
|
* snapshotted by nux-phase4-screenshots.spec.ts.
|
|
*/
|
|
|
|
const COMPANY_NAME = `E2E-Test-${Date.now()}`;
|
|
const MISSION = "Build affordable home robots that handle household chores.";
|
|
|
|
test.describe("Onboarding wizard", () => {
|
|
test("create-company path: name + mission creates company and goal", async ({
|
|
page,
|
|
}) => {
|
|
const pageErrors: string[] = [];
|
|
page.on("pageerror", (err) => pageErrors.push(err.message));
|
|
|
|
// New-NUX surfaces are flag-gated default-OFF (PAP-136/137/138): turn the
|
|
// experimental flag on for this throwaway instance before driving them.
|
|
const flagRes = await page.request.patch("/api/instance/settings/experimental", {
|
|
data: { enableConferenceRoomChat: true },
|
|
});
|
|
expect(flagRes.ok()).toBe(true);
|
|
|
|
await page.goto("/onboarding");
|
|
|
|
// The wizard may open on a launcher card or directly on the capsule
|
|
// wizard; the front door (step 0) requires a click into the create path.
|
|
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();
|
|
}
|
|
|
|
// Step 1 — Name your organization.
|
|
await expect(
|
|
page.getByRole("heading", { name: "Name your organization" }),
|
|
).toBeVisible({ timeout: 15_000 });
|
|
await page.getByPlaceholder("Acme Corp").fill(COMPANY_NAME);
|
|
await page.getByRole("button", { name: /^Next/ }).click();
|
|
|
|
// Step 2 — Define your mission (direct entry is the default path).
|
|
await expect(
|
|
page.getByRole("heading", { name: "Define your mission" }),
|
|
).toBeVisible({ timeout: 10_000 });
|
|
await page
|
|
.getByPlaceholder("What is your team trying to achieve?")
|
|
.fill(MISSION);
|
|
|
|
// "Confirm mission" creates the company + a company-level goal, then
|
|
// advances to the team-lead naming step of the capsule wizard.
|
|
await page.getByRole("button", { name: /Confirm mission/ }).click();
|
|
await page.waitForSelector("#onboarding-agent-role", {
|
|
timeout: 30_000,
|
|
});
|
|
|
|
// Verify the company + company-level goal were persisted.
|
|
const baseUrl = page.url().split("/").slice(0, 3).join("/");
|
|
const companiesRes = await page.request.get(`${baseUrl}/api/companies`);
|
|
expect(companiesRes.ok()).toBe(true);
|
|
const companies = await companiesRes.json();
|
|
const company = companies.find(
|
|
(c: { name: string }) => c.name === COMPANY_NAME,
|
|
);
|
|
expect(company, `company ${COMPANY_NAME} should exist`).toBeTruthy();
|
|
|
|
const goalsRes = await page.request.get(
|
|
`${baseUrl}/api/companies/${company.id}/goals`,
|
|
);
|
|
expect(goalsRes.ok()).toBe(true);
|
|
const goals = await goalsRes.json();
|
|
const companyGoal = (Array.isArray(goals) ? goals : []).find(
|
|
(g: { level?: string }) => g.level === "company",
|
|
);
|
|
expect(companyGoal, "a company-level goal should be created").toBeTruthy();
|
|
|
|
// The expanded wizard must not crash the app (Rules-of-Hooks regression).
|
|
expect(pageErrors, pageErrors.join("\n")).toHaveLength(0);
|
|
});
|
|
});
|