From 40a2ffff35c08cbc9b10f630b7eff8ca306f5b9e Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 17 Sep 2026 18:05:01 -0700 Subject: [PATCH] feat(web,hub)!: rename plugins to tools across UI and backend --- DESIGN.md | 10 +- PRODUCT.md | 2 +- agents/myra/src/system-prompt.ts | 2 +- apps/web/src/app.css | 4 +- apps/web/src/chat/strings.ts | 8 +- apps/web/src/chat/styles.css | 132 ----------------- apps/web/src/chat/timeline.test.ts | 4 +- apps/web/src/chat/timeline.tsx | 6 +- .../src/chat/workbench-settings/model.test.ts | 10 +- apps/web/src/chat/workbench-settings/model.ts | 10 +- .../src/command-palette/detail-paths.test.ts | 2 +- apps/web/src/lib/icons/index.tsx | 1 - apps/web/src/pages/tools-page.tsx | 136 ++++++++++++++++++ apps/web/src/path-ids.ts | 2 +- apps/web/src/routes.tsx | 11 ++ apps/web/src/settings/styles.css | 4 +- apps/web/src/tailwind.css | 1 - apps/web/test/chat/system-row-chrome.test.tsx | 10 +- .../chat/workbench-settings-retry.test.tsx | 4 +- apps/web/test/routes.test.tsx | 4 +- apps/web/test/sidebar.test.tsx | 21 +-- .../test/stage-chrome-consistency.test.tsx | 2 +- apps/web/test/stage-top-nav.test.tsx | 4 +- 23 files changed, 195 insertions(+), 195 deletions(-) create mode 100644 apps/web/src/pages/tools-page.tsx diff --git a/DESIGN.md b/DESIGN.md index 74da00fe63..0e2cca1361 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -34,11 +34,11 @@ Top to bottom: conversations, not product sections. 3. **Footer rail** — Mission Control is pinned above the rail as its own row. The first-run rail below it is Routines, Files, Skills, Agents, - Plugins, in that order. Insights and Evals join that rail only when + Tools, in that order. Insights and Evals join that rail only when existing reads prove real usage (turns > 0 / at least one eval run). These are utility destinations, not workbenches, and each is its own top-level route (`/mission-control`, `/routines`, `/files`, `/skills`, - `/agents`, `/plugins`; plus `/insights`, `/evals` when reached). + `/agents`, `/tools`; plus `/insights`, `/evals` when reached). 4. **Account row** — avatar and name, anchoring the rail, plus a separate settings icon beside it. The avatar+name half is a menu trigger (weekly usage, feedback, log out) that pops upward; the gear is a @@ -74,7 +74,7 @@ not home. The primary act is a prompt: say what the channel should do, or pick a named-template shortcut underneath. Blank `+` / prompt mint an empty channel and invite nobody. Named templates mint that same empty channel with no host, then instantiate the picked Workbench -Definition — the agents, block workflows, and pending plugins it names +Definition — the agents, block workflows, and pending tools it names (Myra joins only when the definition names her; Code review's three reviewers do not) — and run its ordered onboarding walkthrough as an in-room card the room itself posts, never a side effect of hosting an @@ -107,7 +107,7 @@ example (`ConnectorRow` / `OAuthConnectorRow`, Density over cards: one row per item, a small logo tile, name, a single-line outcome sentence, a status/provenance caption, and one honest action button that reflects the item's actual state. A browseable catalog -of installable things, each with its own status and actions — the plugins +of installable things, each with its own status and actions — the tools gallery is the standing example — is the one case where a two-column card catalog with status/category chip filters is the right idiom instead. Extend an existing idiom to a new directory before inventing a fourth @@ -116,7 +116,7 @@ pattern; three ways to list things is a defect, not a design system. ## Detail Pages Anything with enough content to browse gets a full page, not a panel: -`/agents/`, `/skills/`, `/plugins/`, `/routines/`, +`/agents/`, `/skills/`, `/tools/`, `/routines/`, `/files/`, `/evals/`. Slugs are immutable once assigned and tenant-unique, enforced as a hard diff --git a/PRODUCT.md b/PRODUCT.md index 84e789a2e9..83840d082c 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -22,7 +22,7 @@ existing teammate's capability before duplicating it. ## MCP -Myra can also reach any external MCP server: connect one in Plugins and +Myra can also reach any external MCP server: connect one in Tools and its tools become callable the same way a native `@corbits/*` tool is, with no separate integration per server. diff --git a/agents/myra/src/system-prompt.ts b/agents/myra/src/system-prompt.ts index 1afcbb6530..f0a044b64d 100644 --- a/agents/myra/src/system-prompt.ts +++ b/agents/myra/src/system-prompt.ts @@ -224,7 +224,7 @@ export const ASSISTANT_SYSTEM_PROMPT = "when you actually need a fact from earlier; never fabricate a " + "recollection when a search comes back empty, and if memory isn't " + "set up on this deployment, proceed without mentioning it. Any MCP " + - "server connected under Plugins is " + + "server connected under Tools is " + "reachable with mcp_list_servers, mcp_list_tools, mcp_read, and " + "mcp_call — discover once with mcp_list_tools (pattern search when " + "unsure which server has the tool you want); use mcp_read for " + diff --git a/apps/web/src/app.css b/apps/web/src/app.css index be5470c235..ff26cf6935 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -1140,7 +1140,7 @@ select:disabled, } /* Mission Control — pinned above the footer rail as its own row (DESIGN.md), - never a 7th button mixed into Routines/Files/Skills/Agents/Plugins/ + never a 7th button mixed into Routines/Files/Skills/Agents/Tools/ Insights below. The border-bottom is what reads it as a separate band rather than the first row of that rail. */ .shell-sidebar-mission-control { @@ -1180,7 +1180,7 @@ select:disabled, outline-offset: -1px; } -/* Footer (reference shape): a Plugins row, then the account row that +/* Footer (reference shape): a Tools row, then the account row that anchors the pop-up menu. Both are full-width quiet rows; the account row has no top margin of its own so the two never read as separate stacked bands. */ diff --git a/apps/web/src/chat/strings.ts b/apps/web/src/chat/strings.ts index ac2460c889..638df0541f 100644 --- a/apps/web/src/chat/strings.ts +++ b/apps/web/src/chat/strings.ts @@ -96,12 +96,12 @@ export const CHAT_STRINGS = { eventBlockResponsePoll: "A vote was recorded", eventBlockResponseForm: "A form was submitted", /** Plain-text form of the settle notice. EventLine renders - * the same copy with "Plugins" as a `/plugins` link. */ + * the same copy with "Tools" as a `/tools` link. */ eventConnectionConnected: (displayName: string): string => - `${displayName} connected successfully. Manage in Plugins`, - eventConnectionConnectedBeforePlugins: (displayName: string): string => + `${displayName} connected successfully. Manage in Tools`, + eventConnectionConnectedBeforeTools: (displayName: string): string => `${displayName} connected successfully. Manage in `, - eventConnectionConnectedPlugins: "Plugins", + eventConnectionConnectedTools: "Tools", eventGeneric: (event: string) => event.replace(/[.\-_]+/g, " "), inviteAgentAction: "Invite agent", diff --git a/apps/web/src/chat/styles.css b/apps/web/src/chat/styles.css index f758f6e7cb..58fc19d7ad 100644 --- a/apps/web/src/chat/styles.css +++ b/apps/web/src/chat/styles.css @@ -549,138 +549,6 @@ color: var(--muted-foreground); } -/* Plugins directory: a marketplace-style list, not a grid - of grey cards — one row per connector, search on top, Active before - Available. */ -.plugins-directory-toolbar { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.plugins-directory-search { - max-width: 22rem; - flex: 1; -} - -.plugins-directory-group { - display: flex; - flex-direction: column; - gap: 0.375rem; -} - -.plugins-directory-group-label { - font-size: 0.65rem; - font-weight: 650; - letter-spacing: 0.07em; - text-transform: uppercase; - color: var(--muted-foreground); -} - -.plugins-directory-list { - display: flex; - flex-direction: column; -} - -.plugins-directory-row { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.6rem 0.125rem; - border-bottom: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); -} - -.plugins-directory-list .plugins-directory-row:last-child { - border-bottom: 0; -} - -.plugins-directory-logo { - display: inline-flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); - border-radius: 0; - background: var(--card, var(--background)); -} - -.plugins-directory-logo-initial { - font-size: 0.8125rem; - font-weight: 650; - color: var(--muted-foreground); -} - -.plugins-directory-text { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 0.1rem; -} - -.plugins-directory-name-row { - display: flex; - align-items: baseline; - gap: 0.5rem; -} - -.plugins-directory-name { - font-size: 0.875rem; - font-weight: 640; -} - -.plugins-directory-description { - margin: 0; - font-size: 0.8125rem; - color: var(--muted-foreground); -} - -/* Needs-attention is the one colored state on this page — every other - status (ownership included) reads as plain caption text. */ -.plugins-directory-needs-attention { - font-size: 0.65rem; - font-weight: 650; - letter-spacing: 0.07em; - text-transform: uppercase; - color: var(--shell-accent, var(--primary)); -} - -.plugins-directory-ownership { - font-size: 0.65rem; - font-weight: 650; - letter-spacing: 0.07em; - text-transform: uppercase; - color: var(--muted-foreground); -} - -.plugins-directory-action { - flex-shrink: 0; -} - -/* Connect/Override read as quiet accent-text actions, not filled buttons — - `--shell-accent` only ever appears as text or a 2px inset edge, never a - filled rectangle. */ -.plugins-directory-connect-action { - color: var(--shell-accent, var(--primary)); - font-weight: 650; -} - -/* Remove keeps `ConfirmButton`'s confirm-step behavior but never the red - fill it renders once armed — overriding the literal Tailwind utility - class it emits for that state, since that state is not otherwise - reachable from a variant prop. */ -.plugins-directory-remove-action { - color: var(--muted-foreground); - font-weight: 600; -} - -.plugins-directory-remove-action.bg-destructive { - background: transparent; - color: var(--destructive); -} - .chat-settings-choice-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); diff --git a/apps/web/src/chat/timeline.test.ts b/apps/web/src/chat/timeline.test.ts index 1c20b8dff3..973c3fe4c4 100644 --- a/apps/web/src/chat/timeline.test.ts +++ b/apps/web/src/chat/timeline.test.ts @@ -55,12 +55,12 @@ describe("friendlyEventText — workbench.agent-joined", () => { }); describe("friendlyEventText — connection.connected", () => { - test("names the connected service and points at Plugins", () => { + test("names the connected service and points at Tools", () => { const part: Part & { kind: "event" } = { kind: "event", event: "connection.connected", data: { connectorId: "github", displayName: "GitHub" }, }; - expect(friendlyEventText(part, [])).toBe("GitHub connected successfully. Manage in Plugins"); + expect(friendlyEventText(part, [])).toBe("GitHub connected successfully. Manage in Tools"); }); }); diff --git a/apps/web/src/chat/timeline.tsx b/apps/web/src/chat/timeline.tsx index b0c5c99b4f..9b726011aa 100644 --- a/apps/web/src/chat/timeline.tsx +++ b/apps/web/src/chat/timeline.tsx @@ -615,8 +615,8 @@ function EventLine({ collapsedText ) : connectedDisplayName !== undefined ? ( <> - {CHAT_STRINGS.eventConnectionConnectedBeforePlugins(connectedDisplayName)} - {CHAT_STRINGS.eventConnectionConnectedPlugins} + {CHAT_STRINGS.eventConnectionConnectedBeforeTools(connectedDisplayName)} + {CHAT_STRINGS.eventConnectionConnectedTools} ) : ( friendlyEventText(part, participants, agentDisplayNames) @@ -1357,7 +1357,7 @@ function MessagePartsInner({ * connection" action — undefined renders no affordance at * all, the same "no port, no feature" contract every other optional * action here follows. No chat-ui component owns routing: the host - * decides where "fix" goes (Plugins' connect panel today). */ + * decides where "fix" goes (Tools' connect panel today). */ readonly onFixConnection?: () => void; readonly approvalActions?: ApprovalActions; /** Host round-trip for the generic "connect-service" card. Undefined diff --git a/apps/web/src/chat/workbench-settings/model.test.ts b/apps/web/src/chat/workbench-settings/model.test.ts index ce0ffd274c..f7f08677c0 100644 --- a/apps/web/src/chat/workbench-settings/model.test.ts +++ b/apps/web/src/chat/workbench-settings/model.test.ts @@ -63,16 +63,16 @@ describe("workbenchSettingsSections", () => { ]); }); - test("Myra/Keys & plugins/Inference are gone as distinct nav ids", () => { + test("Myra/Keys & tools/Inference are gone as distinct nav ids", () => { const ids = workbenchSettingsSections("workbench").map((s) => s.id); expect(ids).not.toContain("assistant"); - expect(ids).not.toContain("keys-plugins"); + expect(ids).not.toContain("keys-tools"); expect(ids).not.toContain("inference"); }); - test("Plugins is global-only now — no workbench-scoped nav id", () => { - expect(workbenchSettingsSections("workbench").map((s) => s.id)).not.toContain("plugins"); - expect(workbenchSettingsSections("chat", true).map((s) => s.id)).not.toContain("plugins"); + test("Tools is global-only now — no workbench-scoped nav id", () => { + expect(workbenchSettingsSections("workbench").map((s) => s.id)).not.toContain("tools"); + expect(workbenchSettingsSections("chat", true).map((s) => s.id)).not.toContain("tools"); }); test("Capacity is gone — sidecar placement is Interchange's own concern now", () => { diff --git a/apps/web/src/chat/workbench-settings/model.ts b/apps/web/src/chat/workbench-settings/model.ts index 8c65802820..acd4d6dc30 100644 --- a/apps/web/src/chat/workbench-settings/model.ts +++ b/apps/web/src/chat/workbench-settings/model.ts @@ -5,15 +5,15 @@ // Agents is now the one place an agent participant's persona, // capabilities, and history are edited — a click-through master-detail, // not a separate "Myra" nav item duplicating the same editor for one -// hardcoded agent. Keys & plugins and Inference are gone as distinct +// hardcoded agent. Keys & tools and Inference are gone as distinct // sections: inference provider+model assignment moves onto the Agents // detail view, per agent, fed from the tenant-wide provider pool. // -// Plugins are global-only for now (owner ruling): the workbench-scoped -// `plugins` section that used to live here is removed — connect/manage -// plugins from the bench-level Plugins page instead. Per-workbench +// Tools are global-only for now (owner ruling): the workbench-scoped +// `tools` section that used to live here is removed — connect/manage +// tools from the bench-level Tools page instead. Per-workbench // credential rows written by that old section are left in place (nothing -// reads or writes them now); see the Plugins page for the surviving +// reads or writes them now); see the Tools page for the surviving // surface. import { CHAT_STRINGS } from "../strings"; diff --git a/apps/web/src/command-palette/detail-paths.test.ts b/apps/web/src/command-palette/detail-paths.test.ts index d4018a03a9..8b40cce941 100644 --- a/apps/web/src/command-palette/detail-paths.test.ts +++ b/apps/web/src/command-palette/detail-paths.test.ts @@ -15,6 +15,6 @@ describe("detailPath", () => { }); test("the id fallback survives a segment that needs escaping", () => { - expect(detailPath("/plugins", { slug: "Not A Slug", id: "a/b" })).toBe("/plugins/a%2Fb"); + expect(detailPath("/tools", { slug: "Not A Slug", id: "a/b" })).toBe("/tools/a%2Fb"); }); }); diff --git a/apps/web/src/lib/icons/index.tsx b/apps/web/src/lib/icons/index.tsx index 4512f67522..6153fa313f 100644 --- a/apps/web/src/lib/icons/index.tsx +++ b/apps/web/src/lib/icons/index.tsx @@ -64,7 +64,6 @@ export { PlayCircle, Plus, Plugs, - PuzzlePiece, PushPin, PushPinSlash, Repeat, diff --git a/apps/web/src/pages/tools-page.tsx b/apps/web/src/pages/tools-page.tsx new file mode 100644 index 0000000000..97bedccead --- /dev/null +++ b/apps/web/src/pages/tools-page.tsx @@ -0,0 +1,136 @@ +// Tools: a standalone rail destination listing what this tenant can call — +// Interchange tool packages, read from the same capability inventory the +// Agents detail view's "Add a capability" picker already uses +// (`listCapabilityInventory`, `packages/agent-directory`'s package-registry +// read). There is no connect flow here: pinning a tool package to an agent +// happens on that agent's own detail page. No stock route lists a tenant's +// MCP servers yet, so this page has nothing to show for those until one +// exists. + +import { + PageShell, + RichEmptyState, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@corbits/react-ui"; +import { Plugs } from "@/lib/icons"; +import { WorkbenchLoadingState } from "@/chat"; +import { useCallback, useEffect, useState } from "react"; + +import { listCapabilityInventory, type CapabilityInventory } from "../chat/api"; +import { useBench } from "../bench-context"; +import { StageTopBar } from "../shell/stage-top-bar"; + +type ToolsState = + | { readonly status: "loading" } + | { readonly status: "ready"; readonly inventory: CapabilityInventory } + | { readonly status: "error"; readonly message: string }; + +function messageOf(cause: unknown): string { + return cause instanceof Error ? cause.message : String(cause); +} + +/** + * The tenant's installed tool packages, over the same capability inventory + * the Agents detail view reads. `tenantId` is the tenant every read is + * scoped to. + */ +export function ToolsPage({ tenantId }: { readonly tenantId: string | null }) { + const [state, setState] = useState({ status: "loading" }); + + const reload = useCallback(async () => { + if (tenantId === null) return; + setState({ status: "loading" }); + try { + const inventory = await listCapabilityInventory(tenantId); + setState({ status: "ready", inventory }); + } catch (cause) { + setState({ status: "error", message: messageOf(cause) }); + } + }, [tenantId]); + + useEffect(() => { + void reload(); + }, [reload]); + + const crumbs = [{ label: "Tools" }]; + + function stage(body: React.ReactNode) { + return ( +
+ +
+ + {body} + +
+
+ ); + } + + if (tenantId === null) { + return stage( +

Pick a workbench to see its tools.

, + ); + } + + if (state.status === "loading") { + return stage(); + } + + if (state.status === "error") { + return stage( + } + title="Couldn't load your tools" + description="Something went wrong on our side. Try again in a moment." + actions={[{ label: "Retry", onClick: () => void reload() }]} + />, + ); + } + + const { toolPackages } = state.inventory; + + if (toolPackages.length === 0) { + return stage( + } + title="No tools yet" + description="A tool package gives every agent in this workbench a new capability. Install one to see it here." + />, + ); + } + + return stage( +
+ + + + Tool package + + + + {toolPackages.map((tool) => ( + + {tool.name} + + ))} + +
+
, + ); +} + +/** + * Tools roster mount at `/tools`: a thin adapter that resolves which + * workbench's inventory is listed. The stage chrome lives on `ToolsPage`. + */ +export function ToolsRoute() { + const { selectedTenantId } = useBench(); + + return ; +} diff --git a/apps/web/src/path-ids.ts b/apps/web/src/path-ids.ts index add732a41a..ff952d6e16 100644 --- a/apps/web/src/path-ids.ts +++ b/apps/web/src/path-ids.ts @@ -14,7 +14,7 @@ export const SETTINGS_PATH_PREFIX = "/settings"; export const AGENTS_PATH_PREFIX = "/agents"; export const SKILLS_PATH_PREFIX = "/skills"; export const FILES_PATH_PREFIX = "/files"; -export const PLUGINS_PATH_PREFIX = "/plugins"; +export const TOOLS_PATH_PREFIX = "/tools"; export const ROUTINES_PATH_PREFIX = "/routines"; export const WORKFLOWS_PATH_PREFIX = "/workflows"; export const INSIGHTS_PATH_PREFIX = "/insights"; diff --git a/apps/web/src/routes.tsx b/apps/web/src/routes.tsx index 53fa3b3550..6ad1e95643 100644 --- a/apps/web/src/routes.tsx +++ b/apps/web/src/routes.tsx @@ -23,6 +23,7 @@ import { FlowArrow, FolderOpen, Lightning, + Plugs, Robot, SlidersHorizontal, SquaresFour, @@ -75,6 +76,9 @@ const AgentsRoute = lazy(async () => ({ const SkillsRoute = lazy(async () => ({ default: (await import("./pages/skills-page")).SkillsRoute, })); +const ToolsRoute = lazy(async () => ({ + default: (await import("./pages/tools-page")).ToolsRoute, +})); const InsightsRoute = lazy(async () => ({ default: (await import("./pages/insights-page")).InsightsRoute, })); @@ -369,6 +373,12 @@ export const APP_ROUTES: readonly AppRoute[] = [ ), }, + { + path: "/tools", + label: "Tools", + icon: , + render: () => , + }, { path: "/insights", label: "Insights", @@ -405,6 +415,7 @@ export const NAV_ROUTES: readonly AppRoute[] = routesInOrder([ "/files", "/skills", "/agents", + "/tools", "/insights", SETTINGS_PATH, ]); diff --git a/apps/web/src/settings/styles.css b/apps/web/src/settings/styles.css index c4a278f409..3dc65076cb 100644 --- a/apps/web/src/settings/styles.css +++ b/apps/web/src/settings/styles.css @@ -464,7 +464,7 @@ } } -/* Connections: rows over grid (the plugins-directory +/* Connections: rows over grid (the tools directory rebuild is the reference pattern) — one row per connector, not a grid of bordered cards. Same brand rules: color-mix hairline dividers, the accent color only ever as text (needs-attention) or an inset edge, @@ -576,7 +576,7 @@ /* Disconnect keeps `ConfirmButton`'s confirm-step behavior but never the red fill it renders once armed — overriding the literal Tailwind - utility class it emits for that state, the same override the plugins + utility class it emits for that state, the same override the tools directory's own remove action uses. */ .settings-connection-row-disconnect-action { color: var(--muted-foreground); diff --git a/apps/web/src/tailwind.css b/apps/web/src/tailwind.css index a83a5d8ebe..7bef695c89 100644 --- a/apps/web/src/tailwind.css +++ b/apps/web/src/tailwind.css @@ -44,5 +44,4 @@ @source "../../../packages/bench-ui/src"; @source "../../../packages/chat-ui/src"; @source "../../../packages/context-menu/src"; -@source "../../../packages/plugins-ui/src"; @source "../../../packages/settings-ui/src"; diff --git a/apps/web/test/chat/system-row-chrome.test.tsx b/apps/web/test/chat/system-row-chrome.test.tsx index e1f8446f5e..fb71204869 100644 --- a/apps/web/test/chat/system-row-chrome.test.tsx +++ b/apps/web/test/chat/system-row-chrome.test.tsx @@ -69,7 +69,7 @@ describe("system / error / connect rows hide social chrome", () => { expectNoSocialChrome(el); }); - test("connection.connected settle notice links Plugins to /plugins", async () => { + test("connection.connected settle notice links Tools to /tools", async () => { const el = await mount([ { id: "settle_1", @@ -87,10 +87,10 @@ describe("system / error / connect rows hide social chrome", () => { const line = el.querySelector(".chat-event-line"); expect(line).not.toBeNull(); - expect(line?.textContent).toContain("GitHub connected successfully. Manage in Plugins"); - const pluginsLink = line?.querySelector('a[href="/plugins"]'); - expect(pluginsLink).not.toBeNull(); - expect(pluginsLink?.textContent).toBe("Plugins"); + expect(line?.textContent).toContain("GitHub connected successfully. Manage in Tools"); + const toolsLink = line?.querySelector('a[href="/tools"]'); + expect(toolsLink).not.toBeNull(); + expect(toolsLink?.textContent).toBe("Tools"); expectNoSocialChrome(el); }); diff --git a/apps/web/test/chat/workbench-settings-retry.test.tsx b/apps/web/test/chat/workbench-settings-retry.test.tsx index be08e52ba4..64babe0807 100644 --- a/apps/web/test/chat/workbench-settings-retry.test.tsx +++ b/apps/web/test/chat/workbench-settings-retry.test.tsx @@ -2,8 +2,8 @@ // onto `@/lib/api-query`'s `APIQuery` + `QueryView` — a failed settings // load now offers the shared Retry affordance, and a 401 renders as // sign-in-required rather than the generic error copy. This stands in for -// both this package's LoadState consumers (surface.tsx and -// plugins-section.tsx share the same fetch-and-render pattern). +// both this package's LoadState consumers (surface.tsx and the +// Tools page share the same fetch-and-render pattern). import { afterEach, describe, expect, test } from "bun:test"; import { act, createElement } from "react"; diff --git a/apps/web/test/routes.test.tsx b/apps/web/test/routes.test.tsx index 4648063db9..c5b6ceb776 100644 --- a/apps/web/test/routes.test.tsx +++ b/apps/web/test/routes.test.tsx @@ -150,17 +150,19 @@ describe("route table", () => { "/skills/:slug", "/skills", "/settings/skills", + "/tools", "/insights", "/settings", ]); }); - test("palette pages are Routines, Files, Skills, Agents, Insights, Settings", () => { + test("palette pages are Routines, Files, Skills, Agents, Tools, Insights, Settings", () => { expect(NAV_ROUTES.map((route) => route.label)).toEqual([ "Routines", "Files", "Skills", "Agents", + "Tools", "Insights", "Settings", ]); diff --git a/apps/web/test/sidebar.test.tsx b/apps/web/test/sidebar.test.tsx index e55807a11a..2df93c5078 100644 --- a/apps/web/test/sidebar.test.tsx +++ b/apps/web/test/sidebar.test.tsx @@ -183,12 +183,7 @@ describe("Sidebar", () => { test("first-run footer rail is Routines, Files, Skills, Agents, then the account row — no Insights or Inbox", () => { const markup = renderSidebar("/w"); - expect(footerRowLabelsFromMarkup(markup)).toEqual([ - "Routines", - "Files", - "Skills", - "Agents", - ]); + expect(footerRowLabelsFromMarkup(markup)).toEqual(["Routines", "Files", "Skills", "Agents"]); expect(markup).toContain("data-ctx-account"); expect(markup).not.toContain(">Inbox<"); expect(markup).not.toContain('aria-label="Notifications"'); @@ -207,12 +202,7 @@ describe("Sidebar", () => { test("first-run footer rail does not list Insights before there are honest runs", async () => { stubFetch({ runCount: 0 }); const { container, root } = await mountSidebar("/w"); - expect(footerRowLabelsFromDom(container)).toEqual([ - "Routines", - "Files", - "Skills", - "Agents", - ]); + expect(footerRowLabelsFromDom(container)).toEqual(["Routines", "Files", "Skills", "Agents"]); act(() => root.unmount()); container.remove(); }); @@ -254,12 +244,7 @@ describe("Sidebar", () => { test("a failed usage probe omits the row rather than claiming usage", async () => { stubFetch({ failRuns: true }); const { container, root } = await mountSidebar("/w"); - expect(footerRowLabelsFromDom(container)).toEqual([ - "Routines", - "Files", - "Skills", - "Agents", - ]); + expect(footerRowLabelsFromDom(container)).toEqual(["Routines", "Files", "Skills", "Agents"]); act(() => root.unmount()); container.remove(); }); diff --git a/apps/web/test/stage-chrome-consistency.test.tsx b/apps/web/test/stage-chrome-consistency.test.tsx index 0d124884f0..ded964eb6b 100644 --- a/apps/web/test/stage-chrome-consistency.test.tsx +++ b/apps/web/test/stage-chrome-consistency.test.tsx @@ -1,5 +1,5 @@ // /files, /skills, /agents must use the same stage chrome as the -// reference pages (Insights, Plugins) — the shared `StageTopBar` component +// reference pages (Insights, Tools) — the shared `StageTopBar` component // and `Table` row idiom, not bespoke divs standing in for either. This is // a screenshot-free assertion that each page's presentational component // renders those shared components rather than imitating them. diff --git a/apps/web/test/stage-top-nav.test.tsx b/apps/web/test/stage-top-nav.test.tsx index 7a49067da2..3eabc8a1d9 100644 --- a/apps/web/test/stage-top-nav.test.tsx +++ b/apps/web/test/stage-top-nav.test.tsx @@ -78,10 +78,10 @@ describe("stage breadcrumbs", () => { test("a single-level page still declares its title as a trail", () => { const markup = renderToStaticMarkup( - + , ); - expect(markup).toContain('aria-current="page">Plugins'); + expect(markup).toContain('aria-current="page">Tools'); expect(markup).not.toContain("