diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts index 282d7f9c4d..40f89acf4d 100644 --- a/apps/web/src/components/Sidebar.logic.test.ts +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -441,6 +441,36 @@ describe("resolveThreadStatusPill", () => { ).toMatchObject({ label: "Working", pulse: true }); }); + it("exposes the codex provider when a codex thread is running", () => { + expect( + resolveThreadStatusPill({ + thread: { + ...baseThread, + session: { + ...baseThread.session, + provider: "codex", + status: "running", + }, + }, + }), + ).toMatchObject({ label: "Working", workingProvider: "codex", pulse: true }); + }); + + it("exposes the claude provider when a claude thread is running", () => { + expect( + resolveThreadStatusPill({ + thread: { + ...baseThread, + session: { + ...baseThread.session, + provider: "claudeAgent", + status: "running", + }, + }, + }), + ).toMatchObject({ label: "Working", workingProvider: "claudeAgent", pulse: true }); + }); + it("shows plan ready when a settled plan turn has a proposed plan ready for follow-up", () => { expect( resolveThreadStatusPill({ @@ -491,6 +521,25 @@ describe("resolveThreadStatusPill", () => { }), ).toMatchObject({ label: "Completed", pulse: false }); }); + + it("exposes the provider when a completed thread has an unseen completion", () => { + expect( + resolveThreadStatusPill({ + thread: { + ...baseThread, + interactionMode: "default", + latestTurn: makeLatestTurn(), + lastVisitedAt: "2026-03-09T10:04:00.000Z", + session: { + ...baseThread.session, + provider: "claudeAgent", + status: "ready", + orchestrationStatus: "ready", + }, + }, + }), + ).toMatchObject({ label: "Completed", workingProvider: "claudeAgent", pulse: false }); + }); }); describe("resolveThreadRowClassName", () => { diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index ed151c6da8..c79aacaf99 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -1,5 +1,6 @@ import * as React from "react"; import type { SidebarProjectSortOrder, SidebarThreadSortOrder } from "@t3tools/contracts/settings"; +import type { ProviderKind } from "@t3tools/contracts"; import type { SidebarThreadSummary, Thread } from "../types"; import { cn } from "../lib/utils"; import { isLatestTurnSettled } from "../session-logic"; @@ -31,6 +32,7 @@ export interface ThreadStatusPill { colorClass: string; dotClass: string; pulse: boolean; + workingProvider?: ProviderKind; } const THREAD_STATUS_PRIORITY: Record = { @@ -337,6 +339,7 @@ export function resolveThreadStatusPill(input: { colorClass: "text-sky-600 dark:text-sky-300/80", dotClass: "bg-sky-500 dark:bg-sky-300/80", pulse: true, + workingProvider: thread.session.provider, }; } @@ -346,6 +349,7 @@ export function resolveThreadStatusPill(input: { colorClass: "text-sky-600 dark:text-sky-300/80", dotClass: "bg-sky-500 dark:bg-sky-300/80", pulse: true, + workingProvider: thread.session.provider, }; } @@ -369,6 +373,7 @@ export function resolveThreadStatusPill(input: { colorClass: "text-emerald-600 dark:text-emerald-300/90", dotClass: "bg-emerald-500 dark:bg-emerald-300/90", pulse: false, + ...(thread.session?.provider ? { workingProvider: thread.session.provider } : {}), }; } diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index bb63db6fc0..fbf03f34bc 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -12,6 +12,7 @@ import { TriangleAlertIcon, } from "lucide-react"; import { ProjectFavicon } from "./ProjectFavicon"; +import { ClaudeAI, OpenAI } from "./Icons"; import { autoAnimate } from "@formkit/auto-animate"; import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react"; import { useShallow } from "zustand/react/shallow"; @@ -233,6 +234,18 @@ interface PrStatusIndicator { type ThreadPr = GitStatusResult["pr"]; +function providerStatusIconClassName(provider: ThreadStatusPill["workingProvider"]): string { + if (provider === "claudeAgent") { + return "text-[#d97757]"; + } + + if (provider === "codex") { + return "text-foreground"; + } + + return ""; +} + function ThreadStatusLabel({ status, compact = false, @@ -240,17 +253,37 @@ function ThreadStatusLabel({ status: ThreadStatusPill; compact?: boolean; }) { + const isCodex = status.workingProvider === "codex"; + const statusIcon = + status.workingProvider === "claudeAgent" ? ( + + ) : isCodex ? ( + + ) : null; + if (compact) { return ( - + {statusIcon ? ( + statusIcon + ) : ( + + )} {status.label} ); @@ -261,11 +294,15 @@ function ThreadStatusLabel({ title={status.label} className={`inline-flex items-center gap-1 text-[10px] ${status.colorClass}`} > - + {statusIcon ? ( + statusIcon + ) : ( + + )} {status.label} );