Files
PaperClipAI/ui/src/pages/McpConnect.test.tsx
T
DottaandPaperclip f8af817f8d fix(ui): make external agent pairing codes copyable (#16274)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - External agents connect through the shared MCP agent gateway.
> - Grok setup gives the operator a one-use code for the authorization
page.
> - The password field hides the code but does not provide a working
copy action.
> - The pending approval button names Dot before the agent preview
loads.
> - This pull request adds explicit code copying and a neutral approval
label.
> - The operator can review the fixed agent without sending credentials
to chat.

## Linked Issues or Issue Description

Refs: #16219, #15581, #15414.

**What happened?**

The Grok setup code is in a read-only password field. Ordinary browser
copying does not copy that field. The shared authorization page says
“Connect Dot with pairing code” before it knows the invited provider.
These defects blocked a watched Grok desktop setup before approval.

**Expected behavior**

An operator can copy the one-use code with a visible control. The code
stays hidden and outside the Bot prompt. The authorization page shows
the registered client and the fixed company, agent, permissions, and
authorizing person before approval.

**Steps to reproduce**

1. Choose New agent, Invite an external agent, and Grok Bot.
2. Save the Bot identity and open Connect access.
3. Try to copy the masked code into the Bot's browser authorization
page.
4. Inspect the pending approval action before a valid preview loads.

**Paperclip version or commit**

The fix starts from master commit
2c17d3ca52.

**Deployment mode**

Cloud. The same UI serves self-hosted instances.

## What Changed

- Add Copy code beside the masked Grok pairing field. Reuse CopyText and
the shared clipboard fallback, failure feedback, and retry.
- Associate the pairing label with its input for keyboard and
screen-reader access.
- Use Allow agent connection before and after the fixed-agent preview.
Keep approval disabled until that preview loads. Align Dot's generated
setup prompt and guide with that action.
- Add tests for copy failure and retry, a Grok invitation using the
Cursor client identity, and unchanged person and Dot connection paths.
- Add a waiting-for-authorization Storybook state and record the live
qualification blocker in the existing plan.

## Verification

- Focused Grok, Dot, and person-connection UI tests: 33 passed.
- Repository build: passed.
- Token gates: passed.
- Storybook catalog: passed; structure only.
- Watched Storybook preview: native browser input copied the full
synthetic code while the field stayed masked and Continue stayed
disabled. This is fixture evidence, not live OAuth or Runner
qualification.
- Repository typecheck and Storybook build: passed. UI typecheck and
production build passed again after the review fix.
- Current-head CI: 55 successes, two intentional skips, one neutral
result, no unfinished or failed gates. Greptile: 5/5, with the review
finding fixed and resolved. Head:
1b95218c91.
- The additional local aggregate test run is still running. No local
aggregate pass is claimed. The required full server, workspace, Runner,
browser, and release checks passed in CI for the current head.
- After deployment, resume the saved Grok setup, use Copy code, check
the fixed agent preview, and finish the real authenticated round trip.

## Risks

Copying temporarily places the one-use code on the clipboard. Its
existing expiry and one-use checks remain. The authorization endpoint,
grant scopes, fixed identity, and disabled-state checks do not change.
No persistent secret enters the prompt. Live provider and managed Runner
qualification remain incomplete.

## Model Used

OpenAI GPT-6 through Codex. Used tool calls, code execution, and browser
inspection. The exact serving model suffix and context limit are not
exposed in this session.

## 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>
2026-10-14 06:36:47 -05:00

347 lines
21 KiB
TypeScript

// @vitest-environment jsdom
import type { ReactNode } from "react";
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { beforeEach, expect, it, vi } from "vitest";
import { api } from "../api/client";
import { McpConnectPage, McpDevicePage } from "./McpConnect";
const route = vi.hoisted(() => ({ id: "request-one", companyId: null as string | null, unavailable: false, canWrite: true, requestedWrite: true, requestedConfigure: false, agentConnection: false, requiresSignIn: false, clientName: "Assistant", clientOrigin: null as string | null, setupUrl: "https://my.paperclip.app/orgs/new", reverseCompanies: false, hideFirst: false }));
vi.mock("@/lib/router", () => ({
useParams: () => ({ id: route.id }),
Link: ({ children, to }: { children: ReactNode; to: string }) => <a href={to}>{children}</a>,
}));
vi.mock("@/components/CompanyPatternIcon", () => ({
CompanyPatternIcon: ({ companyName, logoUrl }: { companyName: string; logoUrl?: string | null }) => <img alt={`${companyName} logo`} src={logoUrl ?? undefined} />,
}));
vi.mock("../api/client", () => ({ api: {
get: vi.fn(async () => ({
id: route.id, clientName: route.clientName, clientOrigin: route.clientOrigin, redirectOrigin: "https://assistant.example.test",
requestedWrite: route.requestedWrite, requestedConfigure: route.requestedConfigure, agentConnection: route.agentConnection, offlineAccess: true, requiresSignIn: route.requiresSignIn, requestedCompanyId: route.companyId,
companies: route.unavailable ? [] : [
{ id: route.companyId ?? "company-one", name: "Acme Research", logoUrl: "/api/assets/acme-logo/content", canWrite: route.canWrite },
...(!route.companyId ? [{ id: "company-two", name: "Design Partners", logoUrl: null, canWrite: true }] : []),
].filter(item => !route.hideFirst || item.id !== "company-one").sort((a, b) => route.reverseCompanies ? b.id.localeCompare(a.id) : a.id.localeCompare(b.id)), setupUrl: route.setupUrl,
})),
post: vi.fn(() => new Promise(() => {})),
} }));
beforeEach(() => {
Object.assign(route, { id: "request-one", companyId: null, unavailable: false, canWrite: true, requestedWrite: true, requestedConfigure: false, agentConnection: false, requiresSignIn: false, clientName: "Assistant", clientOrigin: null, setupUrl: "https://my.paperclip.app/orgs/new", reverseCompanies: false, hideFirst: false });
vi.clearAllMocks();
});
function setup(device = false, agentPairingOnly = false) {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
const render = () => flushSync(() => root.render(<QueryClientProvider client={client}>{device ? <McpDevicePage initialCode="MIST-YPED" /> : <McpConnectPage agentPairingOnly={agentPairingOnly} />}</QueryClientProvider>));
render();
return {
client, container, render,
connect: () => Array.from(container.querySelectorAll("button")).find(item => item.textContent === "Connect organization")!,
checkbox: () => container.querySelector('[role="checkbox"]') as HTMLButtonElement,
cleanup: () => { flushSync(() => root.unmount()); container.remove(); client.clear(); },
};
}
it("lets a person correct an invalid device code without reloading", async () => {
vi.mocked(api.get).mockRejectedValueOnce(new Error("Invalid code"));
const page = setup(true);
try {
await vi.waitFor(() => expect(page.container.textContent).toContain("Invalid code"));
const edit = Array.from(page.container.querySelectorAll("button")).find(item => item.textContent === "Enter a different code")!;
flushSync(() => edit.click());
expect(page.container.querySelector<HTMLInputElement>("#device-code")?.value).toBe("MIST-YPED");
expect(page.container.querySelector("form")).not.toBeNull();
expect(api.post).not.toHaveBeenCalled();
} finally { page.cleanup(); }
});
it.each([false, true])("lets Dot use the operator pairing capability without signing into a board account (dedicated=%s)", async dedicated => {
Object.assign(route, { agentConnection: true, requiresSignIn: true });
vi.mocked(api.post).mockImplementationOnce(async () => ({ company: { id: "company-one", name: "Dot Test Drive" }, agent: { id: "agent-one", name: "Dot" }, permissions: "Assigned work only", accessDuration: "Ongoing until revoked", pairingExpiresAt: new Date(Date.now() + 60000).toISOString() }) as never);
const page = setup(false, dedicated);
try {
await vi.waitFor(() => expect(page.container.querySelector("#dot-pairing-code")).not.toBeNull());
const input = page.container.querySelector<HTMLInputElement>("#dot-pairing-code")!;
expect(input.type).toBe("password");
const connect = Array.from(page.container.querySelectorAll("button")).find(button => button.textContent === "Allow agent connection")!;
expect(connect.disabled).toBe(true);
flushSync(() => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, "x".repeat(32));
input.dispatchEvent(new Event("input", { bubbles: true }));
});
await vi.waitFor(() => expect(page.container.textContent).toContain("Dot Test Drive"));
expect(page.container.textContent).toContain("Assigned work only");
expect(page.container.textContent).toContain("Ongoing until revoked");
expect(connect.disabled).toBe(false);
flushSync(() => connect.click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith(`${dedicated ? "/dot-mcp" : "/mcp"}/requests/request-one/dot-pairing`, { pairingCode: "x".repeat(32) }));
} finally { page.cleanup(); }
});
it("keeps approval disabled while replacing a code and ignores an older preview response", async () => {
Object.assign(route, { agentConnection: true, requiresSignIn: true });
let resolveOld!: (value: unknown) => void;
let resolveCurrent!: (value: unknown) => void;
vi.mocked(api.post).mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve; }) as never)
.mockImplementationOnce(() => new Promise(resolve => { resolveCurrent = resolve; }) as never);
const page = setup();
try {
await vi.waitFor(() => expect(page.container.querySelector("#dot-pairing-code")).not.toBeNull());
const input = page.container.querySelector<HTMLInputElement>("#dot-pairing-code")!;
const connect = Array.from(page.container.querySelectorAll("button")).find(button => button.textContent === "Allow agent connection")!;
const enter = (value: string) => flushSync(() => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, value);
input.dispatchEvent(new Event("input", { bubbles: true }));
});
enter("x".repeat(32));
await vi.waitFor(() => expect(resolveOld).toBeTypeOf("function"));
enter("y".repeat(32));
expect(connect.disabled).toBe(true);
await vi.waitFor(() => expect(resolveCurrent).toBeTypeOf("function"));
resolveCurrent({ company: { id: "current", name: "Current company" }, agent: { id: "agent", name: "Dot" }, permissions: "Assigned work", accessDuration: "Until revoked" });
await vi.waitFor(() => expect(connect.disabled).toBe(false));
resolveOld({ company: { id: "old", name: "Old company" }, agent: { id: "other", name: "Other agent" }, permissions: "Other work", accessDuration: "Until revoked" });
await new Promise(resolve => setTimeout(resolve, 20));
expect(page.container.textContent).toContain("Current company");
expect(page.container.textContent).not.toContain("Old company");
enter("");
expect(connect.disabled).toBe(true);
expect(page.container.textContent).not.toContain("Current company");
} finally { page.cleanup(); }
});
it("does not offer Dot pairing-code approval on a personal connection", async () => {
const page = setup();
try {
await vi.waitFor(() => expect(page.container.querySelector("h1")).not.toBeNull());
expect(page.container.querySelector("#dot-pairing-code")).toBeNull();
} finally { page.cleanup(); }
});
it("identifies the receiving app and registered callback origin before approval", async () => {
route.companyId = "company-one";
route.clientName = "Claude";
route.clientOrigin = "https://claude.ai";
const page = setup();
try {
await vi.waitFor(() => expect(page.container.querySelector("h1")?.textContent).toBe("Connect Claude to Paperclip"));
expect(page.container.textContent).toContain("https://claude.ai");
expect(page.container.textContent).toContain("https://assistant.example.test");
expect(page.container.querySelector('img[src^="https://assistant.example.test"]')).toBeNull();
expect(page.container.textContent).not.toContain("Access for");
expect(page.container.querySelector('img[src="/brands/claude-color.svg"]')).not.toBeNull();
expect(page.container.querySelector('a[href="https://my.paperclip.app/orgs/new"]')).toBeNull();
expect(page.connect().disabled).toBe(false);
// Registration metadata is text, never an executable link or verified-app badge.
expect(page.container.querySelector('a[href="https://assistant.example.test"]')).toBeNull();
} finally { page.cleanup(); }
});
it("defaults eligible writes on and preserves opt-out across organization changes and refetch", async () => {
const page = setup();
try {
await vi.waitFor(() => expect(page.container.querySelector('input[type="radio"]')).not.toBeNull());
expect(page.connect().disabled).toBe(false);
expect((page.container.querySelector('input[value="company-one"]') as HTMLInputElement).checked).toBe(true);
expect(page.checkbox().getAttribute("aria-checked")).toBe("true");
expect(api.post).not.toHaveBeenCalled();
expect(page.container.querySelector("h1")?.textContent).toBe("Connect your assistant to Paperclip");
flushSync(() => page.checkbox().click());
flushSync(() => (page.container.querySelector('input[value="company-two"]') as HTMLInputElement).click());
expect(page.checkbox().getAttribute("aria-checked")).toBe("false");
flushSync(() => (page.container.querySelector('input[value="company-one"]') as HTMLInputElement).click());
expect(page.checkbox().getAttribute("aria-checked")).toBe("false");
await page.client.invalidateQueries({ queryKey: ["mcp-request", route.id] });
expect(page.checkbox().getAttribute("aria-checked")).toBe("false");
flushSync(() => page.connect().click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith("/mcp/requests/request-one/consent", { decision: "approve", companyId: "company-one", allowWrites: false, allowConfiguration: false }));
route.id = "request-two";
page.render();
await vi.waitFor(() => expect(page.container.querySelector('input[type="radio"]')).not.toBeNull());
expect((page.container.querySelector('input[type="radio"]') as HTMLInputElement).checked).toBe(true);
expect(page.connect().disabled).toBe(false);
expect(page.checkbox().getAttribute("aria-checked")).toBe("true");
} finally { page.cleanup(); }
});
it("keeps hosted organization identity fixed and defaults each new request to eligible write access", async () => {
route.companyId = "company-one";
const page = setup();
try {
await vi.waitFor(() => expect(page.container.textContent).toContain("Acme Research"));
expect(page.container.querySelector('input[type="radio"]')).toBeNull();
expect(page.container.querySelector('img[alt="Acme Research logo"]')?.getAttribute("src")).toBe("/api/assets/acme-logo/content");
expect(page.checkbox().getAttribute("aria-checked")).toBe("true");
flushSync(() => page.checkbox().click());
expect(page.checkbox().getAttribute("aria-checked")).toBe("false");
route.id = "hosted-two";
route.companyId = "company-two";
page.render();
await vi.waitFor(() => expect(page.container.textContent).toContain("Acme Research"));
expect(page.checkbox().getAttribute("aria-checked")).toBe("true");
route.canWrite = false;
await page.client.invalidateQueries({ queryKey: ["mcp-request", route.id] });
await vi.waitFor(() => expect(page.checkbox().disabled).toBe(true));
expect(page.checkbox().getAttribute("aria-checked")).toBe("false");
// Losing membership never turns a fixed hosted organization into a picker.
route.unavailable = true;
await page.client.invalidateQueries({ queryKey: ["mcp-request", route.id] });
await vi.waitFor(() => expect(page.container.textContent).toContain("selected organization is no longer available"));
expect(page.container.querySelector('input[type="radio"]')).toBeNull();
expect(page.connect().disabled).toBe(true);
expect(Array.from(page.container.querySelectorAll("button")).find(item => item.textContent === "Cancel")!.disabled).toBe(false);
} finally { page.cleanup(); }
});
it.each([
{ requestedWrite: true, canWrite: true, allowWrites: true },
{ requestedWrite: true, canWrite: false, allowWrites: false },
{ requestedWrite: false, canWrite: true, allowWrites: false },
])("submits only allowed requested access: %j", async ({ requestedWrite, canWrite, allowWrites }) => {
Object.assign(route, { companyId: "company-one", requestedWrite, canWrite });
const page = setup();
try {
await vi.waitFor(() => expect(page.container.textContent).toContain("Acme Research"));
if (requestedWrite) {
expect(page.checkbox().getAttribute("aria-checked")).toBe(String(allowWrites));
expect(page.checkbox().disabled).toBe(!canWrite);
} else expect(page.checkbox()).toBeNull();
flushSync(() => page.connect().click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith("/mcp/requests/request-one/consent", { decision: "approve", companyId: "company-one", allowWrites, allowConfiguration: false }));
} finally { page.cleanup(); }
});
it("pins the default organization across refetches and never falls back after it disappears", async () => {
const page = setup();
try {
await vi.waitFor(() => expect(page.container.querySelector<HTMLInputElement>('input[value="company-one"]')?.checked).toBe(true));
route.reverseCompanies = true;
await page.client.invalidateQueries({ queryKey: ["mcp-request", route.id] });
await vi.waitFor(() => expect(page.container.querySelector('input[type="radio"]')?.getAttribute("value")).toBe("company-two"));
expect(page.container.querySelector<HTMLInputElement>('input[value="company-one"]')?.checked).toBe(true);
route.hideFirst = true;
await page.client.invalidateQueries({ queryKey: ["mcp-request", route.id] });
await vi.waitFor(() => expect(page.connect().disabled).toBe(true));
expect(page.container.querySelector<HTMLInputElement>('input[value="company-two"]')?.checked).toBe(false);
expect(page.checkbox().getAttribute("aria-checked")).toBe("false");
expect(page.container.textContent).not.toContain("Create a hosted organization");
expect(api.post).not.toHaveBeenCalled();
} finally { page.cleanup(); }
});
it("denies without granting default work or configuration access", async () => {
route.requestedConfigure = true;
const page = setup();
try {
await vi.waitFor(() => expect(page.checkbox()?.getAttribute("aria-checked")).toBe("true"));
flushSync(() => Array.from(page.container.querySelectorAll("button")).find(button => button.textContent === "Cancel")!.click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith("/mcp/requests/request-one/consent", { decision: "deny", companyId: "company-one", allowWrites: false, allowConfiguration: false }));
} finally { page.cleanup(); }
});
it.each([false, true])("explains Dot agent consent and requires an operator in both browser and device flows (device=%s)", async device => {
Object.assign(route, { companyId: "company-one", agentConnection: true, canWrite: false });
const page = setup(device);
const connect = () => Array.from(page.container.querySelectorAll("button")).find(button => button.textContent === "Connect Dot agent")!;
try {
await vi.waitFor(() => expect(page.container.textContent).toContain("Connect Dot as a Paperclip agent"));
expect(page.container.textContent).not.toContain("Read all of your Paperclip data");
expect(page.checkbox()).toBeNull();
expect(connect().disabled).toBe(true);
route.canWrite = true;
await page.client.invalidateQueries({ queryKey: [device ? "mcp-device" : "mcp-request", device ? "MIST-YPED" : route.id] });
await vi.waitFor(() => expect(connect().disabled).toBe(false));
flushSync(() => connect().click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith(device ? "/mcp/device/consent" : "/mcp/requests/request-one/consent", {
decision: "approve", companyId: "company-one", allowWrites: false, allowConfiguration: false, ...(device ? { userCode: "MIST-YPED" } : {}),
}));
} finally { page.cleanup(); }
});
it("does not fetch client-selected favicons or trust names for branding", async () => {
route.clientName = "Claude";
route.clientOrigin = "https://unrecognized.example.test";
const page = setup();
try {
await vi.waitFor(() => expect(page.container.textContent).toContain(route.clientOrigin));
expect(page.container.textContent).toContain("https://assistant.example.test");
expect(page.container.querySelector('img[src^="https://"]')).toBeNull();
expect(page.container.querySelector('img[src="/brands/claude-color.svg"]')).toBeNull();
expect(api.post).not.toHaveBeenCalled();
} finally { page.cleanup(); }
});
it.each([
{ device: false, optOut: false },
{ device: false, optOut: true },
{ device: true, optOut: false },
{ device: true, optOut: true },
])("uses one write choice for requested work and configuration access: %j", async ({ device, optOut }) => {
route.requestedConfigure = true;
const page = setup(device);
try {
await vi.waitFor(() => expect(page.checkbox()?.getAttribute("aria-checked")).toBe("true"));
expect(page.container.querySelectorAll('[role="checkbox"]')).toHaveLength(1);
expect(page.container.textContent).toContain("Write all of your Paperclip data");
expect(page.container.textContent).not.toContain("Allow configuring");
if (optOut) flushSync(() => page.checkbox().click());
flushSync(() => page.connect().click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith(
device ? "/mcp/device/consent" : "/mcp/requests/request-one/consent",
{ decision: "approve", companyId: "company-one", allowWrites: !optOut, allowConfiguration: !optOut, ...(device ? { userCode: "MIST-YPED" } : {}) },
));
} finally { page.cleanup(); }
});
it.each([
{ requestedWrite: false, canWrite: true, enabled: true },
{ requestedWrite: true, canWrite: false, enabled: false },
])("keeps one write choice for configuration-only requests and viewer restrictions: %j", async ({ requestedWrite, canWrite, enabled }) => {
Object.assign(route, { requestedWrite, requestedConfigure: true, canWrite });
const page = setup();
try {
await vi.waitFor(() => expect(page.checkbox()?.getAttribute("aria-checked")).toBe(String(enabled)));
expect(page.container.querySelectorAll('[role="checkbox"]')).toHaveLength(1);
expect(page.checkbox().disabled).toBe(!canWrite);
flushSync(() => page.connect().click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ allowWrites: false, allowConfiguration: enabled })));
} finally { page.cleanup(); }
});
it("shows the fixed Grok agent and authorizing person before granting a Cursor client access", async () => {
Object.assign(route, { agentConnection: true, requiresSignIn: true, clientName: "Cursor", clientOrigin: "https://www.cursor.com" });
vi.mocked(api.post).mockImplementationOnce(async () => ({ providerKind: "grok_bot", authorizingPerson: "Test Operator",
company: { id: "company-one", name: "Acme Research" }, agent: { id: "agent-grok", name: "Night Shift" },
permissions: "Work as this agent. Execution requires a current task-bound session.", accessDuration: "Ongoing until revoked",
pairingExpiresAt: new Date(Date.now() + 60_000).toISOString() }) as never);
const page = setup(false, true);
try {
await vi.waitFor(() => expect(page.container.querySelector("#dot-pairing-code")).not.toBeNull());
const input = page.container.querySelector<HTMLInputElement>("#dot-pairing-code")!;
const connect = [...page.container.querySelectorAll("button")].find(button => button.textContent === "Allow agent connection")!;
expect(connect.disabled).toBe(true);
expect(page.container.textContent).not.toContain("Connect Dot");
flushSync(() => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, "g".repeat(32));
input.dispatchEvent(new Event("input", { bubbles: true }));
});
await vi.waitFor(() => expect(page.container.textContent).toContain("Allow this connection to work as Night Shift in Acme Research."));
expect(page.container.textContent).toContain("Test Operator");
expect(page.container.textContent).toContain("current task-bound session");
expect(page.container.querySelector("h1")?.textContent).toBe("Connect Cursor to Paperclip");
expect(api.post).toHaveBeenCalledTimes(1);
expect(api.post).toHaveBeenCalledWith("/dot-mcp/requests/request-one/dot-pairing/preview", { pairingCode: "g".repeat(32) });
flushSync(() => connect.click());
await vi.waitFor(() => expect(api.post).toHaveBeenCalledWith("/dot-mcp/requests/request-one/dot-pairing", { pairingCode: "g".repeat(32) }));
} finally { page.cleanup(); }
});