From 53ab67ea352eb05adbff3d31d2a2c3e21ceb5dc4 Mon Sep 17 00:00:00 2001 From: Serhii Dziupin Date: Wed, 12 Aug 2026 09:12:02 +0000 Subject: [PATCH] refactor(integrations): drop unused logos, brand icons, and card wrapper Keep only the Settings Integrations page wiring: three plugins, i18n, search/metadata, and the plugins-store registry boolean needed for failure status. Remove ProviderLogo fallbacks, SVG assets, custom sprite icons, and the separate IntegrationCard layer. Co-authored-by: Serhii Dziupin --- .../src/assets/provider-logos/claude-code.svg | 4 - .../ui/src/assets/provider-logos/opencode.svg | 8 - packages/ui/src/components/icon/sprite.ts | 4 +- .../integrations/IntegrationCard.test.tsx | 54 ------ .../sections/integrations/IntegrationCard.tsx | 64 ------- .../ThirdPartyIntegrationsSection.tsx | 163 ++++++++++-------- .../integrations/thirdPartyPlugins.ts | 9 +- .../ui/src/components/ui/ProviderLogo.tsx | 5 +- .../ui/providerLogoFallback.test.ts | 13 -- .../src/components/ui/providerLogoFallback.ts | 5 - scripts/generate-icon-sprite.mjs | 15 -- 11 files changed, 93 insertions(+), 251 deletions(-) delete mode 100644 packages/ui/src/assets/provider-logos/claude-code.svg delete mode 100644 packages/ui/src/assets/provider-logos/opencode.svg delete mode 100644 packages/ui/src/components/sections/integrations/IntegrationCard.test.tsx delete mode 100644 packages/ui/src/components/sections/integrations/IntegrationCard.tsx delete mode 100644 packages/ui/src/components/ui/providerLogoFallback.test.ts delete mode 100644 packages/ui/src/components/ui/providerLogoFallback.ts diff --git a/packages/ui/src/assets/provider-logos/claude-code.svg b/packages/ui/src/assets/provider-logos/claude-code.svg deleted file mode 100644 index 9a454303..00000000 --- a/packages/ui/src/assets/provider-logos/claude-code.svg +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/packages/ui/src/assets/provider-logos/opencode.svg b/packages/ui/src/assets/provider-logos/opencode.svg deleted file mode 100644 index 16a0e9ef..00000000 --- a/packages/ui/src/assets/provider-logos/opencode.svg +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/packages/ui/src/components/icon/sprite.ts b/packages/ui/src/components/icon/sprite.ts index b93478e6..769b58cd 100644 --- a/packages/ui/src/components/icon/sprite.ts +++ b/packages/ui/src/components/icon/sprite.ts @@ -51,7 +51,6 @@ export const iconSpriteData = { "checkbox-blank-circle-fill": ``, "checkbox-circle": ``, "checkbox-multiple": ``, - "claude-code": ``, "clipboard": ``, "close": ``, "close-circle": ``, @@ -63,12 +62,11 @@ export const iconSpriteData = { "code-sslash": ``, "collapse-vertical": ``, "command": ``, - "command-code": ``, "compass-3": ``, "computer": ``, "contract-up-down": ``, "corner-down-left": ``, - "cursor": ``, + "cursor": ``, "database-2": ``, "delete-bin": ``, "discord-fill": ``, diff --git a/packages/ui/src/components/sections/integrations/IntegrationCard.test.tsx b/packages/ui/src/components/sections/integrations/IntegrationCard.test.tsx deleted file mode 100644 index cc9f9aa2..00000000 --- a/packages/ui/src/components/sections/integrations/IntegrationCard.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { describe, expect, test } from 'bun:test'; -import React from 'react'; - -const integrationCards = await import('./Integration' + 'Card').catch(() => ({})); - -type IntegrationCardComponent = (props: { - open: boolean; - onOpenChange: (open: boolean) => void; - header: React.ReactNode; - headerAction?: React.ReactNode; - children: React.ReactNode; -}) => React.ReactElement; - -const IntegrationCard = ( - integrationCards as unknown as { - IntegrationCard?: IntegrationCardComponent; - } -).IntegrationCard; - -describe('IntegrationCard', () => { - test('keeps header controls outside the expansion button', () => { - expect(typeof IntegrationCard).toBe('function'); - if (!IntegrationCard) return; - - const onOpenChange = () => undefined; - const toggle = ; - const card = IntegrationCard({ - open: false, - onOpenChange, - header: Integration, - headerAction: toggle, - children:
Details
, - }); - - const cardProps = card.props as { - open: boolean; - onOpenChange: (open: boolean) => void; - children: React.ReactElement; - }; - expect(cardProps.open).toBe(false); - expect(cardProps.onOpenChange).toBe(onOpenChange); - - const surface = cardProps.children as React.ReactElement<{ children: React.ReactNode }>; - const headerRow = React.Children.toArray(surface.props.children)[0] as React.ReactElement<{ - children: React.ReactNode; - }>; - const headerChildren = React.Children.toArray(headerRow.props.children) as React.ReactElement<{ - children?: React.ReactNode; - }>[]; - - expect(headerChildren[0]?.type).toBe('button'); - expect(headerChildren[1]?.props.children).toBe(toggle); - }); -}); diff --git a/packages/ui/src/components/sections/integrations/IntegrationCard.tsx b/packages/ui/src/components/sections/integrations/IntegrationCard.tsx deleted file mode 100644 index c1440e12..00000000 --- a/packages/ui/src/components/sections/integrations/IntegrationCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { Icon } from '@/components/icon/Icon'; -import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; -import { cn } from '@/lib/utils'; - -interface IntegrationCardProps { - open: boolean; - onOpenChange: (open: boolean) => void; - header: React.ReactNode; - headerAction?: React.ReactNode; - children: React.ReactNode; - className?: string; - contentClassName?: string; - settingsItem?: string; -} - -/** - * Shared chrome for an integration summary and its on-demand configuration. - * Header actions sit beside—not inside—the expansion button so their native - * controls preserve their own keyboard and click behavior. - */ -export const IntegrationCard: React.FC = ({ - open, - onOpenChange, - header, - headerAction, - children, - className, - contentClassName, - settingsItem, -}) => { - return ( - -
-
- - {headerAction ?
{headerAction}
: null} -
- - {children} - -
-
- ); -}; diff --git a/packages/ui/src/components/sections/integrations/ThirdPartyIntegrationsSection.tsx b/packages/ui/src/components/sections/integrations/ThirdPartyIntegrationsSection.tsx index e69e086e..02066ed3 100644 --- a/packages/ui/src/components/sections/integrations/ThirdPartyIntegrationsSection.tsx +++ b/packages/ui/src/components/sections/integrations/ThirdPartyIntegrationsSection.tsx @@ -12,6 +12,7 @@ import { import { toast } from '@/components/ui'; import { Icon } from '@/components/icon/Icon'; import { SettingsSection } from '@/components/sections/shared/SettingsSection'; +import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; import { useI18n } from '@/lib/i18n'; import { openExternalUrl } from '@/lib/url'; import { cn } from '@/lib/utils'; @@ -27,7 +28,6 @@ import { THIRD_PARTY_PLUGINS, type ThirdPartyPluginDefinition, } from './thirdPartyPlugins'; -import { IntegrationCard } from './IntegrationCard'; type PendingAction = 'install' | 'update' | 'setup' | 'remove'; @@ -296,16 +296,26 @@ export const ThirdPartyIntegrationsSection: React.FC setPluginOpen(plugin.id, open)} - settingsItem={`integrations.third-party.${plugin.id}`} - header={( -
+ open={open} + onOpenChange={(nextOpen) => setPluginOpen(plugin.id, nextOpen)} + > +
+ - {registryUnavailable ? ( - + +
+
- {isRefreshing || isLoadingRegistry ? : null} - {t('settings.integrations.thirdParty.actions.refresh')} - - ) : null} - - {state.userEntry && !state.userEntryIsAmbiguous ? ( - - ) : null} -
+

{status}

+ {state.projectEntries.length > 0 ? ( +

+ {t('settings.integrations.thirdParty.status.projectInstalled')} +

+ ) : null} +
+
+ + {registryUnavailable ? ( + + ) : null} + + {state.userEntry && !state.userEntryIsAmbiguous ? ( + + ) : null} +
+
+
- + ); }; diff --git a/packages/ui/src/components/sections/integrations/thirdPartyPlugins.ts b/packages/ui/src/components/sections/integrations/thirdPartyPlugins.ts index de4b95b0..cf1b4d80 100644 --- a/packages/ui/src/components/sections/integrations/thirdPartyPlugins.ts +++ b/packages/ui/src/components/sections/integrations/thirdPartyPlugins.ts @@ -7,8 +7,6 @@ export interface ThirdPartyPluginDefinition { packageName: string; providerId: string; icon: IconName; - /** Brand mark color class (theme tokens only). */ - brandClassName: string; nameKey: I18nKey; descriptionKey: I18nKey; homepage: string; @@ -19,8 +17,7 @@ export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [ id: 'opencode-claude', packageName: '@otto-assistant/opencode-claude', providerId: 'claude-code', - icon: 'claude-code', - brandClassName: 'text-foreground', + icon: 'sparkling', nameKey: 'settings.integrations.thirdParty.opencodeClaude.name', descriptionKey: 'settings.integrations.thirdParty.opencodeClaude.description', homepage: 'https://github.com/otto-assistant/opencode-claude', @@ -29,8 +26,7 @@ export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [ id: 'opencode-commandcode', packageName: '@otto-assistant/opencode-commandcode', providerId: 'command-code', - icon: 'command-code', - brandClassName: 'text-foreground', + icon: 'terminal-box', nameKey: 'settings.integrations.thirdParty.opencodeCommandcode.name', descriptionKey: 'settings.integrations.thirdParty.opencodeCommandcode.description', homepage: 'https://github.com/otto-assistant/opencode-commandcode', @@ -40,7 +36,6 @@ export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [ packageName: '@otto-assistant/opencode-cursor-oauth', providerId: 'cursor', icon: 'cursor', - brandClassName: 'text-foreground', nameKey: 'settings.integrations.thirdParty.opencodeCursorOauth.name', descriptionKey: 'settings.integrations.thirdParty.opencodeCursorOauth.description', homepage: 'https://github.com/otto-assistant/opencode-cursor', diff --git a/packages/ui/src/components/ui/ProviderLogo.tsx b/packages/ui/src/components/ui/ProviderLogo.tsx index 70af4f45..ef2e3bac 100644 --- a/packages/ui/src/components/ui/ProviderLogo.tsx +++ b/packages/ui/src/components/ui/ProviderLogo.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import { Icon } from '@/components/icon/Icon'; import { useProviderLogo } from '@/hooks/useProviderLogo'; import { cn } from '@/lib/utils'; -import { getProviderLogoFallbackIcon } from './providerLogoFallback'; interface ProviderLogoProps { providerId: string; @@ -18,7 +16,6 @@ export const ProviderLogo: React.FC = ({ onError: externalOnError }) => { const { src, onError: handleInternalError, hasLogo } = useProviderLogo(providerId); - const fallbackIcon = getProviderLogoFallbackIcon(providerId); const handleError = React.useCallback(() => { handleInternalError(); @@ -26,7 +23,7 @@ export const ProviderLogo: React.FC = ({ }, [handleInternalError, externalOnError]); if (!hasLogo || !src) { - return fallbackIcon ? : null; + return null; } return ( diff --git a/packages/ui/src/components/ui/providerLogoFallback.test.ts b/packages/ui/src/components/ui/providerLogoFallback.test.ts deleted file mode 100644 index c82b9ad3..00000000 --- a/packages/ui/src/components/ui/providerLogoFallback.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, test } from 'bun:test'; -import { getProviderLogoFallbackIcon } from './providerLogoFallback'; - -describe('provider logo fallbacks', () => { - test('uses a local terminal icon when Command Code has no resolved logo', () => { - expect(getProviderLogoFallbackIcon('command-code')).toBe('terminal-box'); - }); - - test('does not replace providers with their own logo assets', () => { - expect(getProviderLogoFallbackIcon('claude-code')).toBeNull(); - expect(getProviderLogoFallbackIcon('cursor')).toBeNull(); - }); -}); diff --git a/packages/ui/src/components/ui/providerLogoFallback.ts b/packages/ui/src/components/ui/providerLogoFallback.ts deleted file mode 100644 index 9aa871fd..00000000 --- a/packages/ui/src/components/ui/providerLogoFallback.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { IconName } from '@/components/icon/icons'; - -export function getProviderLogoFallbackIcon(providerId: string | null | undefined): IconName | null { - return providerId?.trim().toLowerCase() === 'command-code' ? 'terminal-box' : null; -} diff --git a/scripts/generate-icon-sprite.mjs b/scripts/generate-icon-sprite.mjs index 3098bb05..1d1f1c3e 100644 --- a/scripts/generate-icon-sprite.mjs +++ b/scripts/generate-icon-sprite.mjs @@ -22,21 +22,6 @@ const customIconData = new Map([ "openchamber", ``, ], - // Claude spark — official Anthropic mark (Simple Icons path), monochrome. - [ - "claude-code", - ``, - ], - // Cursor two-cursor mark — official (Simple Icons path), monochrome. - [ - "cursor", - ``, - ], - // Command Code — corner squares + center square (official logo geometry). - [ - "command-code", - ``, - ], ]) const source = readFileSync(remixPath, "utf-8")