From a11353577dc8ce4b84c6b1a8b41a7893504b9d57 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 13:39:49 +0200 Subject: [PATCH] =?UTF-8?q?chrome:=20brandIcons()=20=E2=80=94=20derive=20N?= =?UTF-8?q?ext.js=20favicon=20metadata=20from=20Brand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every app that imports the kit was shipping a /favicon.ico 404 because none of them wired up Next.js's metadata.icons. This adds a tiny helper so an app only has to: export const metadata: Metadata = { title: brand.product, icons: brandIcons(brand), }; brandIcons() returns icon/shortcut/apple entries pointing at brand.faviconUrl (new optional field, defaults to brand.logoUrl). MIME type inferred from the URL extension (svg/png/ico). Brand gains the optional faviconUrl field. Existing apps that just pass logoUrl keep working — they'll now render the logo as the favicon by default. Apps that want a separate icon set faviconUrl explicitly. First consumer: gscSounds layout — verified /favicon.ico now serves the proper icon and /icon.svg works too. --- src/chrome/brandIcons.ts | 37 +++++++++++++++++++++++++++++++++++++ src/chrome/index.ts | 1 + src/chrome/types.ts | 1 + 3 files changed, 39 insertions(+) create mode 100644 src/chrome/brandIcons.ts diff --git a/src/chrome/brandIcons.ts b/src/chrome/brandIcons.ts new file mode 100644 index 0000000..f92b723 --- /dev/null +++ b/src/chrome/brandIcons.ts @@ -0,0 +1,37 @@ +// Returns a Next.js Metadata `icons` object derived from a Brand. +// Apps drop this into their root layout's `metadata` export to ship +// the brand logo as the favicon — fixes the "favicon 404" every +// consumer of the kit was shipping with. +// +// import type { Metadata } from "next"; +// import { brandIcons } from "@gsc/web-kit/chrome"; +// import { brand } from "@/config/brand"; +// +// export const metadata: Metadata = { +// title: brand.product, +// icons: brandIcons(brand), +// }; + +import type { Brand } from "./types"; + +interface BrandMetaIcons { + icon: { url: string; type?: string }[]; + shortcut?: { url: string; type?: string }[]; + apple?: { url: string; type?: string }[]; +} + +export function brandIcons(brand: Brand): BrandMetaIcons { + const url = brand.faviconUrl ?? brand.logoUrl; + // Heuristic: trust the file extension to set the MIME type. Most + // brand logos in the GoSec assets bucket are SVG. + const type = + /\.svg(\?|$)/i.test(url) ? "image/svg+xml" : + /\.png(\?|$)/i.test(url) ? "image/png" : + /\.ico(\?|$)/i.test(url) ? "image/x-icon" : + undefined; + return { + icon: [{ url, ...(type ? { type } : {}) }], + shortcut: [{ url }], + apple: [{ url }], + }; +} diff --git a/src/chrome/index.ts b/src/chrome/index.ts index cab4cde..45eb523 100644 --- a/src/chrome/index.ts +++ b/src/chrome/index.ts @@ -11,6 +11,7 @@ export { type CustomerOption, } from "./header"; export { useChromeLabels, DEFAULT_CHROME_LABELS } from "./labels"; +export { brandIcons } from "./brandIcons"; export type { AdminShellProps, ActivityFeedItem, diff --git a/src/chrome/types.ts b/src/chrome/types.ts index 2d2d6d3..c3e4a24 100644 --- a/src/chrome/types.ts +++ b/src/chrome/types.ts @@ -61,6 +61,7 @@ export type Brand = { product: string; // "GoSec CRM" logoUrl: string; // full navbar logo logoSmallUrl?: string; // optional compact logo + faviconUrl?: string; // optional favicon override (defaults to logoUrl) websiteUrl: string; // footer brand link supportUrl: string; // subbar Support + footer Support link docsUrl: string; // footer docs link