Address Greptile bootstrap claim feedback

This commit is contained in:
Dotta committed 2026-05-27 04:44:06 +00:00
1 parent d93680be79
commit 983ee24ee5
4 files changed
+49 -84

No files matched your search

+34 -78
View File
@@ -42,9 +42,6 @@ vi.mock("@/lib/router", () => ({
useParams: () => ({}),
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
async function flushReact() {
await Promise.resolve();
await new Promise((resolve) => window.setTimeout(resolve, 0));
@@ -58,6 +55,29 @@ async function waitForText(container: HTMLElement, text: string) {
expect(container.textContent).toContain(text);
}
function renderGate(container: HTMLElement) {
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
flushSync(() => {
root.render(
<QueryClientProvider client={queryClient}>
<CloudAccessGate />
</QueryClientProvider>,
);
});
return root;
}
function unmountRoot(root: ReturnType<typeof createRoot>) {
flushSync(() => {
root.unmount();
});
}
describe("CloudAccessGate", () => {
let container: HTMLDivElement;
@@ -92,26 +112,13 @@ describe("CloudAccessGate", () => {
keyId: null,
});
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
flushSync(() => {
root.render(
<QueryClientProvider client={queryClient}>
<CloudAccessGate />
</QueryClientProvider>,
);
});
const root = renderGate(container);
await waitForText(container, "No company access");
expect(container.textContent).toContain("No company access");
expect(container.textContent).not.toContain("Outlet content");
flushSync(() => {
root.unmount();
});
unmountRoot(root);
});
it("allows authenticated users with company access through to the board", async () => {
@@ -128,26 +135,13 @@ describe("CloudAccessGate", () => {
keyId: null,
});
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
flushSync(() => {
root.render(
<QueryClientProvider client={queryClient}>
<CloudAccessGate />
</QueryClientProvider>,
);
});
const root = renderGate(container);
await waitForText(container, "Outlet content");
expect(container.textContent).toContain("Outlet content");
expect(container.textContent).not.toContain("No company access");
flushSync(() => {
root.unmount();
});
unmountRoot(root);
});
it("shows browser sign-in setup for signed-out private bootstrap-pending instances", async () => {
@@ -160,18 +154,7 @@ describe("CloudAccessGate", () => {
});
mockAuthApi.getSession.mockResolvedValue(null);
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
flushSync(() => {
root.render(
<QueryClientProvider client={queryClient}>
<CloudAccessGate />
</QueryClientProvider>,
);
});
const root = renderGate(container);
await waitForText(container, "Finish setting up this Paperclip");
expect(container.textContent).toContain("Finish setting up this Paperclip");
@@ -179,9 +162,7 @@ describe("CloudAccessGate", () => {
expect(container.textContent).toContain("pnpm paperclipai auth bootstrap-ceo");
expect(mockAccessApi.getCurrentBoardAccess).not.toHaveBeenCalled();
flushSync(() => {
root.unmount();
});
unmountRoot(root);
});
it("shows the claim action for signed-in private bootstrap-pending instances", async () => {
@@ -198,18 +179,7 @@ describe("CloudAccessGate", () => {
});
mockAccessApi.claimBootstrapAdmin.mockResolvedValue({ claimed: true, userId: "user-1" });
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
flushSync(() => {
root.render(
<QueryClientProvider client={queryClient}>
<CloudAccessGate />
</QueryClientProvider>,
);
});
const root = renderGate(container);
await waitForText(container, "Claim this instance");
expect(container.textContent).toContain("Claim this instance");
@@ -227,10 +197,9 @@ describe("CloudAccessGate", () => {
expect(mockAccessApi.claimBootstrapAdmin).toHaveBeenCalledTimes(1);
expect(container.textContent).toContain("You're the instance admin");
expect(container.textContent).toContain("Continue to dashboard");
flushSync(() => {
root.unmount();
});
unmountRoot(root);
});
it("keeps public bootstrap-pending instances invite-only", async () => {
@@ -246,18 +215,7 @@ describe("CloudAccessGate", () => {
user: { id: "user-1", email: "user@example.com", name: "User", image: null },
});
const root = createRoot(container);
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
flushSync(() => {
root.render(
<QueryClientProvider client={queryClient}>
<CloudAccessGate />
</QueryClientProvider>,
);
});
const root = renderGate(container);
await waitForText(container, "This Paperclip is waiting on its first admin");
expect(container.textContent).toContain("This Paperclip is waiting on its first admin");
@@ -266,8 +224,6 @@ describe("CloudAccessGate", () => {
expect(container.textContent).not.toContain("Sign in / Create account");
expect(mockAccessApi.claimBootstrapAdmin).not.toHaveBeenCalled();
flushSync(() => {
root.unmount();
});
unmountRoot(root);
});
});
+1
View File
@@ -0,0 +1 @@
export const BOOTSTRAP_FALLBACK_COMMAND = "pnpm paperclipai auth bootstrap-ceo";
+7 -3
View File
@@ -2,10 +2,9 @@ import type { ReactNode } from "react";
import { Loader2, ShieldCheck, Terminal, TriangleAlert } from "lucide-react";
import { Link } from "@/lib/router";
import { Button } from "@/components/ui/button";
import { BOOTSTRAP_FALLBACK_COMMAND } from "@/bootstrapSetup";
import type { AuthSession } from "@paperclipai/shared";
const FALLBACK_COMMAND = "pnpm paperclipai auth bootstrap-ceo";
type BootstrapPendingPageProps = {
claimAvailable: boolean;
hasActiveInvite?: boolean;
@@ -28,7 +27,7 @@ function CliFallback({ hasActiveInvite = false }: { hasActiveInvite?: boolean })
: "Run this command on the host that runs Paperclip to print a one-time first-admin invite URL:"}
</p>
<pre className="mt-3 overflow-x-auto rounded-md border border-border bg-muted/30 p-3 font-mono text-xs">
{FALLBACK_COMMAND}
{BOOTSTRAP_FALLBACK_COMMAND}
</pre>
</div>
);
@@ -108,6 +107,11 @@ export function BootstrapPendingPage({
<Loader2 className="size-4 animate-spin text-muted-foreground" aria-hidden />
<span className="text-sm text-muted-foreground">Redirecting...</span>
</div>
<div className="mt-5">
<Button asChild variant="outline">
<a href="/">Continue to dashboard</a>
</Button>
</div>
</StateChrome>
);
}
+7 -3
View File
@@ -1,5 +1,6 @@
import type { ReactElement, ReactNode } from "react";
import { Loader2, ShieldCheck, Terminal, TriangleAlert } from "lucide-react";
import { BOOTSTRAP_FALLBACK_COMMAND } from "@/bootstrapSetup";
import { Button } from "@/components/ui/button";
type LabFixtureKey =
@@ -28,8 +29,6 @@ const FIXTURE_ORDER: LabFixtureKey[] = [
"public-invite-only",
];
const FALLBACK_COMMAND = "pnpm paperclipai auth bootstrap-ceo";
function CliFallback({ hasActiveInvite }: { hasActiveInvite: boolean }) {
return (
<div className="mt-6 border-t border-border pt-5">
@@ -43,7 +42,7 @@ function CliFallback({ hasActiveInvite }: { hasActiveInvite: boolean }) {
: "Run this command on the host that runs Paperclip to print a one‑time first‑admin invite URL:"}
</p>
<pre className="mt-3 overflow-x-auto rounded-md border border-border bg-muted/30 p-3 font-mono text-xs">
{FALLBACK_COMMAND}
{BOOTSTRAP_FALLBACK_COMMAND}
</pre>
</div>
);
@@ -170,6 +169,11 @@ function ClaimSuccess() {
<Loader2 className="size-4 animate-spin text-muted-foreground" aria-hidden />
<span className="text-sm text-muted-foreground">Redirecting&hellip;</span>
</div>
<div className="mt-5">
<Button asChild variant="outline">
<a href="/">Continue to dashboard</a>
</Button>
</div>
</StateChrome>
);
}