diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts
index f9a62c40a511..e06176dfc48e 100644
--- a/apps/web/src/components/Sidebar.logic.test.ts
+++ b/apps/web/src/components/Sidebar.logic.test.ts
@@ -395,6 +395,18 @@ describe("shouldRecedeSidebarThread", () => {
expect(shouldRecedeSidebarThread({ ...input, isActive: true })).toBe(false);
expect(shouldRecedeSidebarThread({ ...input, isSelected: true })).toBe(false);
});
+
+ it.each([false, true])("keeps input-required threads prominent with unread=%s", (isUnread) => {
+ expect(
+ shouldRecedeSidebarThread({
+ status: "input",
+ isUnread,
+ isWoke: false,
+ isActive: false,
+ isSelected: false,
+ }),
+ ).toBe(false);
+ });
});
describe("createThreadJumpHintVisibilityController", () => {
diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts
index 50650ed389dc..abb67e65a24e 100644
--- a/apps/web/src/components/Sidebar.logic.ts
+++ b/apps/web/src/components/Sidebar.logic.ts
@@ -803,9 +803,9 @@ export function shouldRecedeSidebarThread(input: {
isActive: boolean;
isSelected: boolean;
}): boolean {
- if (input.isActive || input.isSelected) return false;
+ if (input.isActive || input.isSelected || input.status === "input") return false;
if (input.status === "working" || input.status === "monitoring") return true;
- if (input.status === "ready" || input.status === "approval" || input.status === "input") {
+ if (input.status === "ready" || input.status === "approval") {
return !input.isUnread && !input.isWoke;
}
return false;
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index 445ae533c323..792aa3815fcf 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -45,12 +45,15 @@ import {
CircleCheckIcon,
CircleDashedIcon,
ClockIcon,
+ EyeIcon,
FolderIcon,
GitBranchIcon,
+ MessageCircleQuestionIcon,
PinIcon,
PinOffIcon,
PlusIcon,
SettingsIcon,
+ ShieldQuestionIcon,
SquarePenIcon,
TerminalIcon,
Undo2Icon,
@@ -1088,8 +1091,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
// switching sidebars must not light up every historical thread as unread.
const isUnread = hasUnseenCompletion({ ...thread, lastVisitedAt });
const status = resolveSidebarThreadStatus(thread);
- const isInFlight =
- status === "working" || status === "monitoring" || status === "approval" || status === "input";
// A woken thread reappears at its original position (the sort is
// deliberately static), so the pill has to carry the weight. Snoozing is
// an explicit act, so the pill clears only when the user re-engages:
@@ -1123,35 +1124,32 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
icon: "working" as const,
// No shimmer: a label that animates forever is noise in a sidebar
// full of them (and repaints every vsync on high-refresh displays).
- // Working is a background state, so it rests at the dim end of what
- // the old pulse cycled through; only the thread you have open gets
- // the label at full strength.
- className: cn("text-sky-600 dark:text-sky-400", !props.isActive && "opacity-75"),
+ className: "text-sky-600 dark:text-sky-400",
}
: status === "monitoring"
? {
// Monitoring is calm background presence, not active progress
// (monitoring-pill D6), so it keeps the label at full strength.
label: "Monitoring",
- icon: null,
- className: "text-sky-600 dark:text-sky-400",
+ icon: "monitoring" as const,
+ className: "text-foreground dark:text-white",
}
: status === "approval"
? {
label: "Approval",
- icon: null,
+ icon: "approval" as const,
className: "text-amber-700 dark:text-amber-300",
}
: status === "input"
? {
label: "Input",
- icon: null,
+ icon: "input" as const,
className: "text-indigo-600 dark:text-indigo-300",
}
: status === "failed"
? {
label: "Failed",
- icon: null,
+ icon: "failed" as const,
className: "text-red-700 dark:text-red-300",
}
: isWoke
@@ -1396,10 +1394,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
: shouldRecede
? "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground"
: "bg-transparent text-sidebar-foreground hover:bg-sidebar-row-hover",
- isInFlight &&
- !props.isActive &&
- !isSelected &&
- "opacity-70 transition-opacity hover:opacity-100",
isFileDragOver && "ring-1 ring-inset ring-primary/70",
// The hover tint must not clobber an active/selected row's own surface.
isFileDragOver && !props.isActive && !isSelected && "bg-sidebar-row-hover",
@@ -1462,7 +1456,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
"truncate",
shouldRecede
? "text-secondary-label"
- : isUnread || isWoke
+ : isUnread || isWoke || status === "input"
? "text-foreground"
: status === "failed"
? "text-foreground/95"
@@ -1472,7 +1466,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
"truncate group-focus-within/sidebar-row:text-foreground group-hover/sidebar-row:text-foreground",
shouldRecede
? "text-secondary-label/70"
- : props.isActive || isWoke
+ : props.isActive || isWoke || status === "input"
? "text-foreground"
: isUnread
? "text-muted-foreground"
@@ -1814,6 +1808,14 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
>
{topStatus.icon === "working" ? (
+ ) : topStatus.icon === "input" ? (
+
+ ) : topStatus.icon === "approval" ? (
+
+ ) : topStatus.icon === "failed" ? (
+
+ ) : topStatus.icon === "monitoring" ? (
+
) : topStatus.icon === "done" ? (
) : null}