From 79797cd450d38f663f54ea88ade9e72bd0a7a897 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Wed, 27 May 2026 23:11:58 -0300 Subject: [PATCH] =?UTF-8?q?refactor(dashboard,acp):=20rename=20/dashboard/?= =?UTF-8?q?agents=20=E2=86=92=20/dashboard/acp-agents=20+=20use=20shared?= =?UTF-8?q?=20concept/comparison=20cards=20(plan=2014=20F7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - git mv agents/ → acp-agents/ (preserves history) - useTranslations("agents") → useTranslations("acpAgents") - Replace inline architecture/comparison cards with + - Update all cross-links from /dashboard/cli-tools → /dashboard/cli-code - Update cliToolsRedirectCta/openCliTools keys → cliCodeRedirectCta - Update sidebarVisibility: id "agents" → "acp-agents", href "/dashboard/agents" → "/dashboard/acp-agents", i18nKey "agents" → "acpAgents" - Update HIDEABLE_SIDEBAR_ITEM_IDS and DEVELOPER_SHOWN preset: "agents" → "acp-agents" - Add tests/unit/ui/AcpAgentsPage.test.tsx (6 vitest tests: smoke, namespace, concept card, comparison card, cross-link, agent grid) --- .../dashboard/{agents => acp-agents}/page.tsx | 177 +--------------- src/shared/constants/sidebarVisibility.ts | 12 +- tests/unit/ui/AcpAgentsPage.test.tsx | 195 ++++++++++++++++++ 3 files changed, 207 insertions(+), 177 deletions(-) rename src/app/(dashboard)/dashboard/{agents => acp-agents}/page.tsx (61%) create mode 100644 tests/unit/ui/AcpAgentsPage.test.tsx diff --git a/src/app/(dashboard)/dashboard/agents/page.tsx b/src/app/(dashboard)/dashboard/acp-agents/page.tsx similarity index 61% rename from src/app/(dashboard)/dashboard/agents/page.tsx rename to src/app/(dashboard)/dashboard/acp-agents/page.tsx index ac1c417b15..79d4d90f34 100644 --- a/src/app/(dashboard)/dashboard/agents/page.tsx +++ b/src/app/(dashboard)/dashboard/acp-agents/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; import { Card, Button, Input } from "@/shared/components"; import ProviderIcon from "@/shared/components/ProviderIcon"; +import { CliConceptCard, CliComparisonCard } from "@/shared/components/cli"; import { useTranslations } from "next-intl"; interface AgentInfo { @@ -65,7 +66,7 @@ export default function AgentsPage() { versionCommand: "", spawnArgs: "", }); - const t = useTranslations("agents"); + const t = useTranslations("acpAgents"); const fetchAgents = useCallback(async () => { try { @@ -160,174 +161,8 @@ export default function AgentsPage() { - -
-
-
-

{t("architectureTitle")}

-

{t("architectureDescription")}

-
- - open_in_new - {t("cliToolsRedirectCta")} - -
-
- - {t("flowOmniRoute")} - - - arrow_forward - - - {t("flowSpawn")} - - - arrow_forward - - - {t("flowLocalBinary")} - - - arrow_forward - - - {t("flowExecute")} - -
-
-
-
-
- - devices - -
-

{t("flowDiagramClient")}

-

{t("flowDiagramClientDesc")}

-
-
- - arrow_forward - -
-
-
- hub -
-

{t("flowDiagramOmniRoute")}

-

- {t("flowDiagramOmniRouteDesc")} -

-
-
- - arrow_forward - -
-
-
- - launch - -
-

- {t("flowDiagramSpawn")} -

-

{t("flowDiagramSpawnDesc")}

-
-
- - arrow_forward - -
-
-
- - terminal - -
-

- {t("flowDiagramCli")} -

-

{t("flowDiagramCliDesc")}

-
-
-
-
- {t("cliToolsRedirectTitle")}{" "} - {t("cliToolsRedirectDesc")}{" "} - - {t("openCliTools")} - - . -
-
-
- - -
-
-
- -
-

{t("comparisonTitle")}

-
- -
-
-
- - arrow_forward - -

- {t("comparisonCliToolsLabel")} -

-
-

- {t("comparisonCliToolsTitle")} -

-

{t("comparisonCliToolsDesc")}

-
- IDE - arrow_forward - OmniRoute - arrow_forward - Provider API -
-
- -
-
- - arrow_back - -

- {t("comparisonAgentsLabel")} -

-
-

- {t("comparisonAgentsTitle")} -

-

{t("comparisonAgentsDesc")}

-
- Client - arrow_forward - OmniRoute - arrow_forward - CLI Binary -
-
-
- -

{t("comparisonSummary")}

-
-
+ + {/* Summary Cards */} {summary && ( @@ -363,10 +198,10 @@ export default function AgentsPage() {

{t("setupGuideTitle")}

- {t("openCliTools")} + {t("cliCodeRedirectCta")}
diff --git a/src/shared/constants/sidebarVisibility.ts b/src/shared/constants/sidebarVisibility.ts index 251bd0d1d6..f6cf0eb4dc 100644 --- a/src/shared/constants/sidebarVisibility.ts +++ b/src/shared/constants/sidebarVisibility.ts @@ -14,7 +14,7 @@ export const HIDEABLE_SIDEBAR_ITEM_IDS = [ "context-combos", // OmniProxy > Tools "cli-tools", - "agents", + "acp-agents", "cloud-agents", // OmniProxy > Integrations "api-endpoints", @@ -236,10 +236,10 @@ const TOOLS_GROUP: SidebarItemGroup = { icon: "terminal", }, { - id: "agents", - href: "/dashboard/agents", - i18nKey: "agents", - subtitleKey: "agentsSubtitle", + id: "acp-agents", + href: "/dashboard/acp-agents", + i18nKey: "acpAgents", + subtitleKey: "acpAgentsSubtitle", icon: "smart_toy", }, { @@ -802,7 +802,7 @@ const DEVELOPER_SHOWN: ReadonlySet = new Set([ "context-rtk", "context-combos", "cli-tools", - "agents", + "acp-agents", "api-endpoints", "analytics", "analytics-combo-health", diff --git a/tests/unit/ui/AcpAgentsPage.test.tsx b/tests/unit/ui/AcpAgentsPage.test.tsx new file mode 100644 index 0000000000..c823701f0c --- /dev/null +++ b/tests/unit/ui/AcpAgentsPage.test.tsx @@ -0,0 +1,195 @@ +// @vitest-environment jsdom +import React from "react"; +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +// ── Mocks ───────────────────────────────────────────────────────────────────── + +let capturedTranslationsNamespace = ""; + +vi.mock("next/link", () => ({ + default: ({ + href, + children, + ...props + }: React.AnchorHTMLAttributes & { href: string }) => ( + + {children} + + ), +})); + +vi.mock("next-intl", () => ({ + useTranslations: (ns: string) => { + capturedTranslationsNamespace = ns; + return (key: string) => key; + }, +})); + +vi.mock("@/shared/components", () => ({ + Card: ({ children, ...props }: React.HTMLAttributes) => ( +
+ {children} +
+ ), + Button: ({ + children, + onClick, + loading, + ...props + }: React.ButtonHTMLAttributes & { loading?: boolean }) => ( + + ), + Input: ({ + label, + ...props + }: React.InputHTMLAttributes & { label?: string }) => ( + + ), +})); + +vi.mock("@/shared/components/ProviderIcon", () => ({ + default: ({ providerId }: { providerId: string; size?: number; type?: string }) => ( + + ), +})); + +vi.mock("@/shared/components/cli", () => ({ + CliConceptCard: ({ currentType }: { currentType: string }) => ( +
+ ), + CliComparisonCard: ({ currentType }: { currentType: string }) => ( +
+ ), +})); + +// ── Fetch mock ──────────────────────────────────────────────────────────────── + +const mockAgents = [ + { + id: "claude-code", + name: "Claude Code", + binary: "claude", + version: "1.2.3", + installed: true, + protocol: "stdio", + isCustom: false, + }, + { + id: "codex", + name: "Codex", + binary: "codex", + version: null, + installed: false, + protocol: "stdio", + isCustom: false, + }, +]; + +const mockSummary = { + total: 2, + installed: 1, + notFound: 1, + builtIn: 2, + custom: 0, +}; + +const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ agents: mockAgents, summary: mockSummary }), +}); + +(globalThis as typeof globalThis & { fetch: typeof fetch }).fetch = mockFetch; + +// ── Import after mocks ──────────────────────────────────────────────────────── + +const { default: AcpAgentsPage } = await import( + "@/app/(dashboard)/dashboard/acp-agents/page" +); + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +const containers: HTMLElement[] = []; + +async function renderPage(): Promise { + const container = document.createElement("div"); + document.body.appendChild(container); + containers.push(container); + + const root = createRoot(container); + await act(async () => { + root.render(); + }); + // Allow data-fetching effects to resolve + await act(async () => { + await new Promise((r) => setTimeout(r, 0)); + }); + return container; +} + +// ── Lifecycle ───────────────────────────────────────────────────────────────── + +beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + capturedTranslationsNamespace = ""; + mockFetch.mockClear(); +}); + +afterEach(() => { + while (containers.length > 0) { + containers.pop()?.remove(); + } + document.body.innerHTML = ""; +}); + +// ── Tests ───────────────────────────────────────────────────────────────────── + +describe("AcpAgentsPage", () => { + it("smoke: renders without crashing", async () => { + const container = await renderPage(); + expect(container).toBeTruthy(); + expect(container.innerHTML.length).toBeGreaterThan(0); + }); + + it("calls useTranslations with 'acpAgents' namespace", async () => { + await renderPage(); + expect(capturedTranslationsNamespace).toBe("acpAgents"); + }); + + it("renders ", async () => { + const container = await renderPage(); + const card = container.querySelector("[data-testid='cli-concept-card']"); + expect(card).not.toBeNull(); + expect(card?.getAttribute("data-current-type")).toBe("acp"); + }); + + it("renders ", async () => { + const container = await renderPage(); + const card = container.querySelector("[data-testid='cli-comparison-card']"); + expect(card).not.toBeNull(); + expect(card?.getAttribute("data-current-type")).toBe("acp"); + }); + + it("cross-link points to /dashboard/cli-code (not /dashboard/cli-tools)", async () => { + const container = await renderPage(); + const links = container.querySelectorAll("a"); + const hrefs = Array.from(links).map((a) => a.getAttribute("href")); + const cliCodeLinks = hrefs.filter((h) => h === "/dashboard/cli-code"); + const cliToolsLinks = hrefs.filter((h) => h === "/dashboard/cli-tools"); + expect(cliCodeLinks.length).toBeGreaterThan(0); + expect(cliToolsLinks).toHaveLength(0); + }); + + it("agent grid renders with mocked /api/acp/agents response", async () => { + const container = await renderPage(); + expect(mockFetch).toHaveBeenCalledWith("/api/acp/agents"); + // Agent names from mock should appear somewhere in the rendered output + expect(container.textContent).toContain("Claude Code"); + expect(container.textContent).toContain("Codex"); + }); +});