Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions apps/web/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -350,26 +350,6 @@ select:disabled,
color: var(--card-foreground);
}

/* The post-landing setup line (CL-6462): quieter than the health banner
above it — no icon, muted text — because nothing is wrong and nothing
is being asked of the reader. */
.setup-progress-note {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.5rem 1rem;
border-bottom: 1px solid var(--border);
background: var(--background);
flex-shrink: 0;
}

.setup-progress-note-text {
flex: 1;
min-width: 0;
font-size: 0.8125rem;
color: var(--muted-foreground);
}

/* The Plugins gallery's own "couldn't find that connection" notice
(CL-6092): a deep link from the shell banner that names a provider the
gallery has no card for. */
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/pages/new-workbench-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
// next slice).

import { Button, toast } from "@corbits/react-ui";
import { ChatCircle, GitPullRequest, Plus } from "@corbits/icons";
import {
ChatCircle,
GitPullRequest,
MagnifyingGlass,
Plus,
} from "@corbits/icons";
import {
ChatApiError,
describeChatError,
Expand Down Expand Up @@ -74,6 +79,7 @@ type RepoPickerState = {

const ROW_ICON: Record<WorkbenchTemplateId, typeof GitPullRequest> = {
"code-review": GitPullRequest,
"due-diligence": MagnifyingGlass,
blank: ChatCircle,
};

Expand Down
3 changes: 0 additions & 3 deletions apps/web/src/pages/onboarding-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import {
} from "../onboarding";
import type { CredentialProvider, CredentialProviderCard } from "../onboarding";
import { OnboardingLayout } from "../onboarding/onboarding-layout";
import { markSetupInProgress } from "../shell/setup-progress-note";
import type { SessionUser } from "../session";

/** No naming step means provisioning always needs a name to send — this
Expand Down Expand Up @@ -297,7 +296,6 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) {
if (state.phase === "finishing-setup") {
void completeSetup().then((outcome) => {
if (outcome.kind === "connected") {
if (outcome.agentsPending) markSetupInProgress();
navigate("/");
} else if (outcome.kind === "unseeded") {
setResumingUnseeded(true);
Expand Down Expand Up @@ -362,7 +360,6 @@ export function OnboardingPage({ user }: { readonly user: SessionUser }) {
// moves on now, and the warm loading state on the other side
// covers whatever is still coming online.
if (outcome.kind === "connected") {
if (outcome.agentsPending) markSetupInProgress();
navigate("/");
} else {
setState(
Expand Down
2 changes: 0 additions & 2 deletions apps/web/src/shell/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import {
useToggleCanvasFocus,
} from "./canvas-availability";
import { ProviderHealthBanner } from "./provider-health-banner";
import { SetupProgressNote } from "./setup-progress-note";
import { Sidebar } from "./sidebar";
import { ShellContextMenu } from "./context-menu/shell-context-menu";

Expand Down Expand Up @@ -173,7 +172,6 @@ export function AppShell({
<div className="shell-main" ref={mainRef}>
<div className="shell-main-content">
<ProviderHealthBanner />
<SetupProgressNote />
{routeHasNoStageTopBar(path) ? (
<StageTopBar
crumbs={[{ label: routeLabel(path) }]}
Expand Down
94 changes: 0 additions & 94 deletions apps/web/src/shell/setup-progress-note.tsx

This file was deleted.

21 changes: 16 additions & 5 deletions apps/web/src/workbench-templates.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
// The picker's row catalog (CL-6342): one entry per selectable kind, plus
// the disabled "more kinds soon" row. Copy is pinned to the approved mock
// verbatim — see `pages/new-workbench-picker.tsx` for the row rendering
// and `instant-agent-create.ts`'s `createWorkbenchFromTemplate` for what
// The picker's row catalog: one entry per selectable kind, plus the
// disabled "more kinds soon" row. `code-review` and `blank` are pinned to
// the approved mock (CL-6342) verbatim; `due-diligence` mirrors the
// backend's `DUE_DILIGENCE_TEMPLATE` (`@corbits/workflow-catalog`, CL-6499)
// — it and every other id here are still gated by what this bench's
// library actually serves before either is offered as a live row (see
// `NewWorkbenchPickerRoute`'s `servedTemplateIds`). See
// `pages/new-workbench-picker.tsx` for the row rendering and
// `instant-agent-create.ts`'s `createWorkbenchFromTemplate` for what
// picking one actually does today.

export type WorkbenchTemplateId = "code-review" | "blank";
export type WorkbenchTemplateId = "code-review" | "due-diligence" | "blank";

export type WorkbenchTemplate = {
readonly id: WorkbenchTemplateId;
Expand All @@ -19,6 +24,12 @@ export const WORKBENCH_TEMPLATES: readonly WorkbenchTemplate[] = [
promise:
"Three reviewers read every pull request and post what they'd change.",
},
{
id: "due-diligence",
title: "Research & due diligence",
promise:
"Scout researches the web and what your team already knows, and saves what it finds so you can pick it up later.",
},
{
id: "blank",
title: "Just start talking",
Expand Down
104 changes: 104 additions & 0 deletions apps/web/test/app-shell-setup-note-removed.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Regression test: the owner's first-run feedback was explicit — the
// "still setting up in the background" note is noise at the exact moment
// someone is forming a first impression, and it must never appear. This
// mounts the real shell with the legacy session flag set (as a stale
// browser tab from before the removal would have it) and asserts the
// note cannot render, proving removal rather than just a hidden default.

import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";

import { AppShell } from "../src/shell/app-shell";
import { BenchProvider } from "../src/bench-context";
import { NavigationProvider } from "../src/navigation";
import { ProviderHealthProvider } from "../src/shell/provider-health-context";
import { ShellChromeProvider } from "../src/shell/shell-chrome-provider";
import { TestQueryProvider } from "./test-query-provider";

const noop = () => undefined;
const realFetch = globalThis.fetch;
const realMatchMedia = window.matchMedia;

const user = { id: "user_1", name: "Ada Lovelace", email: "ada@example.com" };

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const settle = () => act(() => sleep(10));

function stubMatchMedia(matching: Record<string, boolean>): void {
window.matchMedia = ((media: string) =>
({
media,
matches: matching[media] ?? false,
addEventListener: noop,
removeEventListener: noop,
}) as unknown as MediaQueryList) as typeof window.matchMedia;
}

const emptyMemberships = () =>
new Response(JSON.stringify({ data: [], nextCursor: null }), {
status: 200,
headers: { "content-type": "application/json" },
});

// A pending provisioning status is exactly the shape that used to flip the
// note visible once the legacy session flag was set.
const pendingProvisioningStatus = () =>
new Response(
JSON.stringify({ kind: "provisioning", setupAgentReady: true }),
{ status: 200, headers: { "content-type": "application/json" } },
);

describe("app shell no longer shows the background setup note", () => {
let container: HTMLDivElement;
let root: Root;

beforeEach(() => {
stubMatchMedia({});
sessionStorage.setItem("workbench.setup-in-progress", "1");
globalThis.fetch = ((input: RequestInfo | URL) => {
const url = typeof input === "string" ? input : String(input);
return Promise.resolve(
url.includes("provisioning-status")
? pendingProvisioningStatus()
: emptyMemberships(),
);
}) as typeof fetch;
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
});

afterEach(() => {
act(() => root.unmount());
container.remove();
globalThis.fetch = realFetch;
window.matchMedia = realMatchMedia;
sessionStorage.clear();
});

test("renders no setup note text or markup, even with the legacy flag set", async () => {
await act(async () => {
root.render(
<TestQueryProvider>
<NavigationProvider navigate={noop}>
<BenchProvider>
<ProviderHealthProvider>
<ShellChromeProvider path="/inbox" navigate={noop}>
<AppShell path="/inbox" user={user} onSignOut={noop}>
{"Inbox"}
</AppShell>
</ShellChromeProvider>
</ProviderHealthProvider>
</BenchProvider>
</NavigationProvider>
</TestQueryProvider>,
);
});
await settle();
await settle();

expect(container.textContent).not.toContain("still setting up");
expect(container.querySelector(".setup-progress-note")).toBeNull();
});
});
46 changes: 46 additions & 0 deletions apps/web/test/new-workbench-picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { afterEach, describe, expect, test } from "bun:test";
import {
CODE_REVIEW_TEMPLATE,
DUE_DILIGENCE_TEMPLATE,
serializeWorkbenchTemplateManifest,
} from "@corbits/workflow-catalog";
import { act } from "react";
Expand Down Expand Up @@ -144,6 +145,51 @@ describe("NewWorkbenchPickerRoute", () => {
expect(container?.textContent).toContain("More kinds soon");
});

// The library seeds every shipped template (`createTemplateLibrarySeeder`),
// so a bench whose library serves due-diligence too offers it as a real
// row, not just an entry in the static row catalog with nothing to back it.
test("due-diligence is offered as a selectable row once the library serves it", async () => {
globalThis.fetch = ((input: RequestInfo | URL) => {
const path = typeof input === "string" ? input : String(input);
if (path.includes("/api/me/principals")) {
return Promise.resolve(json(MEMBERSHIP));
}
if (path.endsWith("/library/templates")) {
return Promise.resolve(
json({
data: [
{
id: "code-review",
content:
serializeWorkbenchTemplateManifest(CODE_REVIEW_TEMPLATE),
},
{
id: "due-diligence",
content: serializeWorkbenchTemplateManifest(
DUE_DILIGENCE_TEMPLATE,
),
},
],
}),
);
}
throw new Error(`unexpected fetch: ${path}`);
}) as typeof fetch;
await renderPicker();

const radios = Array.from(
container?.querySelectorAll('[role="radio"]') ?? [],
);
expect(radios.length).toBe(3);
const dueDiligence = radios.find((row) =>
row.textContent?.includes("Research & due diligence"),
);
expect(dueDiligence).not.toBeUndefined();
expect(dueDiligence?.textContent).toContain(
"Scout researches the web and what your team already knows",
);
});

// CL-6458: the picker offers what the bench's library can actually
// serve. A row the library has no manifest for is shown as not set up
// — never offered and then dead-ended on a 404 at create time.
Expand Down
Loading
Loading