Skip to content

Commit ef47feb

Browse files
vitalyiegorovclaude
andcommitted
fix(clients): context handoffs show each side's account badge
A handoff between two accounts of one provider showed the same glyph and model on both sides. Each endpoint's provider icon now carries the same colored initials badge as the sidebar and thread rows, using the shared shouldShowInstanceBadge rule, on web and mobile. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 8fb068c commit ef47feb

3 files changed

Lines changed: 52 additions & 14 deletions

File tree

‎apps/mobile/src/features/threads/ThreadFeed.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,7 @@ function renderFeedEntry(
15851585
environmentId={props.environmentId}
15861586
projectedItem={entry.activities[0]!.projectedItem}
15871587
iconColor={iconSubtleColor}
1588+
surfaceColor={props.screenColor}
15881589
/>
15891590
);
15901591
}

‎apps/mobile/src/features/threads/thread-handoff-row.tsx‎

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { useAtomValue } from "@effect/atom-react";
22
import { scopeThreadRef } from "@t3tools/client-runtime/environment";
33
import { resolveHandoffEndpoints } from "@t3tools/client-runtime/handoff";
4-
import { resolveProviderInstanceDisplayName } from "@t3tools/client-runtime/state/provider-instance-display";
4+
import {
5+
normalizeProviderAccentColor,
6+
resolveProviderInstanceDisplayName,
7+
shouldShowInstanceBadge,
8+
} from "@t3tools/client-runtime/state/provider-instance-display";
59
import type {
610
EnvironmentId,
711
OrchestrationV2ProjectedTurnItem,
@@ -13,7 +17,7 @@ import { Fragment, useMemo } from "react";
1317
import { Alert, Pressable, View, type ColorValue } from "react-native";
1418
import { AppText as Text } from "../../components/AppText";
1519
import { SymbolView } from "../../components/AppSymbol";
16-
import { ProviderIcon } from "../../components/ProviderIcon";
20+
import { ProviderIcon, ProviderInstanceIcon } from "../../components/ProviderIcon";
1721
import { environmentThreadDetails } from "../../state/threads";
1822
import { serverEnvironment } from "../../state/server";
1923
import { ThreadContextDivider } from "./thread-context-divider";
@@ -38,6 +42,7 @@ export function ThreadHandoffRow(props: {
3842
environmentId: EnvironmentId;
3943
projectedItem: OrchestrationV2ProjectedTurnItem;
4044
iconColor: ColorValue;
45+
surfaceColor: string;
4146
}) {
4247
const { item } = props.projectedItem;
4348
const endpointsAtom = useMemo(
@@ -48,6 +53,7 @@ export function ThreadHandoffRow(props: {
4853
const config = useAtomValue(serverEnvironment.configValueAtom(props.environmentId));
4954
if (item.type !== "handoff" || endpoints === null) return null;
5055
const color = item.status === "failed" ? "#e11d48" : props.iconColor;
56+
const endpointProps = { providers: config?.providers ?? [], surfaceColor: props.surfaceColor };
5157
return (
5258
<ThreadContextDivider
5359
label="Context handoff"
@@ -63,13 +69,13 @@ export function ThreadHandoffRow(props: {
6369
,
6470
</Text>
6571
) : null}
66-
<HandoffEndpoint {...endpoint} providers={config?.providers ?? []} />
72+
<HandoffEndpoint {...endpoint} {...endpointProps} />
6773
</Fragment>
6874
))}
6975
{endpoints.from.length > 0 ? (
7076
<SymbolView name="arrow.right" size={12} tintColor={color} />
7177
) : null}
72-
<HandoffEndpoint {...endpoints.to} providers={config?.providers ?? []} />
78+
<HandoffEndpoint {...endpoints.to} {...endpointProps} />
7379
</View>
7480
</ThreadContextDivider>
7581
);
@@ -79,6 +85,7 @@ function HandoffEndpoint(props: {
7985
instanceId: ProviderInstanceId;
8086
model?: string | undefined;
8187
providers: ReadonlyArray<ServerProvider>;
88+
surfaceColor: string;
8289
}) {
8390
const provider = props.providers.find((candidate) => candidate.instanceId === props.instanceId);
8491
const model = provider?.models.find((candidate) => candidate.slug === props.model);
@@ -87,12 +94,21 @@ function HandoffEndpoint(props: {
8794
model?.name ??
8895
props.model ??
8996
(provider ? resolveProviderInstanceDisplayName(provider) : props.instanceId);
97+
const accentColor = normalizeProviderAccentColor(provider?.accentColor);
98+
// Same account badge as the thread rows: a handoff between two accounts of
99+
// one provider would otherwise show the same glyph on both sides.
100+
const showBadge =
101+
provider !== undefined &&
102+
shouldShowInstanceBadge(
103+
{ driverKind: provider.driver, accentColor },
104+
props.providers.map((candidate) => ({ driverKind: candidate.driver })),
105+
);
90106
return (
91107
<Pressable
92108
accessibilityRole="button"
93109
accessibilityLabel={label}
94110
accessibilityHint="Show provider account"
95-
className="min-h-6 max-w-full flex-row items-center justify-center gap-1"
111+
className="min-h-6 max-w-full flex-row items-center justify-center gap-1.5"
96112
hitSlop={{ top: 8, bottom: 8 }}
97113
onPress={() =>
98114
Alert.alert(
@@ -101,11 +117,23 @@ function HandoffEndpoint(props: {
101117
)
102118
}
103119
>
104-
<ProviderIcon
105-
provider={provider?.driver ?? props.instanceId}
106-
iconUrl={provider?.iconUrl}
107-
size={12}
108-
/>
120+
{showBadge && provider !== undefined ? (
121+
<ProviderInstanceIcon
122+
provider={provider.driver}
123+
iconUrl={provider.iconUrl}
124+
size={14}
125+
displayName={resolveProviderInstanceDisplayName(provider)}
126+
accentColor={accentColor}
127+
showBadge
128+
surfaceColor={props.surfaceColor}
129+
/>
130+
) : (
131+
<ProviderIcon
132+
provider={provider?.driver ?? props.instanceId}
133+
iconUrl={provider?.iconUrl}
134+
size={12}
135+
/>
136+
)}
109137
<Text numberOfLines={1} className="shrink text-xs text-foreground-muted">
110138
{label}
111139
</Text>

‎apps/web/src/components/chat/V2LifecycleRow.tsx‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
} from "lucide-react";
3838

3939
import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip";
40-
import { getProviderInstanceEntry } from "../../providerInstances";
40+
import { deriveProviderInstanceEntries, shouldShowInstanceBadge } from "../../providerInstances";
4141
import { formatShortTimestamp } from "../../timestampFormat";
4242
import { getTriggerDisplayModelName } from "./providerIconUtils";
4343
import { ProviderInstanceIcon, providerTextColorClassName } from "./ProviderInstanceIcon";
@@ -556,7 +556,11 @@ function HandoffEndpoint(props: {
556556
readonly instanceId: ProviderInstanceId;
557557
readonly model?: string | undefined;
558558
}) {
559-
const entry = getProviderInstanceEntry(props.providers, props.instanceId);
559+
const entries = deriveProviderInstanceEntries(props.providers);
560+
const entry = entries.find((candidate) => candidate.instanceId === props.instanceId);
561+
// Same account badge as the sidebar: a handoff between two accounts of one
562+
// provider would otherwise show the same glyph on both sides.
563+
const showBadge = entry !== undefined && shouldShowInstanceBadge(entry, entries);
560564
const model = props.model?.trim();
561565
const providerModel =
562566
model === undefined || model.length === 0
@@ -574,14 +578,19 @@ function HandoffEndpoint(props: {
574578
render={
575579
<span
576580
tabIndex={0}
577-
className="inline-flex min-w-0 items-center gap-1 rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
581+
className="inline-flex min-w-0 items-center gap-1.5 rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
578582
>
579583
<ProviderInstanceIcon
580584
driverKind={entry?.driverKind ?? ProviderDriverKind.make(props.instanceId)}
581585
displayName={entry?.displayName ?? props.instanceId}
586+
accentColor={entry?.accentColor}
582587
acpRegistryAgentId={entry?.acpRegistryAgentId}
583588
acpRegistryIconUrl={entry?.acpRegistryIconUrl}
584-
iconClassName="size-3"
589+
showBadge={showBadge}
590+
// Glyph dims, badge stays saturated; sized like the sidebar row.
591+
iconClassName={cn("size-3.5", showBadge && "opacity-60")}
592+
badgeClassName="right-[-0.1875rem] bottom-[-0.1875rem] h-3 min-w-3 px-0.5 text-5xs"
593+
indicatorBackground="var(--background)"
585594
/>
586595
<span
587596
className={cn(

0 commit comments

Comments
 (0)