From 900991fb69add3547398bf1dbf8ba4902dd56121 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 9 Nov 2026 12:24:04 +0100 Subject: [PATCH 1/2] feat(web): show which provider instance is active in the model picker With several instances of one driver (one Claude Code instance per account), nothing said which one a thread runs on. The picker rail now marks the active instance with a dot and an "Active" tooltip, even while another instance is browsed. When same-driver instances could be confused, the composer model button names the instance in its tooltip and accessible name. --- .../src/components/chat/ModelPickerContent.tsx | 1 + .../src/components/chat/ModelPickerSidebar.tsx | 15 ++++++++++++++- .../chat/ProviderModelPicker.browser.test.tsx | 6 ++++++ .../src/components/chat/ProviderModelPicker.tsx | 7 ++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/chat/ModelPickerContent.tsx b/apps/web/src/components/chat/ModelPickerContent.tsx index 211bbd59dbd98..ca6e34141cdf3 100644 --- a/apps/web/src/components/chat/ModelPickerContent.tsx +++ b/apps/web/src/components/chat/ModelPickerContent.tsx @@ -834,6 +834,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { {showSidebar && ( void; onFocusSearch: () => void; instanceEntries: ReadonlyArray; @@ -131,6 +133,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { const isDisabled = (isUnavailable && !unavailableSelectionIsReachable) || isContextDisabled; const isSelected = props.selectedInstanceId === entry.instanceId; + const isActive = props.activeInstanceId === entry.instanceId; const isHovered = hoveredInstanceId === entry.instanceId; const showInstanceBadge = shouldShowInstanceBadge(entry, props.instanceEntries); @@ -138,7 +141,9 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { ? describeUnavailableInstance(entry) : isContextDisabled ? (props.getDisabledInstanceTooltip?.(entry) ?? entry.displayName) - : entry.displayName; + : isActive + ? `${entry.displayName} ยท Active` + : entry.displayName; const button = ( @@ -180,6 +186,13 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { } {...(entry.accentColor ? { badgeClassName: "h-3 min-w-3 px-0.5 text-5xs" } : {})} /> + {isActive ? ( + + ) : null} ); diff --git a/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx b/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx index b6dd1a38e86cf..252164bcb60cd 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.browser.test.tsx @@ -79,6 +79,12 @@ it("keeps each registry image when adding a model from another ACP agent", async await vi.waitFor(() => expect(imageWidths()).toEqual([11])); await screen.getByRole("button", { name: "First model", exact: true }).click(); await page.getByRole("button", { name: "Second agent", exact: true }).click(); + await expect + .element(page.getByRole("button", { name: "First agent", exact: true })) + .toHaveAttribute("aria-current", "true"); + await expect + .element(page.getByRole("button", { name: "Second agent", exact: true })) + .not.toHaveAttribute("aria-current"); await userEvent.keyboard("{Shift>}"); try { await page.getByRole("option", { name: /Second model/ }).click(); diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 2e47b4c2fe3b4..ba4672099d2a0 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -228,9 +228,13 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { const allModelNames = selectedEntries ? selectedEntries.map((selection) => selection.fullLabel).join(", ") || "Choose models" : undefined; + // Only when same-driver instances could be confused, e.g. one Claude instance per account. + const activeInstanceLabel = + showInstanceBadge && !selectedEntries ? activeEntry?.displayName : undefined; const triggerTooltipContent = [ props.triggerLabel ?? allModelNames ?? triggerLabel, props.triggerDetail, + activeInstanceLabel, shortcutLabel, ] .filter(Boolean) @@ -309,6 +313,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { aria-label={[ props.triggerAriaLabel ?? props.triggerLabel ?? allModelNames ?? triggerLabel, props.triggerDetail, + activeInstanceLabel, ] .filter(Boolean) .join(", ")} @@ -387,7 +392,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { /> ) : null} - {props.isComposerOwned ? ( + {props.isComposerOwned && !activeInstanceLabel ? ( {triggerText} ) : ( From cc2716a1d23808a19cb98772f89177267fffd637 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 11 Nov 2026 01:59:18 +0100 Subject: [PATCH 2/2] feat(web): show the active instance in the agents hover card style The composer model button's hover now uses the same ThreadHoverCard as the agents panel when same-driver instances could be confused: the model as the title, the instance with its icon and badge, then effort and the shortcut. Other pickers keep the plain tooltip. --- .../components/chat/ProviderModelPicker.tsx | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index ba4672099d2a0..de528feabdf37 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -7,9 +7,11 @@ import { } from "@t3tools/contracts"; import { createContext, memo, type ReactNode, useRef, useState } from "react"; import { Badge } from "../ui/badge"; +import { Kbd } from "../ui/kbd"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { cn } from "~/lib/utils"; +import { ThreadHoverCard, ThreadHoverCardPopup } from "../ThreadHoverCard"; import { ModelPickerContent, resolveModelPickerSelectedModel } from "./ModelPickerContent"; import { ChatGptSharingControl } from "./ChatGptSharingControl"; import { ProviderInstanceIcon } from "./ProviderInstanceIcon"; @@ -234,7 +236,6 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { const triggerTooltipContent = [ props.triggerLabel ?? allModelNames ?? triggerLabel, props.triggerDetail, - activeInstanceLabel, shortcutLabel, ] .filter(Boolean) @@ -397,7 +398,37 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { ) : ( }>{triggerText} - {triggerTooltipContent} + {activeInstanceLabel && activeEntry ? ( + // Same card as the agents panel, so the thread's instance reads the same way. + + +
+ + + {activeInstanceLabel} + +
+ {props.triggerDetail || shortcutLabel ? ( +
+ {props.triggerDetail} + {shortcutLabel ? {shortcutLabel} : null} +
+ ) : null} +
+
+ ) : ( + {triggerTooltipContent} + )}
)} {props.triggerSuffix}