From 3f46205f12d40a3a4f414d6d51d33c6e5eeb229c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 26 Sep 2026 18:27:39 -0700 Subject: [PATCH 1/2] fix(chat): use hostname brand icons for inline sources --- .../source-chip/source-icon.test.tsx | 31 +++++++++++++++++++ .../components/source-chip/source-icon.tsx | 16 +++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx new file mode 100644 index 00000000000..f56c6e179dc --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx @@ -0,0 +1,31 @@ +/** @vitest-environment jsdom */ +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it } from 'vitest' +import { SourceIcon } from '@/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon' + +describe('source hostname branding', () => { + it.each([ + 'https://example.slack.com/archives/channel/message', + 'https://app.slack.com/client/team/channel', + 'https://SLACK.COM/help', + ])('renders a local brand instead of requesting a tenant favicon for %s', (url) => { + const container = document.createElement('div') + container.innerHTML = renderToStaticMarkup() + expect(container.querySelector('img')).toBeNull() + expect(container.querySelector('svg')).not.toBeNull() + }) + + it.each([ + 'https://notslack.com/channel', + 'https://slack.com.example.org/channel', + 'https://slack.com@example.org/channel', + 'https://example.org/slack.com', + 'https://example.org/?url=https://example.slack.com', + ])('retains the actual destination favicon for a lookalike URL: %s', (url) => { + const container = document.createElement('div') + container.innerHTML = renderToStaticMarkup() + const image = container.querySelector('img') + expect(image).not.toBeNull() + expect(new URL(image!.src).searchParams.get('domain')).toBe(new URL(url).hostname) + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx index bf656dc37fd..0d752570553 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx @@ -29,6 +29,15 @@ const GOOGLE_DOCUMENT_ICON_BY_PATH: ReadonlyMap = new Map ['presentation', GoogleSlidesIcon], ]) +const BRAND_TYPE_BY_HOSTNAME: ReadonlyMap = new Map([ + ['github.com', 'github'], + ['gitlab.com', 'gitlab'], + ['notion.so', 'notion'], + ['drive.google.com', 'google_drive'], + ['mail.google.com', 'gmail'], + ['calendar.google.com', 'google_calendar'], +]) + interface SourceIconProps { source: SourceTagData size?: 'default' | 'inline' @@ -45,7 +54,12 @@ export function SourceIcon({ source, size = 'default' }: SourceIconProps) { hostname === 'docs.google.com' ? GOOGLE_DOCUMENT_ICON_BY_PATH.get(new URL(source.url).pathname.split('/')[1] ?? '') : undefined - const Icon = DocumentIcon ?? ConnectorIcon + const hostBrandType = + hostname === 'slack.com' || hostname?.endsWith('.slack.com') + ? 'slack' + : BRAND_TYPE_BY_HOSTNAME.get(hostname ?? '') + const HostIcon = hostBrandType ? BRAND_ICON_BY_BASE_TYPE.get(hostBrandType) : undefined + const Icon = DocumentIcon ?? ConnectorIcon ?? HostIcon const className = cn('shrink-0', size === 'inline' ? 'size-[12px]' : 'size-[14px]') if (Icon) return if (hostname && failedHostname !== hostname) { From 62827b08737d3d19749f265002cfb9323538875d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 26 Sep 2026 18:33:24 -0700 Subject: [PATCH 2/2] fix(chat): recognize www aliases in source branding --- .../components/source-chip/source-icon.test.tsx | 5 ++++- .../message-content/components/source-chip/source-icon.tsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx index f56c6e179dc..f3466aa81f0 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.test.tsx @@ -8,7 +8,10 @@ describe('source hostname branding', () => { 'https://example.slack.com/archives/channel/message', 'https://app.slack.com/client/team/channel', 'https://SLACK.COM/help', - ])('renders a local brand instead of requesting a tenant favicon for %s', (url) => { + 'https://www.github.com/example/project', + 'https://www.gitlab.com/example/project', + 'https://www.notion.so/example', + ])('renders a local brand instead of requesting a favicon for a recognized host: %s', (url) => { const container = document.createElement('div') container.innerHTML = renderToStaticMarkup() expect(container.querySelector('img')).toBeNull() diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx index 0d752570553..d230b9f41c3 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-icon.tsx @@ -57,7 +57,7 @@ export function SourceIcon({ source, size = 'default' }: SourceIconProps) { const hostBrandType = hostname === 'slack.com' || hostname?.endsWith('.slack.com') ? 'slack' - : BRAND_TYPE_BY_HOSTNAME.get(hostname ?? '') + : BRAND_TYPE_BY_HOSTNAME.get(hostname?.replace(/^www\./, '') ?? '') const HostIcon = hostBrandType ? BRAND_ICON_BY_BASE_TYPE.get(hostBrandType) : undefined const Icon = DocumentIcon ?? ConnectorIcon ?? HostIcon const className = cn('shrink-0', size === 'inline' ? 'size-[12px]' : 'size-[14px]')