From 90fcf51d7622b0e6c413502c95791e5595206f76 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:56:28 +0200 Subject: [PATCH] Render the Antigravity icon for terminal profiles (#6938) * fix(app): render terminal-profile icons instead of the generic Bot * test(app): verify terminal profile artwork in the launch menu Exercise the rendered SVG, dimensions, theme color, catalog artwork and fallback through the real browser app instead of inspecting hook results in JSDOM. The browser regression fails on main before the lookup fix. --------- Co-authored-by: Noah Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../browser/terminal-profile-icons.spec.ts | 71 +++++++++++++++++++ packages/app/src/components/provider-icons.ts | 11 ++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 packages/app/e2e/browser/terminal-profile-icons.spec.ts diff --git a/packages/app/e2e/browser/terminal-profile-icons.spec.ts b/packages/app/e2e/browser/terminal-profile-icons.spec.ts new file mode 100644 index 0000000000..29d786cfd3 --- /dev/null +++ b/packages/app/e2e/browser/terminal-profile-icons.spec.ts @@ -0,0 +1,71 @@ +import { ACP_PROVIDER_ICON_SVGS } from "../../src/assets/acp-provider-icons"; +import { expect, test, type Page } from "../support/fixtures"; +import { gotoAppShell } from "../support/helpers/app"; +import { openNewWorkspaceComposer } from "../support/helpers/new-workspace"; +import { openLaunchMenu } from "../support/helpers/new-workspace-launch"; +import { seedWorkspace } from "../support/helpers/seed-client"; +import { waitForSidebarHydration } from "../support/helpers/workspace-ui"; + +// Harmless commands: this journey opens the profile menu without launching them. +test.use({ + e2eDaemonConfig: { + version: 1, + daemon: { + terminalProfiles: [ + { id: "antigravity", name: "Antigravity artwork", command: "/bin/sh", icon: "agy" }, + { id: "cursor", name: "Cursor artwork", command: "/bin/sh", icon: "cursor" }, + { id: "unknown", name: "Unknown artwork", command: "/bin/sh", icon: "unknown-icon" }, + ], + }, + }, +}); + +function profileIcon(page: Page, name: string) { + return page.getByRole("menuitem", { name: new RegExp(`^${name} artwork`) }).locator("svg"); +} + +async function expectBundledArtwork(page: Page, name: string, artwork: string) { + const icon = profileIcon(page, name); + await expect(icon).toBeVisible(); + const expectedPaths = await page.evaluate((xml) => { + const document = new DOMParser().parseFromString(xml, "image/svg+xml"); + return Array.from(document.querySelectorAll("path"), (path) => path.getAttribute("d")!); + }, artwork); + await expect(icon.locator("path")).toHaveCount(expectedPaths.length); + for (const [index, path] of expectedPaths.entries()) { + await expect(icon.locator("path").nth(index)).toHaveAttribute("d", path); + } + await expect(icon).toHaveAttribute("width", "14"); + await expect(icon).toHaveAttribute("height", "14"); + const terminalColor = await page + .getByRole("menuitem", { name: "Terminal", exact: true }) + .locator("svg") + .evaluate((element) => getComputedStyle(element).stroke); + await expect(icon.locator("path").first()).toHaveCSS("fill", terminalColor); +} + +async function expectUnknownArtworkFallback(page: Page) { + const icon = profileIcon(page, "Unknown"); + await expect(icon).toBeVisible(); + await expect(icon.locator('path[d="M12 8V4H8"]')).toHaveCount(1); +} + +test("terminal profiles show bundled artwork while unknown icons keep the Bot fallback", async ({ + page, +}) => { + const workspace = await seedWorkspace({ repoPrefix: "terminal-icons-" }); + try { + await gotoAppShell(page); + await waitForSidebarHydration(page); + await openNewWorkspaceComposer(page, { + projectKey: workspace.projectKey, + projectDisplayName: workspace.projectDisplayName, + }); + await openLaunchMenu(page); + await expectBundledArtwork(page, "Antigravity", ACP_PROVIDER_ICON_SVGS.agy); + await expectBundledArtwork(page, "Cursor", ACP_PROVIDER_ICON_SVGS.cursor); + await expectUnknownArtworkFallback(page); + } finally { + await workspace.cleanup(); + } +}); diff --git a/packages/app/src/components/provider-icons.ts b/packages/app/src/components/provider-icons.ts index fec4003705..883b4820f7 100644 --- a/packages/app/src/components/provider-icons.ts +++ b/packages/app/src/components/provider-icons.ts @@ -3,6 +3,7 @@ import { providerSnapshotIcons } from "@/data/provider-icons"; import { Bot, PackagePlus } from "lucide-react-native"; import { createElement, useCallback, type ComponentType } from "react"; import { SvgXml } from "react-native-svg"; +import { TERMINAL_PROFILE_ICON_NAMES } from "@getpaseo/protocol/provider-icon-names"; import { ClaudeIcon } from "@/components/icons/claude-icon"; import { CodexIcon } from "@/components/icons/codex-icon"; import { CopilotIcon } from "@/components/icons/copilot-icon"; @@ -10,6 +11,7 @@ import { MiniMaxIcon } from "@/components/icons/minimax-icon"; import { OpenCodeIcon } from "@/components/icons/opencode-icon"; import { OmpIcon } from "@/components/icons/omp-icon"; import { PiIcon } from "@/components/icons/pi-icon"; +import { ACP_PROVIDER_ICON_SVGS } from "@/assets/acp-provider-icons"; import { ACP_PROVIDER_CATALOG } from "@/data/acp-provider-catalog"; import { resolveProviderIconName } from "@/components/provider-icon-name"; @@ -31,10 +33,17 @@ const BUILTIN_PROVIDER_ICONS: Record = { pi: PiIcon as unknown as ProviderIconComponent, }; -const CATALOG_ICON_SVGS = new Map( +const CATALOG_ICON_SVGS = new Map( ACP_PROVIDER_CATALOG.flatMap((entry) => (entry.iconSvg ? [[entry.id, entry.iconSvg]] : [])), ); +for (const name of TERMINAL_PROFILE_ICON_NAMES) { + const iconSvg = (ACP_PROVIDER_ICON_SVGS as Record)[name]; + if (iconSvg) { + CATALOG_ICON_SVGS.set(name, iconSvg); + } +} + const catalogIconComponents = new Map(); const snapshotIconComponents = new Map();