diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx
index 34141a3121d3ddb9c51997f3de120b16a8a00bb4..d06ddde714eba80e56847ed8414ce6f426160c4c 100644
--- a/apps/mobile/src/components/ProviderIcon.tsx
+++ b/apps/mobile/src/components/ProviderIcon.tsx
@@ -1,8 +1,8 @@
import { Image } from "expo-image";
-import { Path, Svg } from "react-native-svg";
+import { Defs, LinearGradient, Path, Stop, Svg } from "react-native-svg";
import { View } from "react-native";
import { providerInstanceInitials } from "@t3tools/client-runtime/state/provider-instance-display";
-import { useState } from "react";
+import { useId, useState } from "react";
import { resolveOfficialAcpRegistryIconUrl } from "@t3tools/contracts";
import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
import { AppText as Text } from "./AppText";
@@ -66,6 +66,7 @@ function AcpRegistryProviderIcon(props: {
export function ProviderIcon(props: ProviderIconProps) {
const { themeAppearance } = useAppearancePreferences();
const isDarkMode = themeAppearance === "dark";
+ const gradientId = useId();
const size = props.size ?? 16;
const mono = isDarkMode ? "#e5e5e5" : "#171717";
@@ -99,12 +100,35 @@ export function ProviderIcon(props: ProviderIconProps) {
isDarkMode ? fill.dark : fill.light;
return (
diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx
index 80ef5ecd054b585046faf93678566a2e89b004b4..f2245e0146d3d74828c62e9bdf454b63f8c16177 100644
--- a/apps/web/src/components/chat/ChatComposer.tsx
+++ b/apps/web/src/components/chat/ChatComposer.tsx
@@ -5649,7 +5649,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
activeProviderIconClassName: cn(
composerProviderState.modelPickerIconClassName,
composerControlsCollapsed &&
- "fill-muted-foreground/70! text-muted-foreground/70! [&_path]:fill-muted-foreground/70! [&_rect]:fill-muted-foreground/70! [&_[data-icon-detail]]:fill-transparent!",
+ // Package glyphs get opaque fills and one opacity, so overlapping paths don't band.
+ "fill-muted-foreground/70! text-muted-foreground/70! [&_path]:fill-muted-foreground/70! [&_rect]:fill-muted-foreground/70! [&_[data-icon-detail]]:fill-transparent! [&[data-provider-package-icon]]:opacity-70 [&[data-provider-package-icon]_path:not([data-icon-detail])]:fill-muted-foreground!",
),
}
: {})}
diff --git a/apps/web/src/components/chat/ProviderPackageIcon.tsx b/apps/web/src/components/chat/ProviderPackageIcon.tsx
index 98c27693dc1e9eb9c6edfae6b701a5dc68e1a647..675c619a702de691b17cb6ede6670b7548e4fc56 100644
--- a/apps/web/src/components/chat/ProviderPackageIcon.tsx
+++ b/apps/web/src/components/chat/ProviderPackageIcon.tsx
@@ -1,4 +1,4 @@
-import type { CSSProperties, SVGProps } from "react";
+import { useId, type CSSProperties, type SVGProps } from "react";
import type { ProviderIcon } from "@t3tools/provider-core/client";
@@ -13,11 +13,14 @@ export function ProviderPackageIcon({
className,
...props
}: { readonly icon: ProviderIcon } & SVGProps) {
+ const gradientId = useId();
return (