From 99aa5b3a681a807dee736470589c3dbb1337f28b Mon Sep 17 00:00:00 2001 From: pandec Date: Sun, 19 Jul 2026 13:31:33 +0200 Subject: [PATCH] feat: show thread provider on sidebar hover --- apps/web/src/components/Sidebar.tsx | 70 ++++++++++++++----- ...e-show-provider-icon-on-hover-cy-review.md | 42 +++++++++++ ...ovider-icon-on-hover-cy-review-deferred.md | 7 ++ 3 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 rev/BR-t3code-show-provider-icon-on-hover-cy-review.md create mode 100644 rev/check/BR-t3code-show-provider-icon-on-hover-cy-review-deferred.md diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 4b30e26b316..dae2b070570 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -23,6 +23,7 @@ import { } from "./ThreadStatusIndicators"; import { ProjectFavicon } from "./ProjectFavicon"; import { SessionImportDialog } from "./SessionImportDialog"; +import { ProviderInstanceIcon } from "./chat/ProviderInstanceIcon"; import { useAtomValue } from "@effect/atom-react"; import { autoAnimate } from "@formkit/auto-animate"; import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react"; @@ -113,6 +114,7 @@ import { readLocalApi } from "../localApi"; import { useComposerDraftStore } from "../composerDraftStore"; import { useNewThreadHandler } from "../hooks/useHandleNewThread"; import { useDesktopUpdateState } from "../state/desktopUpdate"; +import { getProviderInstanceEntry } from "../providerInstances"; import { useThreadActions } from "../hooks/useThreadActions"; import { projectEnvironment } from "../state/projects"; @@ -208,7 +210,11 @@ import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; import { useIsMobile } from "~/hooks/useMediaQuery"; import { CommandDialogTrigger } from "./ui/command"; import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; -import { primaryServerConfigAtom, primaryServerKeybindingsAtom } from "../state/server"; +import { + primaryServerConfigAtom, + primaryServerKeybindingsAtom, + serverEnvironment, +} from "../state/server"; import { derivePhysicalProjectKey, deriveProjectGroupingOverrideKey, @@ -400,6 +406,11 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr reportFailure: false, }); const environment = useEnvironment(thread.environmentId); + const serverConfig = useAtomValue(serverEnvironment.configValueAtom(thread.environmentId)); + const providerInstance = getProviderInstanceEntry( + serverConfig?.providers ?? [], + thread.modelSelection.instanceId, + ); const primaryEnvironmentId = usePrimaryEnvironmentId(); const isRemoteThread = primaryEnvironmentId !== null && thread.environmentId !== primaryEnvironmentId; @@ -471,9 +482,10 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr const prStatus = prStatusIndicator(pr, gitStatus.data?.sourceControlProvider); const terminalStatus = terminalStatusFromRunningIds(runningTerminalIds); const isConfirmingArchive = confirmingArchiveThreadKey === threadKey && !isThreadRunning; + const hasThreadHoverControls = providerInstance !== undefined || !isThreadRunning; const threadMetaClassName = isConfirmingArchive ? "pointer-events-none opacity-0" - : !isThreadRunning + : hasThreadHoverControls ? "pointer-events-none transition-opacity duration-150 max-sm:pr-6 group-hover/menu-sub-item:opacity-0 group-focus-within/menu-sub-item:opacity-0" : "pointer-events-none"; const clearConfirmingArchive = useCallback(() => { @@ -798,9 +810,35 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr > Confirm - ) : !isThreadRunning ? ( - appSettingsConfirmThreadArchive ? ( -
+ ) : hasThreadHoverControls ? ( +
+ {providerInstance ? ( + + event.stopPropagation()} + /> + } + > + + + + {providerInstance.displayName} · {thread.modelSelection.model} + + + ) : null} + {!isThreadRunning && appSettingsConfirmThreadArchive ? ( -
- ) : ( - - + ) : !isThreadRunning ? ( + + -
- } - /> - Archive - - ) + } + /> + Archive + + ) : null} + ) : null} diff --git a/rev/BR-t3code-show-provider-icon-on-hover-cy-review.md b/rev/BR-t3code-show-provider-icon-on-hover-cy-review.md new file mode 100644 index 00000000000..766c5a61ba9 --- /dev/null +++ b/rev/BR-t3code-show-provider-icon-on-hover-cy-review.md @@ -0,0 +1,42 @@ +# Cy-review: provider icon on sidebar thread hover + +- Target: `t3code/show-provider-icon-on-hover` +- Base: `dev` +- Date: 2026-07-19 +- Diff base: uncommitted working-tree diff from `HEAD` +- Round: 1 + +## Review fleet + +Two reviewers were sufficient for this narrow, single-file presentation change: + +- Skeptical Code Reviewer: correctness, state gaps, running-thread regressions, and missing-provider behavior. +- Accessibility and Design/Reuse Reviewer: keyboard access, tooltip semantics, responsive behavior, and reuse of existing provider abstractions. + +Both reviewers were instructed to use GPT-5.6 Sol at medium reasoning effort. + +## Summary + +- Raw findings: 4 +- Deduplicated findings: 3 +- Fix now: 2 +- Deferred: 1 +- Discarded: 0 + +## Combined findings + +| ID | File:line | Sources | Severity | Disposition | Rationale | +| ----- | ----------------------------------------- | ------------- | -------- | ----------- | -------------------------------------------------------------------------------------------------------------------- | +| CR-01 | `apps/web/src/components/Sidebar.tsx:484` | SC-001, AR-01 | Medium | Fix now | Running rows without a resolved provider could fade their metadata while rendering no hover replacement. | +| CR-02 | `apps/web/src/components/Sidebar.tsx:814` | AR-02 | Medium | Fix now | The provider/model tooltip trigger was not focusable, leaving the exact model unavailable to keyboard users. | +| CR-03 | `apps/web/src/components/Sidebar.tsx:409` | SC-002 | Medium | Defer | Deleted or unavailable historical provider instances have no trustworthy driver kind for branded fallback rendering. | + +## Deferred candidates + +| ID | Scope | Reason | +| ----- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CR-03 | Medium | A durable fallback requires choosing whether to persist provider presentation metadata, infer it from instance ids, or show a generic historical-provider indicator. | + +## Discarded summary + +No findings were discarded. diff --git a/rev/check/BR-t3code-show-provider-icon-on-hover-cy-review-deferred.md b/rev/check/BR-t3code-show-provider-icon-on-hover-cy-review-deferred.md new file mode 100644 index 00000000000..87c78dca009 --- /dev/null +++ b/rev/check/BR-t3code-show-provider-icon-on-hover-cy-review-deferred.md @@ -0,0 +1,7 @@ +# Deferred items: provider icon on sidebar thread hover + +## CR-03: Historical provider fallback + +If a thread's provider instance is removed or its environment configuration is unavailable, the thread still retains the instance id and model but no longer has a trustworthy driver kind or display metadata for a branded icon. This means the hover affordance can be absent for those historical or disconnected rows. The current patch does not infer branding from an arbitrary instance id because custom ids do not reliably encode their driver and a generic fallback is a product choice. Follow up by deciding whether thread shells should persist provider presentation metadata or whether the sidebar should show a generic historical-provider indicator. + +> cy-review complete — 2026-07-19T13:31:11+02:00 — rounds: 1