diff --git a/.github/social-preview-brand.svg b/.github/social-preview-brand.svg deleted file mode 100644 index c9c6bbcc..00000000 --- a/.github/social-preview-brand.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - C - lash - diff --git a/.github/social-preview.png b/.github/social-preview.png index dc3c4f23..4d1cc82c 100644 Binary files a/.github/social-preview.png and b/.github/social-preview.png differ diff --git a/apps/desktop/build/icon-1024.png b/apps/desktop/build/icon-1024.png index 9118e56f..b3eb47ba 100644 Binary files a/apps/desktop/build/icon-1024.png and b/apps/desktop/build/icon-1024.png differ diff --git a/apps/desktop/build/icon.icns b/apps/desktop/build/icon.icns index bce48907..45143ca6 100644 Binary files a/apps/desktop/build/icon.icns and b/apps/desktop/build/icon.icns differ diff --git a/apps/desktop/build/icon.svg b/apps/desktop/build/icon.svg index 8af796ef..bda8f1db 100644 --- a/apps/desktop/build/icon.svg +++ b/apps/desktop/build/icon.svg @@ -1,11 +1,22 @@ - + - + + + + + + diff --git a/apps/desktop/src/electron-version.test.ts b/apps/desktop/src/electron-version.test.ts index e04b96f5..eb5ce32a 100644 --- a/apps/desktop/src/electron-version.test.ts +++ b/apps/desktop/src/electron-version.test.ts @@ -176,7 +176,7 @@ describe("desktop Electron runtime", () => { expect(readFileSync(iconUrl).subarray(0, 4).toString("ascii")).toBe("icns"); }); - it("uses a full-size centered Clash C desktop icon source", () => { + it("uses a full-size centered desktop icon source", () => { const iconSvg = readFileSync( new URL("../build/icon.svg", import.meta.url), "utf8", @@ -184,9 +184,9 @@ describe("desktop Electron runtime", () => { expect(iconSvg).toContain('viewBox="0 0 1024 1024"'); expect(iconSvg).toContain('rx="216"'); - expect(iconSvg).toContain('aria-label="Clash C app icon"'); - expect(iconSvg).toContain('stroke="#FF6B50"'); - expect(iconSvg).not.toMatch(/ { diff --git a/apps/web/app/agent-logo-contract.test.ts b/apps/web/app/agent-logo-contract.test.ts new file mode 100644 index 00000000..a2701fc5 --- /dev/null +++ b/apps/web/app/agent-logo-contract.test.ts @@ -0,0 +1,43 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +const repoRoot = join(import.meta.dirname, "../../.."); + +const readRepoFile = (path: string) => + readFileSync(join(repoRoot, path), "utf8"); + +describe("Clash agent logo contract", () => { + it("uses the chatbot avatar for the packaged desktop application", () => { + const source = readRepoFile("apps/desktop/build/icon.svg"); + + expect(source).toContain('aria-label="Clash app icon"'); + expect(source.match(/ { + const favicon = readRepoFile("apps/web/public/favicon.svg"); + const mark = readRepoFile("apps/web/public/brand/logo-mark.svg"); + const html = readRepoFile("apps/web/index.html"); + const consumers = [ + "apps/web/app/root.tsx", + "apps/web/app/routes/login.tsx", + "packages/web-ui/src/components/ProjectsClient.tsx", + "packages/web-ui/src/components/SettingsClient.tsx", + "packages/web-ui/src/components/TopNavigation.tsx", + ] + .map(readRepoFile) + .join("\n"); + + expect(favicon).toBe(mark); + expect(mark.match(/ { - it("uses the theme-colored Clash C for the browser tab favicon", () => { - const favicon = readFileSync( - join(repoRoot, "apps/web/public/favicon-c.svg"), - "utf8", - ); - const mark = readFileSync( - join(repoRoot, "apps/web/public/brand/logo-mark.svg"), - "utf8", - ); - const html = readFileSync(join(repoRoot, "apps/web/index.html"), "utf8"); - - expect(favicon).toBe(mark); - expect(favicon).toContain("#FF6B50"); - expect(favicon).toContain('aria-label="Clash C"'); - expect(favicon).not.toMatch(/ { - const source = readFileSync( - join(repoRoot, ".github/social-preview-brand.svg"), - "utf8", - ); - - expect(source).toContain('aria-label="Clash"'); - expect(source).toContain('class="clash-c"'); - expect(source).toContain('fill="#FF6B50"'); - expect(source).toContain(">C"); - expect(source).toContain(">lash"); - expect(source).not.toMatch(/ { - const source = readFileSync( - join(repoRoot, "apps/desktop/build/icon.svg"), - "utf8", - ); - - expect(source).toContain('aria-label="Clash C app icon"'); - expect(source).toContain("#FF6B50"); - expect(source).not.toMatch(/ { - const consumers = [ - "apps/web/app/root.tsx", - "apps/web/app/routes/login.tsx", - "packages/web-ui/src/components/ProjectsClient.tsx", - "packages/web-ui/src/components/SettingsClient.tsx", - "packages/web-ui/src/components/TopNavigation.tsx", - ] - .map((path) => readFileSync(join(repoRoot, path), "utf8")) - .join("\n"); - - expect(consumers).toContain("/brand/logo-c.svg"); - expect(consumers).toContain("/brand/logo-c-animated.svg"); - expect(consumers).toContain("/brand/logo-c-error.svg"); - expect(consumers).not.toContain("/brand/logo-mark"); - }); -}); diff --git a/apps/web/app/root-route-primitives.test.ts b/apps/web/app/root-route-primitives.test.ts index 311bff64..e5daacd8 100644 --- a/apps/web/app/root-route-primitives.test.ts +++ b/apps/web/app/root-route-primitives.test.ts @@ -60,24 +60,6 @@ describe("root route primitives", () => { expect(sitemap).not.toContain("/login"); }); - it("publishes cache-busted Clash C install icons", () => { - const html = readFileSync( - new URL("../index.html", import.meta.url), - "utf8", - ); - const webManifest = JSON.parse( - readFileSync(new URL("../public/site.webmanifest", import.meta.url), "utf8"), - ) as { icons?: Array<{ src?: string }> }; - - expect(html).toContain('href="/favicon-c.svg"'); - expect(html).toContain('href="/apple-touch-icon-c.png"'); - expect(webManifest.icons?.map((icon) => icon.src)).toEqual([ - "/icon-c-192.png", - "/icon-c-512.png", - "/maskable-icon-c-512.png", - ]); - }); - it("keeps the desktop runtime callout legible in dark mode", () => { const css = readFileSync(new URL("./globals.css", import.meta.url), "utf8"); diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx index c78dbe7d..181a4d10 100644 --- a/apps/web/app/root.tsx +++ b/apps/web/app/root.tsx @@ -92,7 +92,7 @@ export function ErrorBoundary() {
- Clash + Clash

Route paused

diff --git a/apps/web/app/routes/login.tsx b/apps/web/app/routes/login.tsx index badafdfd..f42aea8f 100644 --- a/apps/web/app/routes/login.tsx +++ b/apps/web/app/routes/login.tsx @@ -178,13 +178,13 @@ export default function LoginRoute() { > { expect(css).toMatch( /\.dark \.clash-copilot-launcher \.clash-agent-motion__pen\s*\{[\s\S]*?fill: #ff6b50/, ); - expect(topNavigation).toContain('/brand/logo-c.svg'); + expect(topNavigation).toContain('/brand/logo-mark-dark.svg'); expect(topNavigation).not.toContain("dark:grayscale"); }); @@ -333,7 +333,7 @@ describe("application theme contract", () => { expect(login).toContain("dark:text-neutral-50"); expect(login).toContain("dark:text-neutral-100"); expect(login).toContain("dark:placeholder:text-neutral-500"); - expect(login).toContain('/brand/logo-c.svg'); + expect(login).toContain('/brand/logo-mark-dark.svg'); }); it("exposes Appearance as a real persisted settings section", () => { diff --git a/apps/web/index.html b/apps/web/index.html index 188c151b..2695d8fe 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -2,9 +2,8 @@ - - - + + - - - - diff --git a/apps/web/public/brand/logo-c-error.svg b/apps/web/public/brand/logo-c-error.svg deleted file mode 100644 index 84c5da91..00000000 --- a/apps/web/public/brand/logo-c-error.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apps/web/public/brand/logo-c.svg b/apps/web/public/brand/logo-c.svg deleted file mode 100644 index 84c5da91..00000000 --- a/apps/web/public/brand/logo-c.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apps/web/public/brand/logo-lockup-horizontal.svg b/apps/web/public/brand/logo-lockup-horizontal.svg index 79d76cad..d482fa16 100644 --- a/apps/web/public/brand/logo-lockup-horizontal.svg +++ b/apps/web/public/brand/logo-lockup-horizontal.svg @@ -1,10 +1,31 @@ - + + + + + + + + + Clash + font-size="42" + font-weight="650" + >Clash diff --git a/apps/web/public/brand/logo-mark-animated.svg b/apps/web/public/brand/logo-mark-animated.svg index 9cdf8bd4..5afad8c1 100644 --- a/apps/web/public/brand/logo-mark-animated.svg +++ b/apps/web/public/brand/logo-mark-animated.svg @@ -1,20 +1,100 @@ - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/brand/logo-mark-dark.svg b/apps/web/public/brand/logo-mark-dark.svg index 84c5da91..d8f7a2f5 100644 --- a/apps/web/public/brand/logo-mark-dark.svg +++ b/apps/web/public/brand/logo-mark-dark.svg @@ -1,10 +1,20 @@ - + + + stroke-linejoin="round"/> + + + diff --git a/apps/web/public/brand/logo-mark-error.svg b/apps/web/public/brand/logo-mark-error.svg index 84c5da91..a498e061 100644 --- a/apps/web/public/brand/logo-mark-error.svg +++ b/apps/web/public/brand/logo-mark-error.svg @@ -1,10 +1,23 @@ - - + + + + + + + + + diff --git a/apps/web/public/brand/logo-mark.svg b/apps/web/public/brand/logo-mark.svg index 84c5da91..2561f53a 100644 --- a/apps/web/public/brand/logo-mark.svg +++ b/apps/web/public/brand/logo-mark.svg @@ -1,10 +1,21 @@ - - + + + + + + + diff --git a/apps/web/public/favicon-c.svg b/apps/web/public/favicon-c.svg deleted file mode 100644 index 84c5da91..00000000 --- a/apps/web/public/favicon-c.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg index 84c5da91..2561f53a 100644 --- a/apps/web/public/favicon.svg +++ b/apps/web/public/favicon.svg @@ -1,10 +1,21 @@ - - + + + + + + + diff --git a/apps/web/public/icon-192.png b/apps/web/public/icon-192.png index fe1292f2..b53d2012 100644 Binary files a/apps/web/public/icon-192.png and b/apps/web/public/icon-192.png differ diff --git a/apps/web/public/icon-512.png b/apps/web/public/icon-512.png index ae3369ca..3e6383e1 100644 Binary files a/apps/web/public/icon-512.png and b/apps/web/public/icon-512.png differ diff --git a/apps/web/public/icon-c-192.png b/apps/web/public/icon-c-192.png deleted file mode 100644 index fe1292f2..00000000 Binary files a/apps/web/public/icon-c-192.png and /dev/null differ diff --git a/apps/web/public/icon-c-512.png b/apps/web/public/icon-c-512.png deleted file mode 100644 index ae3369ca..00000000 Binary files a/apps/web/public/icon-c-512.png and /dev/null differ diff --git a/apps/web/public/maskable-icon-512.png b/apps/web/public/maskable-icon-512.png index ae3369ca..3e6383e1 100644 Binary files a/apps/web/public/maskable-icon-512.png and b/apps/web/public/maskable-icon-512.png differ diff --git a/apps/web/public/maskable-icon-c-512.png b/apps/web/public/maskable-icon-c-512.png deleted file mode 100644 index ae3369ca..00000000 Binary files a/apps/web/public/maskable-icon-c-512.png and /dev/null differ diff --git a/apps/web/public/og-image.png b/apps/web/public/og-image.png index 995f5a1f..cb36c9b7 100644 Binary files a/apps/web/public/og-image.png and b/apps/web/public/og-image.png differ diff --git a/apps/web/public/site.webmanifest b/apps/web/public/site.webmanifest index 4c5ecdb4..a3201f4c 100644 --- a/apps/web/public/site.webmanifest +++ b/apps/web/public/site.webmanifest @@ -8,17 +8,17 @@ "theme_color": "#FF6B50", "icons": [ { - "src": "/icon-c-192.png", + "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/icon-c-512.png", + "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }, { - "src": "/maskable-icon-c-512.png", + "src": "/maskable-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/packages/web-ui/src/components/ProjectsClient.tsx b/packages/web-ui/src/components/ProjectsClient.tsx index 0eb2f871..c16f188e 100644 --- a/packages/web-ui/src/components/ProjectsClient.tsx +++ b/packages/web-ui/src/components/ProjectsClient.tsx @@ -47,7 +47,7 @@ export default function ProjectsClient({ projects }: ProjectsClientProps) { First cut, open frame

- + Agent pass
diff --git a/packages/web-ui/src/components/SettingsClient.tsx b/packages/web-ui/src/components/SettingsClient.tsx index 2f6bf3e8..f3de0d7b 100644 --- a/packages/web-ui/src/components/SettingsClient.tsx +++ b/packages/web-ui/src/components/SettingsClient.tsx @@ -2355,7 +2355,7 @@ function modelCardBrand(model: ModelCatalogEntryInfo['model']): ModelBrand { return { id: 'recraft', label: 'Recraft', src: '/brand/models/recraft.svg' }; } if (/(clash mock|mock image|mock text)/.test(identity)) { - return { id: 'clash', label: 'Clash', src: '/brand/logo-c.svg' }; + return { id: 'clash', label: 'Clash', src: '/brand/logo-mark.svg' }; } if (/(nano-banana|gemini|veo|google)/.test(identity)) { return { id: 'google', label: 'Google', src: '/brand/providers/google.svg' }; @@ -2392,7 +2392,7 @@ function modelCardBrand(model: ModelCatalogEntryInfo['model']): ModelBrand { } if (/(local|mlx|whisper|kokoro|piper)/.test(identity)) { if (/(local agent)/.test(identity)) { - return { id: 'clash', label: 'Clash', src: '/brand/logo-c.svg' }; + return { id: 'clash', label: 'Clash', src: '/brand/logo-mark.svg' }; } return { id: 'local', label: 'Local' }; } diff --git a/packages/web-ui/src/components/TopNavigation.desktop.test.tsx b/packages/web-ui/src/components/TopNavigation.desktop.test.tsx index 10baf8f1..fa23a92b 100644 --- a/packages/web-ui/src/components/TopNavigation.desktop.test.tsx +++ b/packages/web-ui/src/components/TopNavigation.desktop.test.tsx @@ -177,7 +177,7 @@ describe("TopNavigation desktop chrome", () => { const home = await screen.findByRole("button", { name: "Clash home" }); const logos = Array.from(home.querySelectorAll("img")); expect(logos.map((logo) => logo.getAttribute("src"))).toEqual( - expect.arrayContaining(["/brand/logo-c.svg"]), + expect.arrayContaining(["/icon-192.png", "/brand/logo-mark-dark.svg"]), ); expect(logos.some((logo) => logo.className.includes("dark:block"))).toBe(true); expect(logos.every((logo) => !logo.className.includes("dark:grayscale"))).toBe(true); diff --git a/packages/web-ui/src/components/TopNavigation.tsx b/packages/web-ui/src/components/TopNavigation.tsx index c7e554b6..849fd6d9 100644 --- a/packages/web-ui/src/components/TopNavigation.tsx +++ b/packages/web-ui/src/components/TopNavigation.tsx @@ -347,13 +347,13 @@ export default function TopNavigation() { icon={ - - + + + ); } diff --git a/packages/web-ui/src/components/landing/LandingNav.test.tsx b/packages/web-ui/src/components/landing/LandingNav.test.tsx index 0cbd4830..98f1bd40 100644 --- a/packages/web-ui/src/components/landing/LandingNav.test.tsx +++ b/packages/web-ui/src/components/landing/LandingNav.test.tsx @@ -32,17 +32,16 @@ describe("LandingNav", () => { expect(headerSurface?.className).toContain("clash-control-surface"); }); - it("uses the Clash wordmark with a brand-colored C", () => { + it("uses the current Clash agent logo", () => { const { container, getByLabelText } = render(); expect(getByLabelText("Clash")).toBeTruthy(); - const c = container.querySelector(".clash-wordmark-c"); - expect(c).toBeTruthy(); - expect(c?.className).toContain("text-brand"); - expect(c?.textContent).toBe("C"); - expect(container.querySelector(".clash-wordmark-rest")?.textContent).toBe( - "lash", - ); - expect(container.querySelector('img[src*="/brand/logo-mark"]')).toBeNull(); + expect( + container.querySelector('img[src="/brand/logo-mark.svg"]'), + ).toBeTruthy(); + expect( + container.querySelector('img[src="/brand/logo-mark-dark.svg"]'), + ).toBeTruthy(); + expect(container.querySelector(".clash-wordmark-slash")).toBeNull(); }); }); diff --git a/packages/web-ui/src/components/visualLanguage.test.tsx b/packages/web-ui/src/components/visualLanguage.test.tsx index 9c65f5a1..21be1ba5 100644 --- a/packages/web-ui/src/components/visualLanguage.test.tsx +++ b/packages/web-ui/src/components/visualLanguage.test.tsx @@ -284,6 +284,14 @@ describe("visual language surfaces", () => { expect(source).toMatch(/clash-node-badge-draft/); }); + it("uses the transparent Clash mark for the browser tab favicon", () => { + const favicon = readFileSync(join(process.cwd(), "apps/web/public/favicon.svg"), "utf8"); + const mark = readFileSync(join(process.cwd(), "apps/web/public/brand/logo-mark.svg"), "utf8"); + + expect(favicon).toBe(mark); + expect(favicon).not.toMatch(/ { const source = [ "packages/web-ui/src/components/nodes/TextNode.tsx", @@ -509,7 +517,7 @@ describe("visual language surfaces", () => { expect(source).toMatch(/clash-hero-stage/); expect(source).toMatch(/clash-hero-prompt/); expect(source).not.toMatch(/lg:pl-(12|16)|xl:pl-(12|16)/); - expect(source).toMatch(/\/brand\/logo-c-animated\.svg/); + expect(source).toMatch(/\/brand\/logo-mark-animated\.svg/); expect(source).toMatch(/clash-dashboard-shell/); expect(source).toMatch(/clash-projects-empty-workbench/); expect(source).toMatch(/clash-projects-empty-canvas/); @@ -715,7 +723,7 @@ describe("visual language surfaces", () => { expect(source).toMatch(/error\.code/); expect(source).toMatch(/Reload/); expect(source).toMatch(/Go home/); - expect(source).toMatch(/\/brand\/logo-c-error\.svg/); + expect(source).toMatch(/\/brand\/logo-mark-error\.svg/); expect(errorMark).toMatch(/aria-label="Clash error logo"/); expect(errorMark.match(/stroke-linecap="round"/g)?.length).toBeGreaterThanOrEqual(5); });